feat: 添加表单示例,可通过JSON格式配置生成 (#999)

* feat: 添加表单示例,可通过`JSON`格式配置生成
This commit is contained in:
xiaoming 2024-03-21 16:04:40 +08:00 committed by GitHub
parent 2dac3f193b
commit 9e0518319b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1372 additions and 274 deletions

View File

@ -131,6 +131,7 @@ menus:
hsSensitive: Sensitive Filter
hsPinyin: PinYin
hsdanmaku: Danmaku
hsSchemaForm: Form
hsPureTableBase: Base Usage
hsPureTableHigh: High Usage
hsPureTableEdit: Edit Usage

View File

@ -116,7 +116,7 @@ menus:
hsIconSelect: 图标选择器
hsTimeline: 时间线
hsLineTree: 树形连接线
hsList: 列表页
hsList: 列表页
hsListCard: 卡片列表页
hsDebounce: 防抖节流
hsFormDesign: 表单设计器
@ -131,6 +131,7 @@ menus:
hsSensitive: 敏感词过滤
hsPinyin: 汉语拼音
hsdanmaku: 弹幕
hsSchemaForm: 表单
hsPureTableBase: 基础用法
hsPureTableHigh: 高级用法
hsPureTableEdit: 可编辑用法

View File

@ -81,7 +81,7 @@
"path": "^0.12.7",
"pinia": "^2.1.7",
"pinyin-pro": "^3.19.6",
"plus-pro-components": "^0.0.2",
"plus-pro-components": "^0.0.5",
"qrcode": "^1.5.3",
"qs": "^6.12.0",
"responsive-storage": "^2.2.0",

519
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,11 @@
import { defineComponent } from "vue";
import { checkVersion } from "version-rocket";
import { ElConfigProvider } from "element-plus";
import en from "element-plus/dist/locale/en.mjs";
import { ReDialog } from "@/components/ReDialog";
import en from "element-plus/dist/locale/en.mjs";
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import plusEn from "plus-pro-components/locale/en.mjs";
import plusZhCn from "plus-pro-components/locale/zh-cn.mjs";
export default defineComponent({
name: "app",
@ -21,7 +23,9 @@ export default defineComponent({
},
computed: {
currentLocale() {
return this.$storage.locale?.locale === "zh" ? zhCn : en;
return this.$storage.locale?.locale === "zh"
? { ...zhCn, ...plusZhCn }
: { ...en, ...plusEn };
}
},
beforeCreate() {

View File

@ -38,6 +38,7 @@ import SystemLog from "@iconify-icons/ri/file-search-line";
import ListCheck from "@iconify-icons/ri/list-check";
import UbuntuFill from "@iconify-icons/ri/ubuntu-fill";
import OnlineUser from "@iconify-icons/ri/user-voice-line";
import EditBoxLine from "@iconify-icons/ri/edit-box-line";
import OperationLog from "@iconify-icons/ri/history-fill";
import InformationLine from "@iconify-icons/ri/information-line";
import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line";
@ -60,6 +61,7 @@ addIcon("ri:artboard-line", Artboard);
addIcon("ri:list-check", ListCheck);
addIcon("ri:ubuntu-fill", UbuntuFill);
addIcon("ri:user-voice-line", OnlineUser);
addIcon("ri:edit-box-line", EditBoxLine);
addIcon("ri:history-fill", OperationLog);
addIcon("ri:information-line", InformationLine);
addIcon("ri:terminal-window-line", TerminalWindowLine);

View File

@ -6,23 +6,24 @@ const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以
components = 3,
able = 4,
table = 5,
list = 6,
result = 7,
error = 8,
frame = 9,
nested = 10,
permission = 11,
system = 12,
monitor = 13,
tabs = 14,
about = 15,
editor = 16,
flowchart = 17,
formdesign = 18,
board = 19,
ppt = 20,
guide = 21,
menuoverflow = 22;
form = 6,
list = 7,
result = 8,
error = 9,
frame = 10,
nested = 11,
permission = 12,
system = 13,
monitor = 14,
tabs = 15,
about = 16,
editor = 17,
flowchart = 18,
formdesign = 19,
board = 20,
ppt = 21,
guide = 22,
menuoverflow = 23;
export {
home,
@ -31,6 +32,7 @@ export {
components,
able,
table,
form,
list,
result,
error,

View File

@ -0,0 +1,23 @@
import { $t } from "@/plugins/i18n";
import { form } from "@/router/enums";
export default {
path: "/form",
redirect: "/form/index",
meta: {
icon: "ri:edit-box-line",
title: $t("menus.hsSchemaForm"),
rank: form
},
children: [
{
path: "/form/index",
name: "SchemaForm",
component: () => import("@/views/schema-form/index.vue"),
meta: {
title: $t("menus.hsSchemaForm"),
extraIcon: "IF-pure-iconfont-new svg"
}
}
]
} satisfies RouteConfigsTable;

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import { ref, watch } from "vue";
// https://plus-pro-components.com/components/check-card-group.html
import { PlusCheckCardGroup } from "plus-pro-components";
import "plus-pro-components/es/components/check-card-group/style/css";
import { PlusCheckCardGroup } from "plus-pro-components";
defineOptions({
name: "CheckCard"

View File

@ -0,0 +1,162 @@
<script setup lang="ts">
import { ref } from "vue";
// https://plus-pro-components.com/components/form.html
import "plus-pro-components/es/components/form/style/css";
import {
type PlusColumn,
type FieldValues,
PlusForm
} from "plus-pro-components";
const state = ref<FieldValues>({
status: "1",
name: "",
rate: 4,
progress: 100,
switch: true,
time: new Date().toString(),
endTime: []
});
const rules = {
name: [
{
required: true,
message: "请输入名称"
}
]
};
const columns: PlusColumn[] = [
{
label: "名称",
width: 120,
prop: "name",
valueType: "copy",
tooltip: "我是名称"
},
{
label: "状态",
width: 120,
prop: "status",
valueType: "select",
options: [
{
label: "未解决",
value: "0",
color: "red"
},
{
label: "已解决",
value: "1",
color: "blue"
},
{
label: "解决中",
value: "2",
color: "yellow"
},
{
label: "失败",
value: "3",
color: "red"
}
]
},
{
label: "执行进度",
width: 200,
prop: "progress"
},
{
label: "评分",
width: 200,
prop: "rate",
valueType: "rate"
},
{
label: "是否显示",
width: 100,
prop: "switch",
valueType: "switch"
},
{
label: "时间",
prop: "time",
valueType: "date-picker"
},
{
label: "数量",
prop: "number",
valueType: "input-number",
fieldProps: { precision: 2, step: 2 }
},
{
label: "梦想",
prop: "gift",
valueType: "radio",
options: [
{
label: "诗",
value: "0"
},
{
label: "远方",
value: "1"
},
{
label: "美食",
value: "2"
}
]
},
{
label: "到期时间",
prop: "endTime",
valueType: "date-picker",
fieldProps: {
type: "datetimerange",
startPlaceholder: "请选择开始时间",
endPlaceholder: "请选择结束时间"
}
},
{
label: "说明",
prop: "desc",
valueType: "textarea",
fieldProps: {
maxlength: 10,
showWordLimit: true,
// @ts-expect-error
autosize: { minRows: 2, maxRows: 4 }
}
}
];
const handleChange = (values: FieldValues, prop: PlusColumn) => {
console.log(values, prop, "change");
};
const handleSubmit = (values: FieldValues) => {
console.log(values, "Submit");
};
const handleSubmitError = (err: any) => {
console.log(err, "err");
};
const handleReset = () => {
console.log("handleReset");
};
</script>
<template>
<PlusForm
v-model="state"
class="w-[450px] m-auto"
:columns="columns"
:rules="rules"
label-position="right"
@change="handleChange"
@submit="handleSubmit"
@submit-error="handleSubmitError"
@reset="handleReset"
/>
</template>

View File

@ -0,0 +1,206 @@
<script setup lang="ts">
import { ref } from "vue";
// https://plus-pro-components.com/components/dialog-form.html
import "plus-pro-components/es/components/dialog-form/style/css";
import {
type PlusColumn,
type FieldValues,
PlusDialogForm
} from "plus-pro-components";
const columns: PlusColumn[] = [
{
label: "名称",
width: 120,
prop: "name",
valueType: "copy",
tooltip: "名称最多显示6个字符"
},
{
label: "状态",
width: 120,
prop: "status",
valueType: "select",
options: [
{
label: "未解决",
value: "0",
color: "red"
},
{
label: "已解决",
value: "1",
color: "blue"
},
{
label: "解决中",
value: "2",
color: "yellow"
},
{
label: "失败",
value: "3",
color: "red"
}
]
},
{
label: "是否显示",
width: 100,
prop: "switch",
valueType: "switch"
},
{
label: "时间",
prop: "time",
valueType: "date-picker"
},
{
label: "数量",
prop: "number",
valueType: "input-number",
fieldProps: { precision: 2, step: 2 }
},
{
label: "城市",
prop: "city",
valueType: "cascader",
options: [
{
value: "0",
label: "陕西",
children: [
{
value: "0-0",
label: "西安",
children: [
{
value: "0-0-0",
label: "新城区"
},
{
value: "0-0-1",
label: "高新区"
},
{
value: "0-0-2",
label: "灞桥区"
}
]
}
]
},
{
value: "1",
label: "山西",
children: [
{
value: "1-0",
label: "太原",
children: [
{
value: "1-0-0",
label: "小店区"
},
{
value: "1-0-1",
label: "古交市"
},
{
value: "1-0-2",
label: "万柏林区"
}
]
}
]
}
]
},
{
label: "地区",
prop: "place",
tooltip: "请精确到门牌号",
fieldProps: {
placeholder: "请精确到门牌号"
}
},
{
label: "要求",
prop: "demand",
valueType: "checkbox",
options: [
{
label: "四六级",
value: "0"
},
{
label: "计算机二级证书",
value: "1"
},
{
label: "普通话证书",
value: "2"
}
]
},
{
label: "梦想",
prop: "gift",
valueType: "radio",
options: [
{
label: "诗",
value: "0"
},
{
label: "远方",
value: "1"
},
{
label: "美食",
value: "2"
}
]
},
{
label: "到期时间",
prop: "endTime",
valueType: "date-picker",
fieldProps: {
type: "datetimerange",
startPlaceholder: "请选择开始时间",
endPlaceholder: "请选择结束时间"
}
},
{
label: "说明",
prop: "desc",
valueType: "textarea",
fieldProps: {
maxlength: 10,
showWordLimit: true,
// @ts-expect-error
autosize: { minRows: 2, maxRows: 4 }
}
}
];
const visible = ref(false);
const values = ref<FieldValues>({});
const handleOpen = () => {
visible.value = true;
};
</script>
<template>
<div>
<el-button @click="handleOpen">打开弹窗表单</el-button>
<PlusDialogForm
v-model:visible="visible"
v-model="values"
:form="{ columns }"
/>
</div>
</template>

View File

@ -0,0 +1,206 @@
<script setup lang="ts">
import { ref } from "vue";
// https://plus-pro-components.com/components/drawer-form.html
import "plus-pro-components/es/components/drawer-form/style/css";
import {
type PlusColumn,
type FieldValues,
PlusDrawerForm
} from "plus-pro-components";
const columns: PlusColumn[] = [
{
label: "名称",
width: 120,
prop: "name",
valueType: "copy",
tooltip: "名称最多显示6个字符"
},
{
label: "状态",
width: 120,
prop: "status",
valueType: "select",
options: [
{
label: "未解决",
value: "0",
color: "red"
},
{
label: "已解决",
value: "1",
color: "blue"
},
{
label: "解决中",
value: "2",
color: "yellow"
},
{
label: "失败",
value: "3",
color: "red"
}
]
},
{
label: "是否显示",
width: 100,
prop: "switch",
valueType: "switch"
},
{
label: "时间",
prop: "time",
valueType: "date-picker"
},
{
label: "数量",
prop: "number",
valueType: "input-number",
fieldProps: { precision: 2, step: 2 }
},
{
label: "城市",
prop: "city",
valueType: "cascader",
options: [
{
value: "0",
label: "陕西",
children: [
{
value: "0-0",
label: "西安",
children: [
{
value: "0-0-0",
label: "新城区"
},
{
value: "0-0-1",
label: "高新区"
},
{
value: "0-0-2",
label: "灞桥区"
}
]
}
]
},
{
value: "1",
label: "山西",
children: [
{
value: "1-0",
label: "太原",
children: [
{
value: "1-0-0",
label: "小店区"
},
{
value: "1-0-1",
label: "古交市"
},
{
value: "1-0-2",
label: "万柏林区"
}
]
}
]
}
]
},
{
label: "地区",
prop: "place",
tooltip: "请精确到门牌号",
fieldProps: {
placeholder: "请精确到门牌号"
}
},
{
label: "要求",
prop: "demand",
valueType: "checkbox",
options: [
{
label: "四六级",
value: "0"
},
{
label: "计算机二级证书",
value: "1"
},
{
label: "普通话证书",
value: "2"
}
]
},
{
label: "梦想",
prop: "gift",
valueType: "radio",
options: [
{
label: "诗",
value: "0"
},
{
label: "远方",
value: "1"
},
{
label: "美食",
value: "2"
}
]
},
{
label: "到期时间",
prop: "endTime",
valueType: "date-picker",
fieldProps: {
type: "datetimerange",
startPlaceholder: "请选择开始时间",
endPlaceholder: "请选择结束时间"
}
},
{
label: "说明",
prop: "desc",
valueType: "textarea",
fieldProps: {
maxlength: 10,
showWordLimit: true,
// @ts-expect-error
autosize: { minRows: 2, maxRows: 4 }
}
}
];
const visible = ref(false);
const values = ref<FieldValues>({});
const handleOpen = () => {
visible.value = true;
};
</script>
<template>
<div>
<el-button @click="handleOpen">打开抽屉表单</el-button>
<PlusDrawerForm
v-model:visible="visible"
v-model="values"
:form="{ columns }"
/>
</div>
</template>

View File

@ -0,0 +1,162 @@
<script setup lang="ts">
import { ref } from "vue";
// https://plus-pro-components.com/components/search.html
import "plus-pro-components/es/components/search/style/css";
import { type PlusColumn, PlusSearch } from "plus-pro-components";
const state = ref({
status: "0",
time: new Date().toString()
});
const columns: PlusColumn[] = [
{
label: "名称",
prop: "name",
valueType: "copy",
tooltip: "名称最多显示6个字符"
},
{
label: "状态",
prop: "status",
valueType: "select",
options: [
{
label: "未解决",
value: "0",
color: "red"
},
{
label: "已解决",
value: "1",
color: "blue"
},
{
label: "解决中",
value: "2",
color: "yellow"
},
{
label: "失败",
value: "3",
color: "red"
}
]
},
{
label: "时间",
prop: "time",
valueType: "date-picker"
},
{
label: "数量",
prop: "number",
valueType: "input-number",
fieldProps: { precision: 2, step: 2 }
},
{
label: "城市",
prop: "city",
valueType: "cascader",
options: [
{
value: "0",
label: "陕西",
children: [
{
value: "0-0",
label: "西安",
children: [
{
value: "0-0-0",
label: "新城区"
},
{
value: "0-0-1",
label: "高新区"
},
{
value: "0-0-2",
label: "灞桥区"
}
]
}
]
},
{
value: "1",
label: "山西",
children: [
{
value: "1-0",
label: "太原",
children: [
{
value: "1-0-0",
label: "小店区"
},
{
value: "1-0-1",
label: "古交市"
},
{
value: "1-0-2",
label: "万柏林区"
}
]
}
]
}
]
},
{
label: "地区",
prop: "place",
tooltip: "请精确到门牌号",
fieldProps: {
placeholder: "请精确到门牌号"
}
},
{
label: "到期时间",
prop: "endTime",
valueType: "date-picker",
fieldProps: {
type: "datetimerange",
startPlaceholder: "请选择",
endPlaceholder: "请选择"
}
},
{
label: "奖励",
prop: "price"
},
{
label: "提成",
prop: "percentage"
}
];
const handleChange = (values: any) => {
console.log(values, "change");
};
const handleSearch = (values: any) => {
console.log(values, "search");
};
const handleRest = () => {
console.log("handleRest");
};
</script>
<template>
<PlusSearch
v-model="state"
:columns="columns"
:show-number="2"
label-width="80"
label-position="right"
@change="handleChange"
@search="handleSearch"
@reset="handleRest"
/>
</template>

View File

@ -0,0 +1,204 @@
<script setup lang="ts">
import { ref } from "vue";
// https://plus-pro-components.com/components/steps-form.html
import "plus-pro-components/es/components/steps-form/style/css";
import { PlusStepsForm } from "plus-pro-components";
const stepForm = ref([
{
title: "第一步",
form: {
labelPosition: "top",
style: {
width: "400px",
margin: "40px auto"
},
modelValue: {},
columns: [
{
label: "名称",
width: 120,
prop: "name",
valueType: "copy",
tooltip: "名称最多显示6个字符"
},
{
label: "状态",
width: 120,
prop: "status",
valueType: "select",
options: [
{
label: "未解决",
value: "0",
color: "red"
},
{
label: "已解决",
value: "1",
color: "blue"
},
{
label: "解决中",
value: "2",
color: "yellow"
},
{
label: "失败",
value: "3",
color: "red"
}
]
}
],
rules: {
name: [
{
required: true,
message: "请输入名称"
}
]
}
}
},
{
title: "第二步",
form: {
labelPosition: "top",
style: {
width: "400px",
margin: "40px auto"
},
labelWidth: "100",
modelValue: {},
columns: [
{
label: "标签",
width: 120,
prop: "tag"
},
{
label: "执行进度",
width: 200,
prop: "progress"
},
{
label: "评分",
width: 200,
prop: "rate",
valueType: "rate"
},
{
label: "是否显示",
width: 100,
prop: "switch",
valueType: "switch"
}
],
rules: {
tag: [
{
required: true,
message: "请输入标签"
}
],
progress: [
{
required: true,
message: "请输入执行进度"
}
]
}
}
},
{
title: "第三步",
form: {
labelPosition: "top",
style: {
width: "400px",
margin: "40px auto"
},
modelValue: {},
columns: [
{
label: "时间",
prop: "time",
valueType: "date-picker"
},
{
label: "要求",
prop: "demand",
valueType: "checkbox",
options: [
{
label: "四六级",
value: "0"
},
{
label: "计算机二级证书",
value: "1"
},
{
label: "普通话证书",
value: "2"
}
]
},
{
label: "奖励",
prop: "price"
},
{
label: "提成",
prop: "percentage"
},
{
label: "说明",
prop: "desc",
valueType: "textarea",
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
}
],
rules: {
time: [
{
required: true,
trigger: "change",
message: "请选择时间"
}
],
demand: [
{
required: true,
trigger: "change",
message: "请选择要求"
}
]
}
}
}
]);
const active = ref(1);
const next = (actives: number, values: any) => {
active.value = actives;
console.log(active, values, stepForm.value);
};
</script>
<template>
<PlusStepsForm
v-model="active"
simple
class="w-[800px] m-auto"
:data="stepForm"
align-center
@next="next"
/>
</template>

View File

@ -0,0 +1,67 @@
<script setup lang="ts">
import { ref } from "vue";
import { list } from "./list";
defineOptions({
name: "SchemaForm"
});
const selected = ref(0);
function tabClick({ index }) {
selected.value = index;
}
</script>
<template>
<el-card shadow="never" :body-style="{ height: 'calc(100vh - 180px)' }">
<template #header>
<div class="card-header">
<span class="font-medium">
JSON 格式配置表单采用优秀开源的
<el-link
href="https://plus-pro-components.com/components/form.html"
target="_blank"
style="margin: 0 4px 5px; font-size: 16px"
>
PlusProComponents
</el-link>
维护整体表单只需操作 columns 配置即可
</span>
</div>
</template>
<el-tabs @tab-click="tabClick">
<template v-for="(item, index) of list" :key="item.key">
<el-tab-pane :lazy="true">
<template #label>
<el-tooltip
:content="`(第 ${index + 1} 个示例)${item.content}`"
placement="top-end"
>
<span>{{ item.title }}</span>
</el-tooltip>
</template>
<component :is="item.component" v-if="selected == index" />
</el-tab-pane>
</template>
</el-tabs>
</el-card>
</template>
<style scoped>
:deep(.el-tabs__nav-wrap)::after {
height: 1px;
}
:deep(.el-tabs__nav-next),
:deep(.el-tabs__nav-prev) {
font-size: 16px;
color: var(--el-text-color-primary);
}
:deep(.el-tabs__nav-next.is-disabled),
:deep(.el-tabs__nav-prev.is-disabled) {
opacity: 0.5;
}
</style>

View File

@ -0,0 +1,41 @@
import Base from "./form/base.vue";
import Dialog from "./form/dialog.vue";
import Drawer from "./form/drawer.vue";
import Steps from "./form/steps.vue";
import Search from "./form/search.vue";
const rendContent = (val: string) =>
`代码位置src/views/schema-form/form/${val}.vue`;
export const list = [
{
key: "base",
content: rendContent("base"),
title: "基础表单",
component: Base
},
{
key: "dialog",
content: rendContent("dialog"),
title: "弹框表单",
component: Dialog
},
{
key: "drawer",
content: rendContent("drawer"),
title: "抽屉表单",
component: Drawer
},
{
key: "steps",
content: rendContent("steps"),
title: "分步表单",
component: Steps
},
{
key: "search",
content: rendContent("search"),
title: "搜索表单",
component: Search
}
];

View File

@ -14,3 +14,5 @@ declare module "vue-virtual-scroller";
declare module "vuedraggable/src/vuedraggable";
declare module "element-plus/dist/locale/en.mjs";
declare module "element-plus/dist/locale/zh-cn.mjs";
declare module "plus-pro-components/locale/en.mjs";
declare module "plus-pro-components/locale/zh-cn.mjs";