项目 6 数据存储:仿冒 SqlClient(T-SQL 转写成 SQLite)、数据库页面、DataGridView、DateTimePicker
- 每个学生一套数据库,长期保存(data/databases/<用户编号>/库名.db);学生程序运行时挂进沙箱可读写 - Sim.Data:SqlConnection / SqlCommand / SqlDataReader / SqlDataAdapter / SqlCommandBuilder / SqlParameter; 连接字符串只按库名打开学生自己的库;ScriptDom 解析 T-SQL 后按白名单转写成 SQLite 语句, STRICT 表 + 元数据表还原 SQL Server 的列类型,报错编号和中文信息与 SQL Server 一致; INSERT 不写列名时跳过自增列,日期统一格式(书上 DateTime.ToString() 拼接的日期能正确比较) - 常驻 SQL 服务(Sim.Runner sql-server):数据库页面的查询、对象资源管理器,评分前按实验 database.sql 建库 - 数据库页面(代替 SSMS):对象资源管理器、查询窗口、编辑前 200 行、表设计器、恢复实验初始数据 - DataGridView(绑定 DataView/DataTable/DataSet,单元格修改写回 DataTable)、DateTimePicker;设计器工具箱加入 - 评分:每个检查项一份新建的实验库;新增点 / 修改表格单元格、选日期、表格里有 / 没有某行的检查 - 示例:项目 6 任务 2、3;时间格式改成和中文 Windows 一致(9:05:03);异常堆栈不暴露服务器路径 - 测试:数据库端到端、接口(每人只看到自己的库、恢复实验数据)、评分出错情况、T-SQL 生成 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b088240da
commit
670abe5921
72 files changed
+7253
-51
No files matched your search
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
// 日期时间选择器:文字由运行器按 Format 生成(如 2024年1月16日),点下拉按钮弹出浏览器的日期 / 时间选择框,
|
||||
// 选好后把完整的日期时间发给运行器(只选日期时保留原来的时间,和 WinForms 一样)。
|
||||
import { computed, ref, type CSSProperties } from 'vue'
|
||||
import type { ControlNode, UserAction } from '@iot/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
node: ControlNode
|
||||
style: CSSProperties
|
||||
enabled: boolean
|
||||
act: (action: UserAction) => void
|
||||
}>()
|
||||
|
||||
const p = computed(() => props.node.props)
|
||||
const value = computed(() => String(p.value.Value ?? '2000-01-01T00:00:00'))
|
||||
const hasDate = computed(() => p.value.HasDate !== false)
|
||||
const hasTime = computed(() => p.value.HasTime === true)
|
||||
const inputType = computed(() => (hasDate.value && hasTime.value ? 'datetime-local' : hasTime.value ? 'time' : 'date'))
|
||||
const nativeValue = computed(() => {
|
||||
const [date, time] = value.value.split('T')
|
||||
return inputType.value === 'date' ? date : inputType.value === 'time' ? time : value.value
|
||||
})
|
||||
const picker = ref<HTMLInputElement>()
|
||||
|
||||
function open() {
|
||||
if (!props.enabled) return
|
||||
props.act({ action: 'dropDown' })
|
||||
picker.value?.showPicker?.()
|
||||
}
|
||||
|
||||
function pick(event: Event) {
|
||||
const picked = (event.target as HTMLInputElement).value
|
||||
if (!picked) return
|
||||
const [date, time] = value.value.split('T')
|
||||
const withSeconds = (t: string) => (t.length === 5 ? `${t}:00` : t)
|
||||
const next =
|
||||
inputType.value === 'date' ? `${picked}T${time}` : inputType.value === 'time' ? `${date}T${withSeconds(picked)}` : withSeconds(picked)
|
||||
props.act({ action: 'pick', value: next })
|
||||
}
|
||||
|
||||
/** ShowUpDown 的上下箭头:日期格式按天、时间格式按分钟调整 */
|
||||
function step(delta: number) {
|
||||
if (!props.enabled) return
|
||||
const d = new Date(value.value)
|
||||
if (hasTime.value && !hasDate.value) d.setMinutes(d.getMinutes() + delta)
|
||||
else d.setDate(d.getDate() + delta)
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
props.act({
|
||||
action: 'pick',
|
||||
value: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wf-control wf-dtp" :class="{ 'wf-disabled': !enabled }" :style="style">
|
||||
<span class="wf-dtp-text">{{ p.Text }}</span>
|
||||
<span v-if="p.ShowUpDown" class="wf-dtp-spin">
|
||||
<span @mousedown.prevent="step(1)">▴</span>
|
||||
<span @mousedown.prevent="step(-1)">▾</span>
|
||||
</span>
|
||||
<span v-else class="wf-dtp-button" title="选择日期" @mousedown.prevent="open">▾</span>
|
||||
<input ref="picker" class="wf-dtp-native" :type="inputType" step="1" :value="nativeValue" tabindex="-1" @change="pick" />
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user