瀏覽代碼

Merge remote-tracking branch 'origin/feat/formStart'

lanceJiang 10 月之前
父節點
當前提交
d3cd004c07
共有 26 個文件被更改,包括 1077 次插入59 次删除
  1. 1 1
      .env
  2. 1 1
      src/api/system/menu.ts
  3. 251 0
      src/components/SelectRole.vue
  4. 362 0
      src/components/SelectUser.vue
  5. 38 0
      src/components/packages/formEditor/components/FormTypes/SelectDepart/mobile.vue
  6. 48 0
      src/components/packages/formEditor/components/FormTypes/SelectDepart/pc.vue
  7. 52 0
      src/components/packages/formEditor/components/FormTypes/SelectDict/mobile.vue
  8. 52 0
      src/components/packages/formEditor/components/FormTypes/SelectDict/pc.vue
  9. 18 0
      src/components/packages/formEditor/components/FormTypes/SelectRole/mobile.vue
  10. 18 0
      src/components/packages/formEditor/components/FormTypes/SelectRole/pc.vue
  11. 18 0
      src/components/packages/formEditor/components/FormTypes/SelectUser/mobile.vue
  12. 25 0
      src/components/packages/formEditor/components/FormTypes/SelectUser/pc.vue
  13. 1 1
      src/components/packages/formEditor/components/Layout/DragGable.jsx
  14. 14 12
      src/components/packages/formEditor/components/Panels/Config/components/PropsPanel.vue
  15. 5 2
      src/components/packages/formEditor/components/Panels/Config/components/TypeComponent.vue
  16. 22 18
      src/components/packages/formEditor/componentsConfig.js
  17. 14 14
      src/components/packages/formEditor/index.vue
  18. 4 0
      src/components/packages/formEditor/locale/en.js
  19. 4 0
      src/components/packages/formEditor/locale/zh-cn.js
  20. 25 6
      src/components/packages/hooks/use-props/index.js
  21. 6 0
      src/components/packages/theme/formEditor/FormTypes/Cascader.scss
  22. 7 0
      src/components/packages/theme/formEditor/FormTypes/Select.scss
  23. 2 1
      src/components/packages/utils/field.js
  24. 1 1
      src/router/index.ts
  25. 81 1
      src/views/components/index.vue
  26. 7 1
      vite.config.ts

+ 1 - 1
.env

@@ -10,7 +10,7 @@ VITE_APP_BASE_API = 'https://apiboot.aizuda.com'
 VITE_DROP_CONSOLE = true
 
 # 本地端口号
-VITE_PORT = 3000
+VITE_PORT = 3003
 
 # 是否使用本地路由配置数据
 VITE_APP_USE_LOCAL_ROUTES = 0

+ 1 - 1
src/api/system/menu.ts

