forked from tools/tape-springboot-sysadmin
888 lines
30 KiB
Vue
888 lines
30 KiB
Vue
<template>
|
||
<div class="demo-page">
|
||
<div class="demo-hero">
|
||
<h2 class="demo-hero__title">IView 组件库使用文档</h2>
|
||
<p class="demo-hero__desc">
|
||
<code>@/iview</code> 是基于 Ant Design Vue 封装的业务组件库,提供配置驱动的表单、筛选、表格、详情与整页布局能力。
|
||
本页包含<strong>说明文档</strong>与<strong>可交互示例</strong>,数据均为本地 Mock,无需后端接口。
|
||
</p>
|
||
<div class="demo-hero__tags">
|
||
<a-tag color="blue">Vue 3</a-tag>
|
||
<a-tag color="green">Ant Design Vue 4</a-tag>
|
||
<a-tag>配置驱动</a-tag>
|
||
<a-tag>响应式</a-tag>
|
||
</div>
|
||
</div>
|
||
|
||
<IViewTabs v-model:activeKey="activeSection" :animated="false" embedded>
|
||
<!-- ── 概览 ── -->
|
||
<IViewTabPane key="overview" tab="概览">
|
||
<div v-if="activeSection === 'overview'" class="demo-section">
|
||
<a-collapse v-model:activeKey="overviewCollapse" :bordered="false" ghost>
|
||
<a-collapse-panel key="arch" header="目录结构">
|
||
<pre class="demo-code">{{ docOverview.structure }}</pre>
|
||
</a-collapse-panel>
|
||
<a-collapse-panel key="components" header="组件一览">
|
||
<a-table :columns="componentTableColumns" :data-source="componentTableData" :pagination="false"
|
||
size="small" row-key="name" />
|
||
</a-collapse-panel>
|
||
<a-collapse-panel key="quickstart" header="快速上手">
|
||
<pre class="demo-code">{{ docOverview.quickstart }}</pre>
|
||
</a-collapse-panel>
|
||
</a-collapse>
|
||
</div>
|
||
</IViewTabPane>
|
||
|
||
<!-- ── 表单 ── -->
|
||
<IViewTabPane key="form" tab="表单 IViewForm">
|
||
<div v-if="activeSection === 'form'" class="demo-section">
|
||
<DemoDoc title="IViewForm" :docs="docForm" />
|
||
<div class="demo-live">
|
||
<IViewForm ref="formRef" v-model="formData" :config="formConfig" :read-only="formReadOnly" />
|
||
<div class="demo-actions">
|
||
<a-button type="primary" @click="handleFormSubmit">提交校验</a-button>
|
||
<a-button @click="handleFormReset">重置</a-button>
|
||
<a-button @click="formReadOnly = !formReadOnly">
|
||
{{ formReadOnly ? "切换为编辑" : "切换为只读" }}
|
||
</a-button>
|
||
</div>
|
||
<a-alert v-if="formResult" type="info" show-icon :message="'表单数据:' + formResult" style="margin-top: 16px" />
|
||
</div>
|
||
</div>
|
||
</IViewTabPane>
|
||
|
||
<!-- ── 列表 ── -->
|
||
<IViewTabPane key="list" tab="列表 IViewTable">
|
||
<div v-if="activeSection === 'list'" class="demo-section">
|
||
<DemoDoc title="IViewFilter + IViewTable" :docs="docList" />
|
||
<div class="demo-live">
|
||
<IViewFilter v-model="filterParams" :config="filterConfig" @search="handleFilterSearch"
|
||
@reset="handleFilterReset" />
|
||
<div class="demo-list-header">
|
||
<span class="demo-list-title">订单列表示例(共 {{ filteredList.length }} 条)</span>
|
||
<a-button type="primary" @click="handleAddRecord">
|
||
<template #icon><plus-outlined /></template>
|
||
新增
|
||
</a-button>
|
||
</div>
|
||
<IViewTable :loading="listLoading" :columns="listColumns" :data-source="filteredList" row-key="id"
|
||
:pagination="tablePagination" @change="handleTableChange">
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.dataIndex === 'status'">
|
||
<a-tag :color="statusColor(record.status)">{{ statusLabel(record.status) }}</a-tag>
|
||
</template>
|
||
<template v-if="column.dataIndex === 'action'">
|
||
<a-button size="small" type="link" @click="handleViewRecord(record)">查看</a-button>
|
||
<IViewConfirm importance="normal" title="确认删除该记录吗?" @confirm="() => handleDeleteRecord(record)">
|
||
<a-button size="small" type="link" danger>删除</a-button>
|
||
</IViewConfirm>
|
||
</template>
|
||
</template>
|
||
</IViewTable>
|
||
</div>
|
||
</div>
|
||
</IViewTabPane>
|
||
|
||
<!-- ── 详情 ── -->
|
||
<IViewTabPane key="detail" tab="详情 IViewDescriptions">
|
||
<div v-if="activeSection === 'detail'" class="demo-section">
|
||
<DemoDoc title="IViewDescriptions" :docs="docDetail" />
|
||
<div class="demo-live">
|
||
<IViewDescriptions :record-data="detailRecord" :columns-list="detailColumns" :column-size="2"
|
||
:hidden-label="false">
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.dataIndex === 'status'">
|
||
<a-tag :color="statusColor(record.status)">{{ statusLabel(record.status) }}</a-tag>
|
||
</template>
|
||
</template>
|
||
</IViewDescriptions>
|
||
</div>
|
||
</div>
|
||
</IViewTabPane>
|
||
|
||
<!-- ── 整页布局 ── -->
|
||
<IViewTabPane key="layout" tab="页面 ILayoutPage">
|
||
<div v-if="activeSection === 'layout'" class="demo-section">
|
||
<DemoDoc title="ILayoutPage" :docs="docLayout" />
|
||
<div class="demo-live">
|
||
<a-alert type="warning" show-icon message="ILayoutPage 需配置真实 API,完整可运行示例请参考「系统设置 → 管理员账号」页面。"
|
||
style="margin-bottom: 16px" />
|
||
<pre class="demo-code">{{ layoutPageExample }}</pre>
|
||
</div>
|
||
</div>
|
||
</IViewTabPane>
|
||
|
||
<!-- ── 通用组件 ── -->
|
||
<IViewTabPane key="common" tab="通用组件">
|
||
<div v-if="activeSection === 'common'" class="demo-section">
|
||
<DemoDoc title="IViewConfirm / IViewDrawer / IViewTabs" :docs="docCommon" />
|
||
<div class="demo-live">
|
||
<h4 class="demo-subtitle">IViewConfirm — 确认操作</h4>
|
||
<div class="demo-actions">
|
||
<IViewConfirm importance="normal" title="确认执行此操作吗?" @confirm="handleNormalConfirm">
|
||
<a-button>普通确认(PC 气泡 / 移动端 Modal)</a-button>
|
||
</IViewConfirm>
|
||
<IViewConfirm importance="important" title="危险操作确认" entity-name="确认删除" @confirm="handleImportantConfirm">
|
||
<a-button danger>重要确认(需输入文字)</a-button>
|
||
</IViewConfirm>
|
||
</div>
|
||
|
||
<h4 class="demo-subtitle">IViewDrawer — 抽屉</h4>
|
||
<a-button type="primary" @click="drawerOpen = true">打开详情抽屉</a-button>
|
||
|
||
<h4 class="demo-subtitle">消息提示 — messageTips</h4>
|
||
<div class="demo-actions">
|
||
<a-button @click="showSuccessTips('操作成功')">showSuccessTips</a-button>
|
||
<a-button @click="showErrorTips({ msg: '操作失败,请重试' })">showErrorTips</a-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</IViewTabPane>
|
||
</IViewTabs>
|
||
|
||
<IViewDrawer :open="drawerOpen" title="订单详情" @close="drawerOpen = false">
|
||
<IViewDescriptions v-if="currentRecord" :record-data="currentRecord" :columns-list="detailColumns"
|
||
:column-size="1" :hidden-label="false">
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.dataIndex === 'status'">
|
||
<a-tag :color="statusColor(record.status)">{{ statusLabel(record.status) }}</a-tag>
|
||
</template>
|
||
</template>
|
||
</IViewDescriptions>
|
||
</IViewDrawer>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from "vue";
|
||
import { PlusOutlined } from "@ant-design/icons-vue";
|
||
import IViewTabs from "@/iview/IViewTabs.vue";
|
||
import IViewTabPane from "@/iview/IViewTabPane.vue";
|
||
import IViewForm from "@/iview/IViewForm.vue";
|
||
import IViewFilter from "@/iview/IViewFilter.vue";
|
||
import IViewTable from "@/iview/IViewTable.vue";
|
||
import IViewDescriptions from "@/iview/IViewDescriptions.vue";
|
||
import IViewConfirm from "@/iview/IViewConfirm.vue";
|
||
import IViewDrawer from "@/iview/IViewDrawer.vue";
|
||
import DemoDoc from "./DemoDoc.vue";
|
||
import { showSuccessTips, showErrorTips } from "@/iview/utils/messageTips";
|
||
|
||
const activeSection = ref("overview");
|
||
const overviewCollapse = ref(["arch", "components", "quickstart"]);
|
||
|
||
// ── 文档内容 ──────────────────────────────────────────
|
||
const docOverview = {
|
||
structure: `src/iview/
|
||
├── IViewForm.vue # 配置驱动表单
|
||
├── IViewFilter.vue # 筛选栏(基于 IViewForm inline 布局)
|
||
├── IViewTable.vue # 表格(PC 表格 / 移动端卡片)
|
||
├── IViewDescriptions.vue # 详情描述列表
|
||
├── IViewConfirm.vue # 确认弹层
|
||
├── IViewDrawer.vue # 响应式抽屉
|
||
├── IViewTabs.vue # 标签页
|
||
├── ILayoutPage.vue # 整页 CRUD 布局
|
||
├── form/ # 各类型表单控件
|
||
├── display/ # 展示类子组件
|
||
├── utils/ # request、messageTips 等工具
|
||
└── urlEmbed/ # iframe 嵌入页通信`,
|
||
quickstart: `// 1. 按需引入组件
|
||
import IViewForm from "@/iview/IViewForm.vue";
|
||
import { showSuccessTips, showErrorTips } from "@/iview/utils/messageTips";
|
||
import { apiRequest } from "@/iview/utils/request";
|
||
|
||
// 2. 配置驱动表单
|
||
const formData = ref({});
|
||
const formConfig = ref({
|
||
fields: [
|
||
{ name: "username", label: "账号", type: "text", required: true },
|
||
{ name: "status", label: "状态", type: "boolean", trueValue: 1, falseValue: 0 },
|
||
],
|
||
});
|
||
|
||
// 3. 模板
|
||
<IViewForm ref="formRef" v-model="formData" :config="formConfig" />
|
||
|
||
// 4. 提交
|
||
await formRef.value.validate();
|
||
await apiRequest("api.xxx.create", formData.value);
|
||
showSuccessTips("保存成功");`,
|
||
};
|
||
|
||
const componentTableColumns = [
|
||
{ title: "组件", dataIndex: "name", width: 180 },
|
||
{ title: "用途", dataIndex: "desc" },
|
||
{ title: "典型场景", dataIndex: "scene", ellipsis: true },
|
||
];
|
||
|
||
const componentTableData = [
|
||
{ name: "IViewForm", desc: "配置驱动表单,支持 17+ 字段类型", scene: "新增/编辑、设置页" },
|
||
{ name: "IViewFilter", desc: "列表筛选栏,支持高级筛选折叠", scene: "列表页顶部搜索" },
|
||
{ name: "IViewTable", desc: "表格,PC 端可拖拽横向滚动,移动端卡片", scene: "数据列表展示" },
|
||
{ name: "IViewDescriptions", desc: "详情描述列表", scene: "详情页、抽屉内容" },
|
||
{ name: "IViewConfirm", desc: "确认操作(普通 / 重要)", scene: "删除、危险操作" },
|
||
{ name: "IViewDrawer", desc: "响应式抽屉,自动适配侧边栏宽度", scene: "详情、表单弹层" },
|
||
{ name: "IViewTabs", desc: "标签页,移动端 Strip 样式", scene: "模块切换、嵌套 Tab" },
|
||
{ name: "ILayoutPage", desc: "整页 CRUD 布局引擎", scene: "标准增删改查页面" },
|
||
{ name: "apiRequest", desc: "统一 API 请求(自动带 token)", scene: "所有后端接口调用" },
|
||
{ name: "messageTips", desc: "成功/错误消息提示", scene: "操作反馈" },
|
||
];
|
||
|
||
const docForm = [
|
||
{
|
||
title: "Props",
|
||
content: `IViewForm
|
||
v-model / modelValue Object | String 表单数据(modelFormat='json' 时为 JSON 字符串)
|
||
config Object | String 表单配置 { fields, extrasFields, formatValue, unformatValue }
|
||
readOnly Boolean 全局只读
|
||
layout 'vertical' | 'horizontal' | 'inline'
|
||
labelCol / wrapperCol Object 标签布局(inline 时无效)
|
||
disableResponsive Boolean 禁用移动端强制 vertical 布局`,
|
||
},
|
||
{
|
||
title: "暴露方法(ref)",
|
||
content: `formRef.value.validate() // 校验全部可见字段,失败 throw
|
||
formRef.value.validateField(field) // 校验单个字段
|
||
formRef.value.resetFormData() // 重置为 defaultValue`,
|
||
},
|
||
{
|
||
title: "字段配置 fields[]",
|
||
content: `通用属性:
|
||
name 字段名(必填)
|
||
label 标签
|
||
type 字段类型(见下方类型表)
|
||
required 是否必填
|
||
defaultValue 默认值
|
||
placeholder 占位符
|
||
tips 输入提示
|
||
show 显示条件,支持函数或表达式字符串,如 "!!record.guid"
|
||
disabled 禁用,支持函数
|
||
onChanged 值变化回调
|
||
|
||
字段类型:
|
||
title 分组标题(仅展示 label)
|
||
text 单行文本(minLength / maxlength / textLenMax)
|
||
textarea 多行文本(textRows / maxlength)
|
||
number 数字(min / max / numMin / numMax)
|
||
radio 单选(options: [{ value, label }])
|
||
checkbox 多选(options)
|
||
select 下拉(options,支持远程 optionsApi)
|
||
date 日期
|
||
datetime 日期时间
|
||
time 时间
|
||
boolean 开关(trueValue / falseValue)
|
||
image 图片上传
|
||
video 视频上传
|
||
file 文件上传
|
||
fileBase64 Base64 文件
|
||
imageBase64 Base64 图片
|
||
signatureImage 手写签名
|
||
|
||
扩展:registerIViewFormFieldComponent(type, component) 注册自定义字段类型`,
|
||
},
|
||
{
|
||
title: "代码示例",
|
||
content: `<IViewForm ref="formRef" v-model="formData" :config="formConfig" />
|
||
|
||
const formConfig = ref({
|
||
extrasFields: ["guid"], // 额外保留但不渲染的字段
|
||
fields: [
|
||
{ type: "title", label: "基础信息" },
|
||
{ name: "name", label: "姓名", type: "text", required: true, maxlength: 20 },
|
||
{ name: "city", label: "城市", type: "select", options: [
|
||
{ value: "gz", label: "广州" },
|
||
]},
|
||
{ name: "enabled", label: "启用", type: "boolean", trueValue: 1, falseValue: 0 },
|
||
],
|
||
});`,
|
||
},
|
||
];
|
||
|
||
const docList = [
|
||
{
|
||
title: "IViewFilter Props",
|
||
content: `config Object 筛选项配置 { fields: [...] },字段同 IViewForm
|
||
v-model Object 筛选参数
|
||
searchButtonText String 搜索按钮文字,默认「搜索」
|
||
resetButtonText String 重置按钮文字,默认「重置」
|
||
|
||
字段 advanced: true → PC 端归入「高级选项」折叠区
|
||
移动端首个筛选项始终可见,其余可收入「高级筛选」
|
||
|
||
事件:@search @reset @init`,
|
||
},
|
||
{
|
||
title: "IViewTable Props",
|
||
content: `columns Array 列配置
|
||
data-source Array 数据源
|
||
row-key String 行唯一键
|
||
loading Boolean 加载态
|
||
pagination Object | false 分页配置,false 关闭分页
|
||
show-filter Boolean 是否显示列显示设置
|
||
|
||
列配置:
|
||
title / dataIndex 标题 / 字段名
|
||
width 列宽
|
||
bodySlot: true 使用 #bodyCell 插槽自定义渲染
|
||
fixed: 'right' 固定列
|
||
ellipsis 超长省略
|
||
customRender 函数 ({ record, column, mobile }) => HTML 字符串
|
||
|
||
事件:@change(pagination, filters, sorter)
|
||
|
||
特性:PC 端支持列宽拖拽、横向拖拽滚动条;移动端自动切换为卡片布局`,
|
||
},
|
||
{
|
||
title: "代码示例",
|
||
content: `<IViewFilter v-model="filterParams" :config="filterConfig" @search="onSearch" />
|
||
|
||
<IViewTable :columns="columns" :data-source="list" row-key="id"
|
||
:pagination="{ current: 1, pageSize: 10, total: 100 }"
|
||
@change="handleTableChange">
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.dataIndex === 'action'">
|
||
<a-button @click="edit(record)">编辑</a-button>
|
||
</template>
|
||
</template>
|
||
</IViewTable>`,
|
||
},
|
||
];
|
||
|
||
const docDetail = [
|
||
{
|
||
title: "Props",
|
||
content: `record-data Object 详情数据对象
|
||
columns-list Array 列配置(同表格列,支持 bodySlot / customRender / span)
|
||
column-size Number 每行列数,默认 2
|
||
hidden-label Boolean 是否隐藏标签,默认 true`,
|
||
},
|
||
{
|
||
title: "列配置扩展",
|
||
content: `span Number 占列数(如 span: 2 占满一行)
|
||
bodySlot: true 使用 #bodyCell 插槽
|
||
show Function | String 条件显示
|
||
customRender Function ({ record, column, mobile }) => HTML
|
||
|
||
<IViewDescriptions :record-data="record" :columns-list="columns" :hidden-label="false">
|
||
<template #bodyCell="{ column, record }">
|
||
<a-tag v-if="column.dataIndex === 'status'">{{ record.status }}</a-tag>
|
||
</template>
|
||
</IViewDescriptions>`,
|
||
},
|
||
];
|
||
|
||
const docCommon = [
|
||
{
|
||
title: "IViewConfirm",
|
||
content: `importance 'normal' | 'important'
|
||
normal PC 端 Popconfirm 气泡;移动端 Modal
|
||
important Modal + 需输入 entityName 文字才能确认
|
||
|
||
title 确认标题
|
||
entityName important 模式下需输入的确认文字
|
||
@confirm 确认回调,可返回 Promise 自动 loading
|
||
|
||
<IViewConfirm importance="normal" title="确认删除?" @confirm="handleDelete">
|
||
<a-button danger>删除</a-button>
|
||
</IViewConfirm>`,
|
||
},
|
||
{
|
||
title: "IViewDrawer",
|
||
content: `open / v-model:open 是否打开
|
||
title 标题
|
||
width 固定宽度(可选,默认自适应)
|
||
flushContent 去掉内边距,适合 iframe 全屏
|
||
@close 关闭回调
|
||
|
||
<IViewDrawer :open="open" title="详情" @close="open = false">
|
||
<!-- 内容 -->
|
||
</IViewDrawer>`,
|
||
},
|
||
{
|
||
title: "IViewTabs",
|
||
content: `v-model:activeKey 当前 Tab key
|
||
embedded 嵌入模式(去掉外层 padding)
|
||
level 'primary' | 'secondary' 层级样式
|
||
animated 是否开启动画
|
||
|
||
<IViewTabs v-model:activeKey="key" :animated="false" embedded>
|
||
<IViewTabPane key="a" tab="Tab A">...</IViewTabPane>
|
||
<IViewTabPane key="b" tab="Tab B">...</IViewTabPane>
|
||
</IViewTabs>`,
|
||
},
|
||
{
|
||
title: "messageTips / apiRequest",
|
||
content: `import { showSuccessTips, showErrorTips } from "@/iview/utils/messageTips";
|
||
import { apiRequest } from "@/iview/utils/request";
|
||
|
||
showSuccessTips("保存成功");
|
||
showErrorTips(error); // 支持表单 errorFields 自动滚动定位
|
||
|
||
const data = await apiRequest("api.system.admin.info.list", { page: 1 });
|
||
// api 字符串对应后端接口标识,请求体自动附带 token`,
|
||
},
|
||
];
|
||
|
||
const docLayout = [
|
||
{
|
||
title: "config 结构",
|
||
content: `const config = ref({
|
||
listPages: [{ // 列表页(可多个 Tab)
|
||
title, name, api, rowKey, isDefault,
|
||
showFilter, pagination, columns, filters,
|
||
defaultParams,
|
||
}],
|
||
formPages: [{ // 表单页(抽屉内)
|
||
name, title,
|
||
submitApi, // 新增提交接口
|
||
recordApi, // 编辑时拉取详情(update 场景)
|
||
paramsKey: ["guid"], // 从 record 取参的字段
|
||
formConfig: { fields: [...] },
|
||
}],
|
||
detailPages: [{ // 详情页(抽屉内)
|
||
name, title, recordApi, paramsKey, columns,
|
||
}],
|
||
infoPages: [{ // 顶部信息区(可选)
|
||
name, recordApi, columns,
|
||
}],
|
||
});`,
|
||
},
|
||
{
|
||
title: "暴露方法(pageRef)",
|
||
content: `pageRef.value.showFormPage('create') // 打开新增表单
|
||
pageRef.value.showFormPage('update', record) // 打开编辑表单
|
||
pageRef.value.showDetailPage('detail', record)// 打开详情
|
||
pageRef.value.refreshList() // 刷新列表
|
||
pageRef.value.refreshPage() // 刷新整页
|
||
pageRef.value.closeFormPage() // 关闭表单抽屉
|
||
pageRef.value.patchListRow(id, patch) // 局部更新列表行`,
|
||
},
|
||
{
|
||
title: "插槽",
|
||
content: `#listActions="{ type, refresh }" 列表操作区(标题、新增按钮等)
|
||
#bodyCell="{ column, record, type, refresh }" 自定义列/详情渲染
|
||
#listHeader / #listFooter 列表上下扩展区
|
||
#infoHeader / #infoTab / #infoFooter 信息区扩展
|
||
#expandedRowRender 表格行展开`,
|
||
},
|
||
];
|
||
|
||
// ── 表单示例 ──────────────────────────────────────────
|
||
const formRef = ref(null);
|
||
const formReadOnly = ref(false);
|
||
const formResult = ref("");
|
||
const formData = ref({
|
||
name: "张三",
|
||
age: 28,
|
||
gender: "male",
|
||
tags: ["vip"],
|
||
city: "guangzhou",
|
||
birthday: "1998-05-20",
|
||
enabled: 1,
|
||
remark: "这是一段备注说明",
|
||
});
|
||
|
||
const formConfig = ref({
|
||
fields: [
|
||
{ type: "title", label: "基础信息" },
|
||
{
|
||
name: "name",
|
||
label: "姓名",
|
||
type: "text",
|
||
required: true,
|
||
maxlength: 20,
|
||
placeholder: "请输入姓名",
|
||
tips: "2~20 个字符",
|
||
},
|
||
{
|
||
name: "age",
|
||
label: "年龄",
|
||
type: "number",
|
||
required: true,
|
||
min: 1,
|
||
max: 120,
|
||
},
|
||
{
|
||
name: "gender",
|
||
label: "性别",
|
||
type: "radio",
|
||
required: true,
|
||
options: [
|
||
{ value: "male", label: "男" },
|
||
{ value: "female", label: "女" },
|
||
],
|
||
},
|
||
{
|
||
name: "tags",
|
||
label: "标签",
|
||
type: "checkbox",
|
||
options: [
|
||
{ value: "vip", label: "VIP" },
|
||
{ value: "new", label: "新用户" },
|
||
],
|
||
},
|
||
{
|
||
name: "city",
|
||
label: "城市",
|
||
type: "select",
|
||
required: true,
|
||
options: [
|
||
{ value: "guangzhou", label: "广州" },
|
||
{ value: "shenzhen", label: "深圳" },
|
||
{ value: "beijing", label: "北京" },
|
||
],
|
||
},
|
||
{
|
||
name: "birthday",
|
||
label: "生日",
|
||
type: "date",
|
||
},
|
||
{
|
||
name: "enabled",
|
||
label: "启用状态",
|
||
type: "boolean",
|
||
trueValue: 1,
|
||
falseValue: 0,
|
||
defaultValue: 1,
|
||
},
|
||
{
|
||
name: "remark",
|
||
label: "备注",
|
||
type: "textarea",
|
||
maxlength: 200,
|
||
textRows: 3,
|
||
},
|
||
],
|
||
});
|
||
|
||
const handleFormSubmit = async () => {
|
||
try {
|
||
await formRef.value.validate();
|
||
formResult.value = JSON.stringify(formData.value);
|
||
showSuccessTips("表单校验通过");
|
||
} catch (error) {
|
||
showErrorTips(error);
|
||
}
|
||
};
|
||
|
||
const handleFormReset = () => {
|
||
formRef.value?.resetFormData?.();
|
||
formResult.value = "";
|
||
};
|
||
|
||
// ── 列表示例 ──────────────────────────────────────────
|
||
const listLoading = ref(false);
|
||
const filterParams = ref({});
|
||
const drawerOpen = ref(false);
|
||
const currentRecord = ref(null);
|
||
|
||
const mockRecords = ref([
|
||
{ id: 1, orderNo: "ORD-20250605001", customer: "广州物流有限公司", amount: 1280.5, status: 1, createTime: "2025-06-05 09:30:00" },
|
||
{ id: 2, orderNo: "ORD-20250605002", customer: "深圳科技股份", amount: 5600, status: 2, createTime: "2025-06-05 10:15:00" },
|
||
{ id: 3, orderNo: "ORD-20250605003", customer: "北京贸易集团", amount: 890, status: 0, createTime: "2025-06-05 11:00:00" },
|
||
{ id: 4, orderNo: "ORD-20250604004", customer: "上海仓储中心", amount: 3200, status: 3, createTime: "2025-06-04 16:20:00" },
|
||
{ id: 5, orderNo: "ORD-20250604005", customer: "杭州电商公司", amount: 450, status: 1, createTime: "2025-06-04 18:45:00" },
|
||
]);
|
||
|
||
const filterConfig = ref({
|
||
fields: [
|
||
{
|
||
name: "keyword",
|
||
label: "关键词",
|
||
type: "text",
|
||
placeholder: "订单号 / 客户名称",
|
||
},
|
||
{
|
||
name: "status",
|
||
label: "状态",
|
||
type: "select",
|
||
options: [
|
||
{ value: 0, label: "待处理" },
|
||
{ value: 1, label: "进行中" },
|
||
{ value: 2, label: "已完成" },
|
||
{ value: 3, label: "已取消" },
|
||
],
|
||
},
|
||
{
|
||
name: "createTime",
|
||
label: "创建日期",
|
||
type: "date",
|
||
advanced: true,
|
||
},
|
||
],
|
||
});
|
||
|
||
const listColumns = ref([
|
||
{ title: "订单号", dataIndex: "orderNo", width: 160 },
|
||
{ title: "客户", dataIndex: "customer", ellipsis: true },
|
||
{ title: "金额", dataIndex: "amount", width: 100, align: "right" },
|
||
{ title: "状态", dataIndex: "status", width: 90, bodySlot: true },
|
||
{ title: "创建时间", dataIndex: "createTime", width: 170 },
|
||
{ title: "操作", dataIndex: "action", width: 130, bodySlot: true, fixed: "right" },
|
||
]);
|
||
|
||
const filteredList = computed(() => {
|
||
let rows = [...mockRecords.value];
|
||
const { keyword, status } = filterParams.value;
|
||
if (keyword) {
|
||
const kw = String(keyword).trim().toLowerCase();
|
||
rows = rows.filter(
|
||
(r) =>
|
||
r.orderNo.toLowerCase().includes(kw) ||
|
||
r.customer.toLowerCase().includes(kw)
|
||
);
|
||
}
|
||
if (status !== undefined && status !== null && status !== "") {
|
||
rows = rows.filter((r) => r.status === Number(status));
|
||
}
|
||
return rows;
|
||
});
|
||
|
||
const tablePagination = computed(() => ({
|
||
current: paginationState.value.current,
|
||
pageSize: paginationState.value.pageSize,
|
||
total: filteredList.value.length,
|
||
showTotal: (total) => `共 ${total} 条`,
|
||
}));
|
||
|
||
const paginationState = ref({
|
||
current: 1,
|
||
pageSize: 10,
|
||
});
|
||
|
||
const statusLabel = (status) => {
|
||
const map = { 0: "待处理", 1: "进行中", 2: "已完成", 3: "已取消" };
|
||
return map[status] ?? "未知";
|
||
};
|
||
|
||
const statusColor = (status) => {
|
||
const map = { 0: "default", 1: "processing", 2: "success", 3: "error" };
|
||
return map[status] ?? "default";
|
||
};
|
||
|
||
const handleFilterSearch = () => {
|
||
paginationState.value.current = 1;
|
||
};
|
||
|
||
const handleFilterReset = () => {
|
||
filterParams.value = {};
|
||
paginationState.value.current = 1;
|
||
};
|
||
|
||
const handleTableChange = (pagination) => {
|
||
paginationState.value.current = pagination.current;
|
||
paginationState.value.pageSize = pagination.pageSize;
|
||
};
|
||
|
||
const handleViewRecord = (record) => {
|
||
currentRecord.value = record;
|
||
drawerOpen.value = true;
|
||
};
|
||
|
||
const handleDeleteRecord = (record) => {
|
||
mockRecords.value = mockRecords.value.filter((r) => r.id !== record.id);
|
||
showSuccessTips(`已删除 ${record.orderNo}`);
|
||
};
|
||
|
||
const handleAddRecord = () => {
|
||
const id = mockRecords.value.length + 1;
|
||
mockRecords.value.unshift({
|
||
id,
|
||
orderNo: `ORD-202506050${String(id).padStart(2, "0")}`,
|
||
customer: "新客户示例",
|
||
amount: 999,
|
||
status: 0,
|
||
createTime: "2025-06-05 12:00:00",
|
||
});
|
||
showSuccessTips("已添加一条示例记录");
|
||
};
|
||
|
||
// ── 详情示例 ──────────────────────────────────────────
|
||
const detailRecord = ref({
|
||
orderNo: "ORD-20250605001",
|
||
customer: "广州物流有限公司",
|
||
amount: 1280.5,
|
||
status: 1,
|
||
createTime: "2025-06-05 09:30:00",
|
||
remark: "优先配送,请联系收货人确认时间。",
|
||
});
|
||
|
||
const detailColumns = ref([
|
||
{ title: "订单号", dataIndex: "orderNo" },
|
||
{ title: "客户", dataIndex: "customer" },
|
||
{ title: "金额", dataIndex: "amount" },
|
||
{ title: "状态", dataIndex: "status", bodySlot: true },
|
||
{ title: "创建时间", dataIndex: "createTime" },
|
||
{ title: "备注", dataIndex: "remark", span: 2 },
|
||
]);
|
||
|
||
// ── 通用组件示例 ──────────────────────────────────────
|
||
const handleNormalConfirm = () => {
|
||
showSuccessTips("已确认操作");
|
||
};
|
||
|
||
const handleImportantConfirm = () => {
|
||
showSuccessTips("重要操作已确认");
|
||
};
|
||
|
||
// ── ILayoutPage 配置示例 ──────────────────────────────
|
||
const layoutPageExample = `<template>
|
||
<ILayoutPage ref="pageRef" :config="config">
|
||
<template #listActions="{ refresh }">
|
||
<a-button type="primary" @click="pageRef.showFormPage('create')">添加</a-button>
|
||
</template>
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.dataIndex === 'action'">
|
||
<a-button @click="pageRef.showFormPage('update', record)">编辑</a-button>
|
||
<IViewConfirm title="确认删除?" @confirm="() => handleDelete(record)">
|
||
<a-button danger>删除</a-button>
|
||
</IViewConfirm>
|
||
</template>
|
||
</template>
|
||
</ILayoutPage>
|
||
</template>
|
||
|
||
<script setup>
|
||
import ILayoutPage from "@/iview/ILayoutPage.vue";
|
||
import { apiRequest } from "@/iview/utils/request";
|
||
|
||
const pageRef = ref(null);
|
||
|
||
const config = ref({
|
||
listPages: [{
|
||
title: "列表标题",
|
||
name: "recordList",
|
||
api: "api.xxx.list",
|
||
rowKey: "guid",
|
||
isDefault: true,
|
||
showFilter: true,
|
||
pagination: { pageSize: 10 },
|
||
columns: [
|
||
{ title: "名称", dataIndex: "name" },
|
||
{ title: "操作", dataIndex: "action", bodySlot: true, fixed: "right" },
|
||
],
|
||
filters: { fields: [
|
||
{ name: "keyword", label: "关键词", type: "text" },
|
||
]},
|
||
}],
|
||
formPages: [{
|
||
name: "create",
|
||
title: "新增",
|
||
submitApi: "api.xxx.create",
|
||
formConfig: { fields: [/* ... */] },
|
||
}, {
|
||
name: "update",
|
||
title: "编辑",
|
||
paramsKey: ["guid"],
|
||
recordApi: "api.xxx.detail",
|
||
submitApi: "api.xxx.update",
|
||
formConfig: { fields: [/* ... */] },
|
||
}],
|
||
detailPages: [{
|
||
name: "detail",
|
||
title: "详情",
|
||
paramsKey: ["guid"],
|
||
recordApi: "api.xxx.detail",
|
||
columns: [/* ... */],
|
||
}],
|
||
});
|
||
<\/script>`;
|
||
</script>
|
||
|
||
<style scoped>
|
||
.demo-page {
|
||
width: 100%;
|
||
background: #fff;
|
||
}
|
||
|
||
.demo-hero {
|
||
margin-bottom: 20px;
|
||
padding-bottom: 16px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.demo-hero__title {
|
||
margin: 0 0 8px;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: rgba(0, 0, 0, 0.88);
|
||
}
|
||
|
||
.demo-hero__desc {
|
||
margin: 0 0 12px;
|
||
color: rgba(0, 0, 0, 0.65);
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.demo-hero__desc code {
|
||
padding: 2px 6px;
|
||
background: #f5f5f5;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.demo-hero__tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.demo-section {
|
||
padding: 4px 0 16px;
|
||
}
|
||
|
||
.demo-live {
|
||
padding: 16px;
|
||
border: 1px dashed #d9d9d9;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
}
|
||
|
||
.demo-subtitle {
|
||
margin: 20px 0 12px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: rgba(0, 0, 0, 0.65);
|
||
}
|
||
|
||
.demo-subtitle:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.demo-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.demo-list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin: 16px 0;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.demo-list-title {
|
||
color: rgba(0, 0, 0, 0.45);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.demo-code {
|
||
margin: 0;
|
||
padding: 12px 16px;
|
||
background: #fff;
|
||
border: 1px solid #f0f0f0;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
line-height: 1.65;
|
||
overflow-x: auto;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
color: #333;
|
||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||
}
|
||
</style>
|