第 7 篇:进阶——校验、联动、列表页

第 7 篇:进阶------校验、联动、列表页

本篇目标

在第 6 篇的单表单之上,补齐三块:

  1. 提交时统一校验(而不是输入时就报错)
  2. 字段联动显隐(showWhen
  3. 把「表单页」升级为「列表 + 新增/编辑」的完整页面

1. 提交时校验

第 6 篇的错误是「输入即显示」,更常用的交互是「点保存时才校验」。

CustomerPage 加一个 submitted 状态:

vue 复制代码
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import SchemaForm from '@/components/form/SchemaForm.vue'
import { Button } from '@/components/ui/button'
import type { PageSchema } from '@/components/form/schema'

const props = defineProps<{ schema: PageSchema }>()

const form = reactive<Record<string, any>>({})
for (const f of props.schema.fields) {
  if (f.default !== undefined) form[f.name] = f.default
}

const submitted = ref(false)

// 是否通过:所有 required 已填 + pattern 通过
const valid = computed(() =>
  props.schema.fields.every((f) => {
    const v = form[f.name]
    if (f.required && (v === undefined || v === null || v === '')) return false
    if (f.pattern && typeof v === 'string' && v && !new RegExp(f.pattern).test(v)) return false
    return true
  }),
)

function submit() {
  submitted.value = true
  if (!valid.value) return
  console.log('提交数据', form)
}
</script>

<template>
  <div>
    <SchemaForm v-model="form" :fields="schema.fields" :columns="schema.columns" :submitted="submitted" />
    <div class="mt-4 flex justify-end">
      <Button @click="submit">保存</Button>
    </div>
  </div>
</template>

配套地,把 SchemaForm.vue 的错误显示改为「仅 submitted 后显示」:

vue 复制代码
<!-- 替换原来 errorOf(field) 那两行 -->
<p v-if="submitted && errorOf(field)" class="mt-1 text-[var(--text-caption)] text-destructive">
  {{ errorOf(field) }}
</p>

并在 props 增加:

ts 复制代码
defineProps<{
  fields: FieldSchema[]
  modelValue: Record<string, any>
  columns?: number
  submitted?: boolean   // 新增:点保存后才显示错误
}>()

说明:这是最简单可靠的校验路径。字段很多、校验规则很复杂时,

可引入 vee-validate + zod 生成校验(真实常用组合),把 schema 的 required/pattern 映射成 zod 规则。

2. 字段联动(showWhen)

Schema 里已经支持 showWhen,渲染器第 6 篇的 visible() 已实现。用法:

json 复制代码
{
  "name": "contactType",
  "label": "联系方式",
  "type": "select",
  "options": [
    { "label": "手机", "value": "phone" },
    { "label": "邮箱", "value": "email" }
  ]
},
{
  "name": "email",
  "label": "邮箱",
  "type": "input",
  "showWhen": { "field": "contactType", "equals": "email" }
}

效果:联系人选「邮箱」时才显示邮箱输入框。零 JS,纯 schema 声明。

3. 列表页:DataList + 表格

表单页有了,还差「列表展示已有数据」。做一个通用列表页骨架,

用简单表格(HTML table + Tailwind,不引额外依赖,保证可落地):

创建 src/components/list/DataList.vue

vue 复制代码
<script setup lang="ts">
import type { ColumnSchema } from '@/components/form/schema'

// 简化列定义:{ key, label, width? }
defineProps<{
  columns: ColumnSchema[]
  rows: Record<string, any>[]
}>()

const emit = defineEmits<{
  (e: 'edit', row: Record<string, any>): void
}>()
</script>

<template>
  <div class="overflow-x-auto rounded-[var(--radius-lg)] border border-border">
    <table class="w-full min-w-[640px] text-left">
      <thead class="bg-muted">
        <tr>
          <th
            v-for="c in columns"
            :key="c.key"
            class="px-4 py-2.5 text-[var(--text-caption)] font-medium text-muted-foreground"
          >
            {{ c.label }}
          </th>
          <th class="px-4 py-2.5 text-right text-[var(--text-caption)] text-muted-foreground">操作</th>
        </tr>
      </thead>
      <tbody>
        <tr
          v-for="(row, i) in rows"
          :key="i"
          class="border-t border-border hover:bg-muted/40"
        >
          <td v-for="c in columns" :key="c.key" class="px-4 py-2.5 text-[var(--text-body)]">
            {{ row[c.key] ?? '-' }}
          </td>
          <td class="px-4 py-2.5 text-right">
            <Button variant="ghost" size="sm" @click="emit('edit', row)">编辑</Button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

src/components/form/schema.ts 补充列表列类型:

ts 复制代码
export interface ColumnSchema {
  key: string
  label: string
  width?: string
}

4. 组合:一个完整的「台账页」

vue 复制代码
<script setup lang="ts">
import { ref } from 'vue'
import PageShell from '@/components/layout/PageShell.vue'
import PageHeader from '@/components/layout/PageHeader.vue'
import SectionCard from '@/components/layout/SectionCard.vue'
import DataList from '@/components/list/DataList.vue'
import SchemaForm from '@/components/form/SchemaForm.vue'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import type { PageSchema, ColumnSchema } from '@/components/form/schema'

const columns: ColumnSchema[] = [
  { key: 'name', label: '姓名' },
  { key: 'dept', label: '部门' },
  { key: 'phone', label: '手机号' },
]

const rows = ref([
  { name: '张三', dept: '研发', phone: '13800000001' },
  { name: '李四', dept: '产品', phone: '13800000002' },
])

const formSchema: PageSchema = {
  title: '编辑客户',
  columns: 2,
  fields: [
    { name: 'name', label: '姓名', type: 'input', required: true },
    { name: 'dept', label: '部门', type: 'select', options: [{ label: '研发', value: 'rd' }, { label: '产品', value: 'pm' }] },
    { name: 'phone', label: '手机号', type: 'input', pattern: '^1\\d{10}$' },
  ],
}

const dialogOpen = ref(false)
const editing = ref<Record<string, any>>({})

function openEdit(row: Record<string, any>) {
  editing.value = { ...row }
  dialogOpen.value = true
}
</script>

<template>
  <PageShell>
    <PageHeader title="客户台账" description="列表 + 编辑弹窗">
      <template #actions>
        <Button @click="openEdit({})">新增</Button>
      </template>
    </PageHeader>

    <SectionCard>
      <DataList :columns="columns" :rows="rows" @edit="openEdit" />
    </SectionCard>

    <Dialog v-model:open="dialogOpen">
      <DialogContent>
        <DialogHeader><DialogTitle>{{ formSchema.title }}</DialogTitle></DialogHeader>
        <SchemaForm v-model="editing" :fields="formSchema.fields" :columns="formSchema.columns" />
      </DialogContent>
    </Dialog>
  </PageShell>
</template>

提示:Dialog 组件第 2 篇没装,先执行

pnpm dlx shadcn-vue@latest add dialog

也可不加弹窗,直接把 SchemaForm 放在列表下方做「行内编辑」。

5. 验证

  1. 必填留空点保存:不弹错误前不提交;填完再点能通过。
  2. 联系方式的联动字段:切换 select 值,邮箱框显隐正确。
  3. 列表能渲染、点「编辑」能带出数据进弹窗。
  4. 窄屏下表出现横向滚动而非挤破。

本篇小结

  • 校验时机、字段联动、列表 + 编辑,三块拼成一个完整后台页面
  • 全部仍是「固定壳 + schema 数据」的组合
  • 最后一步:怎么让 AI 稳定地只产出 schema------第 8 篇:AI 工作流与完整案例
相关推荐
VeryCool1 小时前
别吹了,依赖图像识别的GPT‑6 Astra永远快不起来
前端·javascript·aigc
临沂GEO1 小时前
芝麻开门GEO|AI数字化新趋势,助力企业线上长效增长
大数据·人工智能·python
雷焰财经1 小时前
美国推动“轻监管”AI路线:AI发展的速度与安全边界将如何平衡?
人工智能·安全·机器学习
luj_17681 小时前
虚实交融中的真实人物塑造
c语言·开发语言·网络·经验分享·算法
短视频矩阵源码定制1 小时前
GPT Image 2.0实战指南:从0到1构建合规稳定的AI服务接入方案
人工智能·gpt
小程故事多_801 小时前
企业AI对话记忆架构实战,告别单一Redis存储,搭建长短效协同的记忆体系
人工智能·redis·架构
hunterandroid1 小时前
Android 测试全景:从单元测试到 UI 自动化的完整实践
android·前端
渔阳节度使1 小时前
Cursor快速上手+科学使用指南
人工智能·ai编程
xsd202411181 小时前
AI去除图像中logo技术全解析:街舞拍摄场景的服饰与背景logo消除实战
人工智能