@@ -25,7 +25,7 @@ export function getMenuList(): AxiosPromise {
 	}).then((res: any) => {
 		// console.error(JSON.stringify(res), 'res...')
 		// res['menu'] = [res.menu[0]/*, res.menu[1]*/]
-		// res['menu'].unshift(...localDemoRoutes) // 注释当前test环境的菜单
+		res['menu'].unshift(...localDemoRoutes) // 注释当前test环境的菜单
 		return res
 		// return Promise.resolve({
 		// 	// 路由菜单

+ 251 - 0
src/components/SelectRole.vue

@@ -0,0 +1,251 @@
+<template>
+	<div class="le-select-role">
+		<el-input :model-value="modelValue" style="display: none" />
+		<el-tooltip v-if="!disabled" content="选择角色" placement="left">
+			<el-button style="width: 32px" @click="selectHandler">
+				<svg-icon style="font-size: 18px" icon-class="flow-group-add" />
+			</el-button>
+		</el-tooltip>
+		<FlowNodeAvatar v-for="(item, i) of modelValue" :key="i" :name="item.name">
+			<template #avatar>
+				<svg-icon icon-class="flow-group" color="#fff" />
+			</template>
+		</FlowNodeAvatar>
+		<el-dialog v-model="dialogVisible" class="le-dialog" title="选择角色" :width="460" destroy-on-close append-to-body>
+			<div class="sc-user-select sc-user-select-role">
+				<div class="sc-user-select__left">
+					<div class="sc-user-select__select">
+						<div v-loading="dataInfo.loading" class="sc-user-select__tree">
+							<el-scrollbar>
+								<el-tree
+									ref="treeRef"
+									class="menu"
+									:data="dataInfo.list"
+									:node-key="roleProps.key"
+									:props="roleProps"
+									show-checkbox
+									check-strictly
+									check-on-click-node
+									:expand-on-click-node="false"
+									:default-checked-keys="selectedIds"
+									@check-change="checkChange"
+								/>
+							</el-scrollbar>
+						</div>
+					</div>
+				</div>
+				<div class="sc-user-select__toicon">
+					<el-icon><ArrowRight /></el-icon>
+				</div>
+				<div class="sc-user-select__selected">
+					<header>已选 ({{ selected.length }})</header>
+					<ul style="padding: 0; margin: 0">
+						<el-scrollbar>
+							<li v-for="(item, index) in selected" :key="item.id">
+								<span class="name">
+									<label>{{ item.name || '-' }}</label>
+								</span>
+								<span class="delete">
+									<el-button type="danger" icon="Delete" circle size="small" @click="removeItem(index)" />
+								</span>
+							</li>
+						</el-scrollbar>
+					</ul>
+				</div>
+			</div>
+			<template #footer>
+				<el-button @click="dialogVisible = false">取消</el-button>
+				<el-button type="primary" @click="submit">确认</el-button>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+<script lang="tsx" setup>
+import { watch, unref, ref, computed, PropType } from 'vue'
+import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
+import role from '@/api/system/role.ts'
+import { ElMessage } from 'element-plus'
+
+defineOptions({ name: 'LeSelectRole' })
+const emit = defineEmits(['update:modelValue', 'change', 'input', 'update:visible', 'getCurRow'])
+type Item = { name: string; id: string; [key: string]: any }
+const props = defineProps({
+	modelValue: {
+		type: Array as PropType<Item[]>,
+		default: () => []
+	},
+	min: { type: Number, default: 0 },
+	max: { type: Number, default: 999999 },
+	disabled: {
+		type: Boolean,
+		default: false
+	},
+	// params: {
+	// 	type: Object
+	// }
+})
+const treeRef = ref()
+const dialogVisible = ref(false)
+const dataInfo = ref({ list: [], loading: false })
+const roleProps = {
+	key: 'id',
+	label: 'name',
+	children: 'children'
+}
+
+// 命中的数据
+const selected = ref<Item[]>([])
+const selectedIds = computed(() => {
+	return selected.value.map(v => v.id)
+})
+watch(
+	() => props.modelValue,
+	vals => {
+		selected.value = JSON.parse(JSON.stringify(vals || []))
+	},
+	{
+		immediate: true,
+		deep: true
+	}
+)
+const checkChange = (data, checked) => {
+	if (checked) {
+		selected.value.push({
+			id: data.id,
+			name: data.name
+		})
+	} else {
+		const idx = selected.value.findIndex(v => v.id === data.id)
+		if (idx >= 0) {
+			selected.value.splice(idx, 1)
+		}
+	}
+}
+const removeItem = (index: number) => {
+	selected.value.splice(index, 1)
+	treeRef.value?.setCheckedKeys(selectedIds.value)
+}
+const selectHandler = () => {
+	dialogVisible.value = true
+	selected.value = JSON.parse(JSON.stringify(props.modelValue || []))
+	const _info = dataInfo.value
+	if (!_info.loading && !_info.list.length) {
+		queryRoleData()
+	}
+}
+const queryRoleData = () => {
+	dataInfo.value.loading = true
+	role
+		.rolePageApi({ page: 1, pageSize: 99999 })
+		.then((res: any) => {
+			dataInfo.value = {
+				list: res.records || [],
+				loading: false
+			}
+		})
+		.catch(() => {
+			dataInfo.value.loading = false
+		})
+}
+const submit = () => {
+	// props.modelValue
+	const _selected = selected.value
+	if (!Array.isArray(_selected)) return ElMessage.warning('请选择数据')
+	// 最小限制
+	if (_selected.length < props.min) return ElMessage.warning(`选中的数据个数不能小于${props.min}条`)
+	// 最大限制
+	if (_selected.length > props.max) return ElMessage.warning(`选中的数据个数不能大于${props.max}条`)
+	dialogVisible.value = false
+	emit('update:modelValue', _selected)
+	// emit('change', _selected)
+}
+// const { prefixIcon, suffixIcon, prop, controlsPosition, t_placeholder, size, placeholder, max = 999999999999999, ...local_props } = ctx.attrs
+</script>
+<style scoped lang="scss">
+.le-select-role {
+	display: flex;
+	align-items: center;
+	gap: 6px;
+	flex-wrap: wrap;
+	min-height: 32px;
+}
+.sc-user-select {
+	display: flex;
+	.sc-user-select__left {
+		width: 200px;
+	}
+	.sc-user-select__tree {
+		width: 200px;
+		//height: 300px;
+		//border: none;
+		height: 343px;
+	}
+
+	.sc-user-select__toicon {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		margin: 0 10px;
+	}
+	.sc-user-select__toicon i {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		background: #ccc;
+		width: 20px;
+		height: 20px;
+		text-align: center;
+		line-height: 20px;
+		border-radius: 50%;
+		color: #fff;
+	}
+	.sc-user-select__select {
+		display: flex;
+		border: 1px solid var(--el-border-color-light);
+		background: var(--el-color-white);
+	}
+	.sc-user-select__selected {
+		height: 345px;
+		width: 200px;
+		border: 1px solid var(--el-border-color-light);
+		background: var(--el-color-white);
+		header {
+			height: 43px;
+			line-height: 43px;
+			border-bottom: 1px solid var(--el-border-color-light);
+			padding: 0 15px;
+			font-size: 12px;
+		}
+		ul {
+			height: 300px;
+			overflow: auto;
+		}
+		li {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 5px 5px 5px 15px;
+			height: 38px;
+			.name {
+				display: flex;
+				align-items: center;
+				.el-avatar {
+					background: var(--el-color-primary);
+					margin-right: 10px;
+				}
+				label {
+				}
+			}
+			.delete {
+				display: none;
+			}
+			&:hover {
+				background: var(--el-color-primary-light-9);
+				.delete {
+					display: inline-block;
+				}
+			}
+		}
+	}
+}
+</style>

+ 362 - 0
src/components/SelectUser.vue

@@ -0,0 +1,362 @@
+<template>
+	<div class="le-select-user">
+		<el-input :model-value="modelValue" style="display: none" />
+		<el-tooltip v-if="!disabled" content="选择人员" placement="left">
+			<el-button style="width: 32px" @click="selectHandler">
+				<svg-icon style="font-size: 18px" icon-class="flow-user-add" />
+			</el-button>
+		</el-tooltip>
+		<FlowNodeAvatar v-for="(item, i) of modelValue" :key="i" :name="item.name" />
+		<el-dialog v-model="dialogVisible" class="le-dialog" title="选择人员" :width="680" destroy-on-close append-to-body>
+			<div class="sc-user-select sc-user-select-role">
+				<div class="sc-user-select__left">
+					<div class="sc-user-select__search">
+						<el-input v-model="searchParams.username" prefix-icon="Search" placeholder="搜索成员" @keyup.enter="searchHandler">
+							<template #append>
+								<el-button icon="Search" @click="searchHandler"></el-button>
+							</template>
+						</el-input>
+					</div>
+
+					<div class="sc-user-select__select">
+						<div v-loading="groupData.loading" class="sc-user-select__tree">
+							<el-scrollbar>
+								<el-tree
+									ref="groupRef"
+									class="menu"
+									:data="groupData.list"
+									:node-key="groupProps.key"
+									:props="groupProps"
+									highlight-current
+									:expand-on-click-node="false"
+									:current-node-key="searchParams.departmentId"
+									@node-click="groupClick"
+								/>
+							</el-scrollbar>
+						</div>
+						<div v-loading="dataInfo.loading" class="sc-user-select__user">
+							<div class="sc-user-select__user__list">
+								<el-scrollbar ref="userScrollbar">
+									<el-tree
+										ref="treeRef"
+										class="menu"
+										:data="dataInfo.list"
+										:node-key="userProps.key"
+										:props="userProps"
+										:default-checked-keys="selectedIds"
+										show-checkbox
+										check-on-click-node
+										@check-change="checkChange"
+									></el-tree>
+								</el-scrollbar>
+							</div>
+							<footer>
+								<el-pagination
+									v-model:currentPage="searchParams.currentPage"
+									background
+									layout="prev,next"
+									small
+									:total="dataInfo.total"
+									:page-size="searchParams.pageSize"
+									@current-change="queryData()"
+								></el-pagination>
+							</footer>
+						</div>
+					</div>
+				</div>
+				<div class="sc-user-select__toicon">
+					<el-icon><ArrowRight /></el-icon>
+				</div>
+				<div class="sc-user-select__selected">
+					<header>已选 ({{ selected.length }})</header>
+					<ul style="padding: 0; margin: 0">
+						<el-scrollbar>
+							<li v-for="(item, index) in selected" :key="item.id">
+								<span class="name">
+									<label>{{ item.name || '-' }}</label>
+								</span>
+								<span class="delete">
+									<el-button type="danger" icon="Delete" circle size="small" @click="removeItem(index)" />
+								</span>
+							</li>
+						</el-scrollbar>
+					</ul>
+				</div>
+			</div>
+			<template #footer>
+				<el-button @click="dialogVisible = false">取消</el-button>
+				<el-button type="primary" @click="submit">确认</el-button>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+<script lang="tsx" setup>
+import { watch, unref, ref, computed, PropType } from 'vue'
+import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
+import user from '@/api/system/user.ts'
+import { ElMessage } from 'element-plus'
+import department from '@/api/system/department.ts'
+
+defineOptions({ name: 'LeSelectRole' })
+const emit = defineEmits(['update:modelValue', 'change', 'input', 'update:visible', 'getCurRow'])
+type Item = { name: string; id: string; [key: string]: any }
+const props = defineProps({
+	modelValue: {
+		type: Array as PropType<Item[]>,
+		default: () => []
+	},
+	min: { type: Number, default: 0 },
+	max: { type: Number, default: 999999 },
+	disabled: {
+		type: Boolean,
+		default: false
+	}
+	// params: {
+	// 	type: Object
+	// }
+})
+const groupRef = ref()
+const treeRef = ref()
+const userScrollbar = ref()
+const dialogVisible = ref(false)
+const dataInfo = ref({ list: [], loading: false, total: 0 })
+const groupProps = {
+	key: 'id',
+	label: 'name',
+	children: 'children'
+}
+const userProps = {
+	key: 'id',
+	label: 'realName'
+}
+
+// 命中的数据
+const selected = ref<Item[]>([])
+const selectedIds = computed(() => {
+	return selected.value.map(v => v.id)
+})
+watch(
+	() => props.modelValue,
+	vals => {
+		selected.value = JSON.parse(JSON.stringify(vals || []))
+	},
+	{
+		immediate: true,
+		deep: true
+	}
+)
+const checkChange = (data, checked) => {
+	if (checked) {
+		selected.value.push({
+			id: data.id,
+			name: data.realName
+		})
+	} else {
+		const idx = selected.value.findIndex(v => v.id === data.id)
+		if (idx >= 0) {
+			selected.value.splice(idx, 1)
+		}
+	}
+}
+const removeItem = (index: number) => {
+	selected.value.splice(index, 1)
+	treeRef.value?.setCheckedKeys(selectedIds.value)
+}
+const selectHandler = () => {
+	dialogVisible.value = true
+	selected.value = JSON.parse(JSON.stringify(props.modelValue || []))
+	const _group = groupData.value
+	if (!_group.loading && !_group.list.length) {
+		queryGroupData()
+	}
+	const _info = dataInfo.value
+	if (!_info.loading && !_info.list.length || searchParams.value.username || searchParams.value.departmentId) {
+		queryData(true)
+	}
+}
+const searchParams = ref({
+	username: undefined,
+	departmentId: undefined,
+	currentPage: 1,
+	pageSize: 20
+})
+const groupData = ref({ list: [], loading: false })
+const groupClick = (data: any) => {
+	Object.assign(searchParams.value, { departmentId: data.id, currentPage: 1 })
+	queryData()
+}
+const queryGroupData = () => {
+	groupData.value.loading = true
+	department
+		.departmentPageApi({ page: 1, pageSize: 9999 })
+		.then((res: any) => {
+			const list = res || []
+			list.unshift({ id: '', name: '所有' })
+			groupData.value = {
+				list,
+				loading: false
+			}
+		})
+		.catch(() => {
+			groupData.value.loading = false
+		})
+}
+const queryData = (init = false) => {
+	if (init) {
+		searchParams.value.username = undefined
+		searchParams.value.departmentId = undefined
+	}
+	dataInfo.value.loading = true
+	const _search = searchParams.value
+	const params = {
+		data: {
+			username: _search.username,
+			departmentId: _search.departmentId
+		},
+		page: _search.currentPage,
+		pageSize: _search.pageSize
+	}
+	user
+		.userPageApi(params)
+		.then((res: any) => {
+			dataInfo.value = {
+				list: res.records || [],
+				total: res.total,
+				loading: false
+			}
+			// 滚动到顶部
+			userScrollbar.value?.setScrollTop(0)
+		})
+		.catch(() => {
+			dataInfo.value.loading = false
+		})
+}
+const searchHandler = () => {
+	const _id = (searchParams.value.departmentId = '')
+	groupRef.value.setCurrentKey(_id)
+	searchParams.value.currentPage = 1
+	queryData()
+}
+const submit = () => {
+	// props.modelValue
+	const _selected = selected.value
+	if (!Array.isArray(_selected)) return ElMessage.warning('请选择数据')
+	// 最小限制
+	if (_selected.length < props.min) return ElMessage.warning(`选中的数据个数不能小于${props.min}条`)
+	// 最大限制
+	if (_selected.length > props.max) return ElMessage.warning(`选中的数据个数不能大于${props.max}条`)
+	dialogVisible.value = false
+	emit('update:modelValue', _selected)
+	emit('change', _selected)
+	// emit('input', _selected)
+}
+// const { prefixIcon, suffixIcon, prop, controlsPosition, t_placeholder, size, placeholder, max = 999999999999999, ...local_props } = ctx.attrs
+</script>
+<style scoped lang="scss">
+.le-select-user {
+	display: flex;
+	align-items: center;
+	gap: 6px;
+	flex-wrap: wrap;
+	min-height: 32px;
+}
+.sc-user-select {
+	display: flex;
+	.sc-user-select__left {
+		//width: 200px;
+		width: 400px;
+	}
+	.sc-user-select__search {
+		padding-bottom: 10px;
+	}
+	.sc-user-select__tree {
+		width: 200px;
+		height: 300px;
+		border-right: 1px solid var(--el-border-color-light);
+		//border: none;
+		//height: 343px;
+	}
+	.sc-user-select__user {
+		width: 200px;
+		height: 300px;
+		display: flex;
+		flex-direction: column;
+	}
+	.sc-user-select__user__list {
+		flex: 1;
+		overflow: auto;
+	}
+	.sc-user-select__user footer {
+		height: 36px;
+		padding-top: 5px;
+		border-top: 1px solid var(--el-border-color-light);
+	}
+
+	.sc-user-select__toicon {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		margin: 0 10px;
+	}
+	.sc-user-select__toicon i {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		background: #ccc;
+		width: 20px;
+		height: 20px;
+		text-align: center;
+		line-height: 20px;
+		border-radius: 50%;
+		color: #fff;
+	}
+	.sc-user-select__select {
+		display: flex;
+		border: 1px solid var(--el-border-color-light);
+		background: var(--el-color-white);
+	}
+	.sc-user-select__selected {
+		height: 345px;
+		width: 200px;
+		border: 1px solid var(--el-border-color-light);
+		background: var(--el-color-white);
+		header {
+			height: 43px;
+			line-height: 43px;
+			border-bottom: 1px solid var(--el-border-color-light);
+			padding: 0 15px;
+			font-size: 12px;
+		}
+		ul {
+			height: 300px;
+			overflow: auto;
+		}
+		li {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 5px 5px 5px 15px;
+			height: 38px;
+			.name {
+				display: flex;
+				align-items: center;
+				.el-avatar {
+					background: var(--el-color-primary);
+					margin-right: 10px;
+				}
+				label {
+				}
+			}
+			.delete {
+				display: none;
+			}
+			&:hover {
+				background: var(--el-color-primary-light-9);
+				.delete {
+					display: inline-block;
+				}
+			}
+		}
+	}
+}
+</style>

+ 38 - 0
src/components/packages/formEditor/components/FormTypes/SelectDepart/mobile.vue

@@ -0,0 +1,38 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectDepart',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+import { watch, ref } from 'vue'
+import department from '@/api/system/department'
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectDepart_mobile')
+const loadOptions = ref({ list: [], loading: false })
+const queryDepartOptions = () => {
+	// {title, content}[]
+	/*if (!dictCode) {
+		loadOptions.value = {
+			loading: false,
+			list: []
+		}
+		return
+	}*/
+	loadOptions.value.loading = true
+	department.departmentPageApi({ page: 1, pageSize: 99999 }).then(list => {
+		loadOptions.value = {
+			loading: false,
+			list
+		}
+	})
+}
+queryDepartOptions()
+</script>
+<template>
+	<el-cascader v-model="data.options.defaultValue" :loading="loadOptions.loading" :class="[ns.b()]" v-bind="params" :options="loadOptions.list" />
+</template>
+
+<style scoped></style>

+ 48 - 0
src/components/packages/formEditor/components/FormTypes/SelectDepart/pc.vue

@@ -0,0 +1,48 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectDepart',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+import { watch, ref } from 'vue'
+import department from '@/api/system/department'
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectDepart_pc')
+const loadOptions = ref({ list: [], loading: false })
+const queryDepartOptions = () => {
+	// {title, content}[]
+	/*if (!dictCode) {
+		loadOptions.value = {
+			loading: false,
+			list: []
+		}
+		return
+	}*/
+	loadOptions.value.loading = true
+	department.departmentPageApi({ page: 1, pageSize: 99999 }).then(list => {
+		loadOptions.value = {
+			loading: false,
+			list
+		}
+	})
+}
+queryDepartOptions()
+/*watch(
+	() => props.params?.dictCode,
+	dictCode => {
+		queryDepartOptions(dictCode)
+		// console.error(dictCode, 'SelectDepart params')
+	},
+	{
+		immediate: true
+	}
+)*/
+</script>
+<template>
+	<el-cascader v-model="data.options.defaultValue" :loading="loadOptions.loading" :class="[ns.b()]" v-bind="params" :options="loadOptions.list"/>
+</template>
+
+<style scoped></style>

+ 52 - 0
src/components/packages/formEditor/components/FormTypes/SelectDict/mobile.vue

@@ -0,0 +1,52 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectDict',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+import { watch, ref } from 'vue'
+import dict from '@/api/system/dict'
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectDict_mobile')
+// type Option = { title: string; content: string }
+const optionKey = { label: 'title', value: 'content' }
+const loadOptions = ref({ list: [], loading: false })
+const queryDictOptions = dictCode => {
+	// {title, content}[]
+	if (!dictCode) {
+		loadOptions.value = {
+			loading: false,
+			list: []
+		}
+		return
+	}
+	loadOptions.value.loading = true
+	dict.dictListSelectOptionApi(dictCode).then(list => {
+		loadOptions.value = {
+			loading: false,
+			list
+		}
+	})
+}
+
+watch(
+	() => props.params?.dictCode,
+	dictCode => {
+		queryDictOptions(dictCode)
+		// console.error(dictCode, 'SelectDict params')
+	},
+	{
+		immediate: true
+	}
+)
+</script>
+<template>
+	<el-select v-model="data.options.defaultValue" :loading="loadOptions.loading" :class="[ns.b()]" v-bind="params">
+		<el-option v-for="item in loadOptions.list" :key="item[optionKey.value]" :label="item[optionKey.label]" :value="item[optionKey.value]" />
+	</el-select>
+</template>
+
+<style scoped></style>

+ 52 - 0
src/components/packages/formEditor/components/FormTypes/SelectDict/pc.vue

@@ -0,0 +1,52 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectDict',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+import { watch, ref } from 'vue'
+import dict from '@/api/system/dict'
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectDict_pc')
+// type Option = { title: string; content: string }
+const optionKey = { label: 'title', value: 'content' }
+const loadOptions = ref({ list: [], loading: false })
+const queryDictOptions = dictCode => {
+	// {title, content}[]
+	if (!dictCode) {
+		loadOptions.value = {
+			loading: false,
+			list: []
+		}
+		return
+	}
+	loadOptions.value.loading = true
+	dict.dictListSelectOptionApi(dictCode).then(list => {
+		loadOptions.value = {
+			loading: false,
+			list
+		}
+	})
+}
+
+watch(
+	() => props.params?.dictCode,
+	dictCode => {
+		queryDictOptions(dictCode)
+		// console.error(dictCode, 'SelectDict params')
+	},
+	{
+		immediate: true
+	}
+)
+</script>
+<template>
+	<el-select v-model="data.options.defaultValue" :loading="loadOptions.loading" :class="[ns.b()]" v-bind="params">
+		<el-option v-for="item in loadOptions.list" :key="item[optionKey.value]" :label="item[optionKey.label]" :value="item[optionKey.value]" />
+	</el-select>
+</template>
+
+<style scoped></style>

+ 18 - 0
src/components/packages/formEditor/components/FormTypes/SelectRole/mobile.vue

@@ -0,0 +1,18 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectRole',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectRole_mobile')
+import SelectRole from '@/components/SelectRole.vue'
+</script>
+<template>
+	<SelectRole v-model="data.options.defaultValue" :class="[ns.b()]" v-bind="params" />
+</template>
+
+<style scoped></style>

+ 18 - 0
src/components/packages/formEditor/components/FormTypes/SelectRole/pc.vue

@@ -0,0 +1,18 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectRole',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectRole_pc')
+import SelectRole from '@/components/SelectRole.vue'
+</script>
+<template>
+	<SelectRole v-model="data.options.defaultValue" :class="[ns.b()]" v-bind="params"/>
+</template>
+
+<style scoped></style>

+ 18 - 0
src/components/packages/formEditor/components/FormTypes/SelectUser/mobile.vue

@@ -0,0 +1,18 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectUser',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectUser_mobile')
+import SelectUser from '@/components/SelectUser.vue'
+</script>
+<template>
+	<SelectUser v-model="data.options.defaultValue" :class="[ns.b()]" v-bind="params" />
+</template>
+
+<style scoped></style>

+ 25 - 0
src/components/packages/formEditor/components/FormTypes/SelectUser/pc.vue

@@ -0,0 +1,25 @@
+<script>
+import hooks from '@ER/hooks'
+export default {
+	name: 'ErSelectUser',
+	inheritAttrs: false,
+	customOptions: {}
+}
+</script>
+<script setup>
+// import { watch } from 'vue'
+const props = defineProps(['data', 'params'])
+const ns = hooks.useNamespace('FormTypesSelectUser_pc')
+import SelectUser from '@/components/SelectUser.vue'
+/*watch(
+	() => props.params,
+	v => {
+		console.error(v, 'SelectUser params')
+	}
+)*/
+</script>
+<template>
+	<SelectUser v-model="data.options.defaultValue" :class="[ns.b()]" v-bind="params"/>
+</template>
+
+<style scoped></style>

+ 1 - 1
src/components/packages/formEditor/components/Layout/DragGable.jsx

@@ -93,7 +93,7 @@ export default defineComponent({
 				findComponent(type, element) {
 					let info = componentMap[type + element]
 					if (!info) {
-						info = componentMap[type + element] = defineAsyncComponent(() => import(`../${type}/${_.startCase(element)}/${state.platform}.vue`))
+						info = componentMap[type + element] = defineAsyncComponent(() => import(`../${type}/${_.upperFirst(element)}/${state.platform}.vue`))
 					}
 					return info
 				}

+ 14 - 12
src/components/packages/formEditor/components/Panels/Config/components/PropsPanel.vue

@@ -437,7 +437,7 @@ onMounted(() => {
 				checkTypeBySelected(['subform'], 'defaultValue')
 					? target.list[0].length
 					: checkTypeBySelected(
-							['input', 'textarea', 'time', 'date', 'number', 'rate', 'color', 'switch', 'slider', 'button', 'divider', 'cascader', 'region'],
+							['input', 'textarea', 'time', 'date', 'number', 'rate', 'color', 'switch', 'slider', 'button', 'divider', 'cascader', 'selectDepart', 'region'],
 							'defaultValue'
 					  ) ||
 					  ([2, 3].indexOf(target.options.renderType) > -1 && target.type === 'select')
@@ -445,7 +445,7 @@ onMounted(() => {
 			:label="t('er.config.propsPanel.defaultContent')"
 			:layout-type="0"
 		>
-			<template v-if="checkTypeBySelected(['cascader', 'region'], 'defaultValue')">
+			<template v-if="checkTypeBySelected(['cascader', 'selectDepart', 'region'], 'defaultValue')">
 				<el-cascader
 					v-model="target.options.defaultValue"
 					:class="[utils.addTestId('configPanel-defaultValue', 'id')]"
@@ -496,7 +496,7 @@ onMounted(() => {
 			<template v-else-if="checkTypeBySelected(['subform'], 'defaultValue')">
 				<PanelsConfigComponentsSubformDefaultValue />
 			</template>
-			<template v-else-if="checkTypeBySelected(['select'], 'defaultValue') && [2, 3, 4].indexOf(target.options.renderType) > -1">
+			<template v-else-if="checkTypeBySelected(['select', 'selectDict'], 'defaultValue') && [2, 3, 4].indexOf(target.options.renderType) > -1">
 				<el-select v-model="target.options.defaultValue" :class="[utils.addTestId('configPanel-defaultValue', 'id')]" v-bind="typeProps">
 					<el-option v-for="item in typeProps.options" :key="item.value" :label="item.label" :value="item.value" />
 				</el-select>
@@ -522,12 +522,12 @@ onMounted(() => {
 		</PanelsConfigComponentsTypeComponent>
 		<!-- placeholder -->
 		<PanelsConfigComponentsTypeComponent
-			v-if="checkTypeBySelected(['input', 'textarea', 'select', 'cascader', 'time', 'date', 'html', 'region'], 'placeholder')"
+			v-if="checkTypeBySelected(['input', 'textarea', 'select', 'selectDict', 'selectDepart', 'cascader', 'time', 'date', 'html', 'region'], 'placeholder')"
 			:layout-type="0"
 			:label="t('er.config.propsPanel.placeholder')"
 		>
 			<el-input
-				v-if="checkTypeBySelected(['input', 'select', 'cascader', 'time', 'date', 'html', 'region'], 'placeholder')"
+				v-if="checkTypeBySelected(['input', 'select', 'selectDict', 'selectDepart', 'cascader', 'time', 'date', 'html', 'region'], 'placeholder')"
 				v-model="target.options.placeholder"
 				clearable
 				v-bind="utils.addTestId('configPanel:placeholder')"
@@ -540,11 +540,12 @@ onMounted(() => {
 				v-bind="utils.addTestId('configPanel:placeholder')"
 			/>
 		</PanelsConfigComponentsTypeComponent>
-		<!-- select dict类型(renderType === 4) 选择字典类型 -->
+		<!-- selectDict类型 选择字典类型 -->
 		<PanelsConfigComponentsTypeComponent
-			v-if="checkTypeBySelected(['select'], 'dictCode') && target.options.renderType === 4"
+			v-if="checkTypeBySelected(['selectDict'], 'dictCode')"
 			:layout-type="0"
 			:label="t('er.config.propsPanel.dictCode')"
+			prop="dictCode"
 		>
 			<el-select v-model="target.options.dictCode" style="width: 100%" v-bind="utils.addTestId('configPanel:dictCode')">
 				<el-option v-for="item in ER.dictParentList.value" :label="item.label" :value="item.value" />
@@ -710,7 +711,8 @@ onMounted(() => {
 		/>
 		<PanelsConfigComponentsCheckboxComponent
 			v-if="
-				(checkTypeBySelected(['input'], 'wordLimit') && target.options.renderType === 1) || checkTypeBySelected(['textarea', 'number'], 'wordLimit')
+				(checkTypeBySelected(['input'], 'wordLimit') && target.options.renderType === 1) ||
+				checkTypeBySelected(['textarea', 'number', 'selectUser', 'selectRole'], 'wordLimit')
 			"
 			:label="t('er.config.propsPanel.wordLimit')"
 			field="isShowWordLimit"
@@ -906,7 +908,7 @@ onMounted(() => {
 			>
 			</PanelsConfigComponentsCheckboxComponent>
 			<PanelsConfigComponentsCheckboxComponent
-				v-if="checkTypeBySelected(['select', 'cascader', 'uploadfile'], 'multiple')"
+				v-if="checkTypeBySelected(['select', 'selectDict', 'selectDepart', 'cascader', 'uploadfile'], 'multiple')"
 				:label="t('er.config.propsPanel.multiple')"
 				field="multiple"
 				v-bind="utils.addTestId('configPanel:multiple')"
@@ -914,7 +916,7 @@ onMounted(() => {
 			>
 			</PanelsConfigComponentsCheckboxComponent>
 			<PanelsConfigComponentsCheckboxComponent
-				v-if="checkTypeBySelected(['select', 'cascader', 'transfer', 'region'], 'filterable')"
+				v-if="checkTypeBySelected(['select', 'selectDict', 'selectDepart', 'cascader', 'transfer', 'region'], 'filterable')"
 				:label="t('er.config.propsPanel.filterable')"
 				field="filterable"
 				v-bind="utils.addTestId('configPanel:filterable')"
@@ -949,14 +951,14 @@ onMounted(() => {
 			>
 			</PanelsConfigComponentsCheckboxComponent>
 			<PanelsConfigComponentsCheckboxComponent
-				v-if="checkTypeBySelected(['cascader'], 'anyNode')"
+				v-if="checkTypeBySelected(['cascader', 'selectDepart'], 'anyNode')"
 				:label="t('er.config.propsPanel.anyNode')"
 				field="checkStrictly"
 				v-bind="utils.addTestId('configPanel:anyNode')"
 				@change="checkLogicData"
 			/>
 			<PanelsConfigComponentsCheckboxComponent
-				v-if="checkTypeBySelected(['input', 'select', 'time', 'date', 'cascader', 'region'], 'clearable')"
+				v-if="checkTypeBySelected(['input', 'select', 'selectDict', 'time', 'date', 'cascader', 'selectDepart', 'region'], 'clearable')"
 				:label="t('er.config.propsPanel.clearable')"
 				field="clearable"
 				v-bind="utils.addTestId('configPanel:clearable')"

+ 5 - 2
src/components/packages/formEditor/components/Panels/Config/components/TypeComponent.vue

@@ -38,7 +38,10 @@ const props = defineProps({
   layoutType: {
     type: Number,
     default: 1
-  }
+  },
+	prop: {
+		type: String
+	}
 })
 const fireEvent = (property, item) => {
   emit('listener', {
@@ -49,7 +52,7 @@ const fireEvent = (property, item) => {
 </script>
 <template>
   <div :class="ns.b()">
-    <el-form-item>
+    <el-form-item :prop="prop">
       <template v-if="label" v-slot:label>
         <div :class="ns.e('label')">
           <div>

+ 22 - 18
src/components/packages/formEditor/componentsConfig.js

@@ -112,48 +112,52 @@ export const fieldsConfig = [
 				}
 			},
 			{
-				type: 'select',
+				type: 'selectUser',
 				label: '员工',
 				icon: 'employee',
 				key: '',
 				id: '',
 				options: {
 					dataKey: '',
-					filterable: true,
-					multiple: false,
-					defaultValue: '',
+					// filterable: true,
+					// multiple: false,
+					defaultValue: undefined,
 					placeholder: '',
 					labelWidth: 100,
 					isShowLabel: true,
 					disabled: false,
-					clearable: true,
+					// clearable: true,
 					required: false,
-					renderType: 2
+					min: undefined,
+					max: 999999
+					// renderType: 2
 				}
 			},
 			{
-				type: 'select',
+				type: 'selectRole',
 				label: '角色',
-				icon: 'dropdown0',
+				icon: 'employee',
 				key: '',
 				id: '',
 				options: {
 					dataKey: '',
-					filterable: true,
-					multiple: false,
-					defaultValue: '',
+					// filterable: true,
+					// multiple: false,
+					defaultValue: undefined,
 					placeholder: '',
 					labelWidth: 100,
 					isShowLabel: true,
 					disabled: false,
-					clearable: true,
+					// clearable: true,
 					required: false,
-					renderType: 3
+					min: undefined,
+					max: 999999
+					// renderType: 2
 				}
 			},
 			{
 				// 渲染组件类型
-				type: 'select',
+				type: 'selectDict',
 				label: '字典',
 				// icon 名称
 				icon: 'dict',
@@ -173,13 +177,13 @@ export const fieldsConfig = [
 					disabled: false,
 					clearable: true,
 					// 额外渲染类型标记(label转义 render 配置等)
-					renderType: 4,
-					props: { value: 'id', label: 'name' },
+					// renderType: 4,
+					// props: { value: 'id', label: 'name' },
 					dictCode: '' // 字典编码
 				}
 			},
 			{
-				type: 'cascader',
+				type: 'selectDepart',
 				label: '部门',
 				icon: 'cascader',
 				key: '',
@@ -195,7 +199,7 @@ export const fieldsConfig = [
 					required: false,
 					disabled: false,
 					clearable: true,
-					renderType: 2,
+					// renderType: 2,
 					props: { value: 'id', label: 'name' }
 				}
 			}

+ 14 - 14
src/components/packages/formEditor/index.vue

@@ -21,13 +21,13 @@ export default {
 import { ls } from '@/utils/index'
 import user from '@/api/system/user'
 import role from '@/api/system/role'
-import department from '@/api/system/department'
+// import department from '@/api/system/department'
 import dict from '@/api/system/dict'
 
 const token = ls.get('token')
 const userOptList = ref([])
 const roleOptList = ref([])
-const departmentOptList = ref([])
+// const departmentOptList = ref([])
 const dictParentList = ref([])
 const emit = defineEmits(['listener'])
 const props = defineProps(
@@ -167,12 +167,12 @@ const getRoleList = async () => {
 }
 getRoleList()
 
-// 部门列表
+/*// 部门列表
 const getDepartmentList = async () => {
 	let data = await department.departmentPageApi({ page: 1, pageSize: 99999 })
 	departmentOptList.value = data || []
 }
-getDepartmentList()
+getDepartmentList()*/
 // 字典下拉配置
 const getDictParentList = async () => {
 	let data = await dict.dictListParentApi().then(async res => {
@@ -180,16 +180,16 @@ const getDictParentList = async () => {
 			return {
 				...v,
 				label: v.name,
-				value: v.code,
-				// 下拉数据
-				options: []
+				value: v.code
+				// // 下拉数据
+				// options: []
 			}
 		})
-		await Promise.all(options.map(v => dict.dictListSelectOptionApi(v.code))).then(codesOptions => {
+		/*await Promise.all(options.map(v => dict.dictListSelectOptionApi(v.code))).then(codesOptions => {
 			options.map((v, i) => {
 				v.options = (codesOptions[i] || []).map(({ title, content }) => ({ label: title, value: content }))
 			})
-		})
+		})*/
 		return options
 	})
 	dictParentList.value = data
@@ -249,20 +249,20 @@ const addFieldData = (node, isCopy = false) => {
 			if (node.options.renderType === 2) {
 				// 选择人员
 				node.options.options = userOptList.value
-			} else if (node.options.renderType === 3) {
+			} /*else if (node.options.renderType === 3) {
 				// 选择角色
 				node.options.options = roleOptList.value
 			} else if (node.options.renderType === 4) {
 				// dictListSelectOptionApi
 				// 字典父级 // 选择 todo...字典 dict
-				node.options.options = dictParentList.value // .xxxx todo...
-			}
-		} else if (node.type === 'cascader') {
+				node.options.options = dictParentList.value
+			}*/
+		} /* else if (node.type === 'cascader') {
 			// 部门类型
 			if (node.options.renderType === 2) {
 				node.options.options = departmentOptList.value
 			}
-		}
+		}*/
 		if (isCopy) {
 			state.data[node.id] = _.cloneDeep(state.data[node.options.dataKey])
 			node.options.dataKey = node.id

+ 4 - 0
src/components/packages/formEditor/locale/en.js

@@ -10,6 +10,10 @@ export default {
 			radio: 'Radio',
 			checkbox: 'Checkbox',
 			select: ['Select', 'Employee', 'Role', 'Dict'], //, 'Department'
+			selectUser: 'Employee',
+			selectRole: 'Role',
+			selectDict: 'Dict',
+			selectDepart: 'Department',
 			time: 'Time',
 			date: 'Date',
 			rate: 'Rate',

+ 4 - 0
src/components/packages/formEditor/locale/zh-cn.js

@@ -10,6 +10,10 @@ export default {
 			radio: '单选框',
 			checkbox: '复选框',
 			select: ['下拉框', '人员', '角色', '字典'], //, '部门'
+			selectUser: '员工',
+			selectDict: '字典',
+			selectRole: '角色',
+			selectDepart: '部门',
 			time: '时间',
 			date: '日期',
 			rate: '评分',

+ 25 - 6
src/components/packages/hooks/use-props/index.js

@@ -174,6 +174,7 @@ const getLogicStateByField = (field, fieldsLogicState) => {
 		readOnly
 	}
 }
+// 给选中类型 将 options 配置 铺设为  params 注入给 组件使用
 export const useProps = (state, data, isPc = true, isRoot = false, specialHandling, t, ExtraParams) => {
 	if (!t) {
 		t = useI18n().t
@@ -294,21 +295,38 @@ export const useProps = (state, data, isPc = true, isRoot = false, specialHandli
 			case 'checkbox':
 				result.options = _.get(state, `data[${options.dataKey}].list`, [])
 				break
+			case 'selectUser': // 人员
+			case 'selectRole': // 角色
+				result.min = options.min
+				result.max = options.max
+				break
+			case 'selectDict': // 字典
+				result.dictCode = options.dictCode
+				result.multiple = options.multiple
+				result.filterable = options.filterable
+				break
+			case 'selectDepart': // 部门
+				result.props = {
+					multiple: options.multiple,
+					checkStrictly: options.checkStrictly,
+					...options.props
+				}
+				break
 			case 'select':
 				// 当前选中的值,如果是人员就获取人员的api,部门就获取部门的api
 				if ([2, 3].indexOf(renderType) > -1) {
 					result.options = options?.options
-				} else if (renderType === 4) {
+				} /*else if (renderType === 4) {
 					// 字典类型
 					const _dictOption = (options?.options || []).find(v => v.code === options.dictCode)
 					// console.error(options?.options, 'options?.options  _dictOption', _dictOption)
-					/*if (_dictOption) {
+					/!*if (_dictOption) {
 						if (!_dictOption.options.length) {
 							_dictOption.options = dict.dictListSelectOptionApi(options.dictCode).then(({ title, content }) => ({ label: title, value: content }))
 						}
-					}*/
+					}*!/
 					result.options = _dictOption?.options
-				} else if (renderType === 1) {
+				}*/ else if (renderType === 1) {
 					result.options = _.get(state, `data[${options.dataKey}].list`, [])
 				}
 				result.multiple = options.multiple
@@ -397,12 +415,13 @@ export const useProps = (state, data, isPc = true, isRoot = false, specialHandli
 				}
 				break
 			case 'cascader':
-				if (renderType === 2) {
+				/*if (renderType === 2) {
 					// 部门类型
 					result.options = options?.options
 				} else if (renderType === 1) {
 					result.options = _.get(state, `data[${options.dataKey}].list`, [])
-				}
+				}*/
+				result.options = _.get(state, `data[${options.dataKey}].list`, [])
 				result.props = {
 					multiple: options.multiple,
 					checkStrictly: options.checkStrictly,

+ 6 - 0
src/components/packages/theme/formEditor/FormTypes/Cascader.scss

@@ -17,3 +17,9 @@
 @include b(FormTypesCascader_pc) {
   width: 100%;
 }
+@include b(FormTypesSelectDepart_pc) {
+  width: 100%;
+}
+@include b(FormTypesSelectDepart_mobile) {
+  width: 100%;
+}

+ 7 - 0
src/components/packages/theme/formEditor/FormTypes/Select.scss

@@ -12,3 +12,10 @@
 @include b(FormTypesSelect_pc) {
   width: 100%;
 }
+
+@include b(FormTypesSelectDict_pc) {
+  width: 100%;
+}
+@include b(FormTypesSelectDict_mobile) {
+  width: 100%;
+}

+ 2 - 1
src/components/packages/utils/field.js

@@ -1,7 +1,8 @@
 import _ from 'lodash-es'
 import { nanoid } from './nanoid'
 // 正则匹配type(itemType 类型)
-const fieldsRe = /^(input|textarea|number|radio|checkbox|select|time|date|rate|switch|slider|html|cascader|uploadfile|signature|region|subform|iframe)$/
+const fieldsRe =
+	/^(input|textarea|number|radio|checkbox|select|selectUser|selectRole|selectDict|selectDepart|time|date|rate|switch|slider|html|cascader|uploadfile|signature|region|subform|iframe)$/
 const deepTraversal = (node, fn) => {
 	fn(node)
 	const nodes = node.type === 'subform' ? node.list[0] : node.list || node.rows || node.columns || node.children || []

+ 1 - 1
src/router/index.ts

@@ -393,7 +393,7 @@ export const constantRoutes: AppRouteRecordRaw[] = [
 		name: 'dashboard',
 		meta: { title: 'dashboard', icon: 'icon-homepage', affix: true, parentName: 'mainLayout' }
 	},
-	// ...localDemoRoutes,
+	...localDemoRoutes,
 	// 用户
 	{
 		path: '/profile',

+ 81 - 1
src/views/components/index.vue

@@ -1,6 +1,36 @@
 <template>
 	<div class="flex-column-page-wrap pageWrap">
 		Home
+		<div class="common_title" style="background: #f00">todo 选择人员 选择角色</div>
+		<div class="content">
+			<el-form ref="formRef" :class="`le-form-config`" :rules="rules" :model="numberValidateForm">
+				<el-form-item
+					label="age"
+					prop="age"
+				>
+					<el-input
+						v-model.number="numberValidateForm.age"
+						type="text"
+						autocomplete="off"
+					/>
+				</el-form-item>
+				<el-form-item
+					label="roles"
+					prop="roles"
+				>
+					<SelectRole v-model="numberValidateForm.roles"></SelectRole>
+				</el-form-item>
+				<el-form-item
+					label="users"
+					prop="users"
+				>
+					<SelectUser v-model="numberValidateForm.users" :max="1"></SelectUser>
+				</el-form-item>
+			</el-form>
+			<el-form-item>
+				<el-button type="primary" @click="submitForm">Submit</el-button>
+			</el-form-item>
+		</div>
 		<div class="common_title">多语言 使用</div>
 		<div class="content">
 			<el-button size="small" @click="switchLang">
@@ -97,12 +127,14 @@
 </template>
 
 <script setup name="home" lang="tsx">
-import { ref } from 'vue'
+import { ref, reactive } from 'vue'
 import SearchGroup2Popover from './components/SearchGroup2Popover'
 import LeSelectDemo from './components/LeSelectDemo'
 import InputNumberDemo from './components/InputNumberDemo'
 import LeDraggableNestDemo from './components/LeDraggableNestDemo'
 import Watermark from './components/Watermark'
+import SelectRole from '@/components/SelectRole.vue'
+import SelectUser from '@/components/SelectUser.vue'
 import useStore from '@/store/index'
 import { useI18n } from 'vue-i18n'
 // import i18n from '@/lang'
@@ -138,6 +170,54 @@ const switchLang = () => {
 	// this.$i18n.locale = lang
 	// this.$i18n.fallbackLocale = lang
 }
+const formRef = ref()
+const numberValidateForm = reactive({
+	age: '',
+	roles: undefined,
+	// roles: [
+	// 	{id: "1456247408778260482",
+	// 	name: "技术总监"}
+	// ],
+	users: undefined
+})
+const rules = {
+	age: [
+		// { required: true, message: '请输入用户名', trigger: 'blur' },
+		// { min: 3, max: 15, message: '用户名长度应在3到15个字符之间', trigger: 'blur' }
+		{ required: true, message: '角色 is required'/*, trigger: ['blur', 'change']*/ }
+	],
+	roles: [
+		// { required: true, message: '请输入 roles', trigger: 'blur' },
+		{
+			required: true,
+			validator: (rule, value, callback) => {
+			if (!value || !value.length) callback(new Error('请选择 roles'))
+			},
+			// trigger: ['change', 'blur']
+		}
+	],
+	users: [
+		// { required: true, message: '请输入 users' },
+		{
+			required: true,
+			validator: (rule, value, callback) => {
+				console.error(value, ' xxxxxxxxxxxx')
+				if (!value || !value.length) callback(new Error('请选择 users'))
+			},
+			// trigger: ['change', 'blur']
+		}
+	]
+}
+const submitForm = () => {
+	if (!formRef.value) return
+	formRef.value.validate((valid) => {
+		if (valid) {
+			console.log('submit!', numberValidateForm)
+		} else {
+			console.log('error submit!')
+		}
+	})
+}
 </script>
 <style lang="scss" scoped>
 .pageWrap {

+ 7 - 1
vite.config.ts

@@ -114,7 +114,13 @@ export default defineConfig(({ mode /*command,*/ }: ConfigEnv): UserConfig => {
 			open: viteEnv.VITE_OPEN,
 			cors: true,
 			// Load proxy configuration from .env.development
-			proxy: {}
+			proxy: {
+				'/sssss': {
+					target: 'https://api.everright.site', // 目标接口的域名
+					changeOrigin: true, // 是否改变源地址
+					rewrite: (path) => path.replace(/^\/sssss/, ''), // 重写路径
+				},
+			},
 		},
 		plugins: [
 			examplePlugin(),