feat: dictionary & organization management pages
- 字典管理:左右分栏主从布局,20 个字典类型,el-scrollbar 滚动 - 组织管理:树形表格 + 上级组织 tree-select,级联删除下级 - ty-table 新增树形支持(row-key/tree-props/default-expand-all) - ty-form 新增 tree-select 控件(format/treeData/treeProps) - JsonSchemaProperty 扩展 tree-select 相关字段 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
35892486e8
commit
b5d95c95d8
8
components.d.ts
vendored
8
components.d.ts
vendored
@ -23,6 +23,7 @@ declare module 'vue' {
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
@ -40,7 +41,10 @@ declare module 'vue' {
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
Icon: typeof import('./src/components/icon/icon.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
@ -76,6 +80,7 @@ declare global {
|
||||
const ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
const ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
const ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
const ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
const ElForm: typeof import('element-plus/es')['ElForm']
|
||||
const ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
const ElInput: typeof import('element-plus/es')['ElInput']
|
||||
@ -93,7 +98,10 @@ declare global {
|
||||
const ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
const ElTable: typeof import('element-plus/es')['ElTable']
|
||||
const ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
const ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
const ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
const ElTag: typeof import('element-plus/es')['ElTag']
|
||||
const ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
const Icon: typeof import('./src/components/icon/icon.vue')['default']
|
||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||
const RouterView: typeof import('vue-router')['RouterView']
|
||||
|
||||
@ -22,6 +22,22 @@
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-tree-select
|
||||
v-else-if="prop.format === 'tree-select'"
|
||||
v-model="modelValue[key]"
|
||||
:data="prop.treeData ?? []"
|
||||
:node-key="prop.treeProps?.value ?? 'value'"
|
||||
:props="{
|
||||
label: prop.treeProps?.label ?? 'label',
|
||||
children: prop.treeProps?.children ?? 'children',
|
||||
}"
|
||||
check-strictly
|
||||
:placeholder="prop.placeholder ?? '请选择'"
|
||||
:clearable="!isRequired(key)"
|
||||
:disabled="isReadonly(key, prop)"
|
||||
:style="widgetStyle(prop)"
|
||||
/>
|
||||
|
||||
<el-switch v-else-if="prop.type === 'boolean'" v-model="modelValue[key]" :disabled="isReadonly(key, prop)" />
|
||||
|
||||
<el-input-number
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
:data="data"
|
||||
border
|
||||
height="100%"
|
||||
:row-key="rowKey"
|
||||
:tree-props="treeProps"
|
||||
:default-expand-all="defaultExpandAll"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column v-if="selection" type="selection" width="48" />
|
||||
@ -75,6 +78,12 @@ const props = withDefaults(
|
||||
pageSizes?: number[]
|
||||
/** 操作列宽度。 */
|
||||
actionsWidth?: number | string
|
||||
/** 树形数据的行唯一键。 */
|
||||
rowKey?: string
|
||||
/** 树形结构字段映射。 */
|
||||
treeProps?: Record<string, string>
|
||||
/** 是否默认展开所有树节点。 */
|
||||
defaultExpandAll?: boolean
|
||||
}>(),
|
||||
{
|
||||
loading: false,
|
||||
@ -84,6 +93,9 @@ const props = withDefaults(
|
||||
pageSize: 10,
|
||||
pageSizes: () => [10, 20, 50],
|
||||
actionsWidth: 200,
|
||||
rowKey: 'id',
|
||||
treeProps: () => ({ children: 'children' }),
|
||||
defaultExpandAll: false,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
402
src/pages/system/dict/index.vue
Normal file
402
src/pages/system/dict/index.vue
Normal file
@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div class="dict-page">
|
||||
<el-card class="dict-page__types" shadow="never">
|
||||
<div class="dict-page__type-head">
|
||||
<el-input v-model="typeKeyword" placeholder="搜索字典类型" clearable class="min-w-0 flex-1" />
|
||||
<el-button type="primary" @click="openTypeCreate">新增</el-button>
|
||||
</div>
|
||||
|
||||
<div class="dict-page__type-list">
|
||||
<el-scrollbar class="h-full">
|
||||
<div
|
||||
v-for="t in filteredTypes"
|
||||
:key="t.code"
|
||||
class="dict-page__type-item"
|
||||
:class="{ 'is-active': t.code === selectedType?.code }"
|
||||
@click="selectType(t)"
|
||||
>
|
||||
<div class="dict-page__type-item-row">
|
||||
<span class="dict-page__type-item-name">{{ t.name }}</span>
|
||||
<div class="dict-page__type-item-actions" @click.stop>
|
||||
<el-button link type="primary" size="small" @click="openTypeEdit(t)">编辑</el-button>
|
||||
<el-button link type="danger" size="small" @click="removeType(t)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dict-page__type-item-meta">
|
||||
<span class="dict-page__type-item-code">{{ t.code }}</span>
|
||||
<el-tag :type="t.status === 'enabled' ? 'success' : 'info'" size="small">
|
||||
{{ t.status === 'enabled' ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="filteredTypes.length === 0" description="无匹配的字典类型" :image-size="60" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div class="dict-page__items">
|
||||
<ty-crud
|
||||
v-if="selectedType"
|
||||
:key="selectedType.code"
|
||||
entity-name="字典项"
|
||||
:columns="itemColumns"
|
||||
:search-schema="itemSearchSchema"
|
||||
:form-schema="itemFormSchema"
|
||||
:list="listItems"
|
||||
:create="createItem"
|
||||
:update="updateItem"
|
||||
:remove="removeItem"
|
||||
id-key="id"
|
||||
name-key="label"
|
||||
/>
|
||||
<div v-else class="dict-page__items-empty">
|
||||
<el-empty description="请选择或新增字典类型" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { JsonSchema, PageQuery, PageResult, TableColumn } from '@/types/crud'
|
||||
import TyForm from '@/components/ty-form.vue'
|
||||
import { showDialog } from '@/utils/dialog'
|
||||
|
||||
const STATUS_MAP = {
|
||||
enabled: { label: '启用', type: 'success' },
|
||||
disabled: { label: '停用', type: 'info' },
|
||||
} as const
|
||||
|
||||
// ---- 占位数据(后续对接后端时替换为 http 调用)----
|
||||
interface DictTypeRow {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
status: 'enabled' | 'disabled'
|
||||
remark?: string
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
interface DictItemRow {
|
||||
id: string
|
||||
dictTypeCode: string
|
||||
label: string
|
||||
value: string
|
||||
sort: number
|
||||
status: 'enabled' | 'disabled'
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const day = 24 * 60 * 60 * 1000
|
||||
|
||||
const dictTypes: DictTypeRow[] = [
|
||||
{ id: 'dt-1', code: 'sys_status', name: '系统状态', status: 'enabled', remark: '通用启用/停用状态', createdAt: now - 30 * day },
|
||||
{ id: 'dt-2', code: 'sys_sex', name: '用户性别', status: 'enabled', remark: '性别选项', createdAt: now - 29 * day },
|
||||
{ id: 'dt-3', code: 'menu_type', name: '菜单类型', status: 'enabled', remark: '菜单/按钮/接口', createdAt: now - 28 * day },
|
||||
{ id: 'dt-4', code: 'oper_type', name: '操作类型', status: 'enabled', remark: '操作日志动作分类', createdAt: now - 27 * day },
|
||||
{ id: 'dt-5', code: 'sys_yes_no', name: '系统是否', status: 'enabled', remark: '是否判断', createdAt: now - 26 * day },
|
||||
{ id: 'dt-6', code: 'login_status', name: '登录状态', status: 'enabled', remark: '登录结果', createdAt: now - 25 * day },
|
||||
{ id: 'dt-7', code: 'audit_status', name: '操作结果', status: 'enabled', remark: '审计结果', createdAt: now - 24 * day },
|
||||
{ id: 'dt-8', code: 'notice_type', name: '通知类型', status: 'enabled', remark: '通知/公告', createdAt: now - 23 * day },
|
||||
{ id: 'dt-9', code: 'job_group', name: '任务分组', status: 'enabled', remark: '定时任务分组', createdAt: now - 22 * day },
|
||||
{ id: 'dt-10', code: 'job_status', name: '任务状态', status: 'enabled', remark: '任务运行状态', createdAt: now - 21 * day },
|
||||
{ id: 'dt-11', code: 'tenant_status', name: '租户状态', status: 'enabled', remark: '租户启用/停用', createdAt: now - 20 * day },
|
||||
{ id: 'dt-12', code: 'data_scope', name: '数据权限', status: 'enabled', remark: '角色数据权限档位', createdAt: now - 19 * day },
|
||||
{ id: 'dt-13', code: 'log_type', name: '日志类型', status: 'enabled', remark: '操作/登录日志', createdAt: now - 18 * day },
|
||||
{ id: 'dt-14', code: 'message_status', name: '消息状态', status: 'enabled', remark: '消息已读/未读', createdAt: now - 17 * day },
|
||||
{ id: 'dt-15', code: 'priority_level', name: '优先级', status: 'enabled', remark: '任务优先级', createdAt: now - 16 * day },
|
||||
{ id: 'dt-16', code: 'deploy_env', name: '部署环境', status: 'enabled', remark: '开发/测试/生产', createdAt: now - 15 * day },
|
||||
{ id: 'dt-17', code: 'language', name: '语言', status: 'enabled', remark: '系统语言', createdAt: now - 14 * day },
|
||||
{ id: 'dt-18', code: 'cache_type', name: '缓存类型', status: 'enabled', remark: '缓存介质', createdAt: now - 13 * day },
|
||||
{ id: 'dt-19', code: 'report_type', name: '报表类型', status: 'disabled', remark: '预留:报表分类', createdAt: now - 12 * day },
|
||||
{ id: 'dt-20', code: 'theme_mode', name: '主题模式', status: 'enabled', remark: '明亮/暗黑', createdAt: now - 11 * day },
|
||||
]
|
||||
|
||||
const dictItems: DictItemRow[] = [
|
||||
{ id: 'di-1', dictTypeCode: 'sys_status', label: '启用', value: 'enabled', sort: 1, status: 'enabled', createdAt: now - 10 * day },
|
||||
{ id: 'di-2', dictTypeCode: 'sys_status', label: '停用', value: 'disabled', sort: 2, status: 'enabled', createdAt: now - 10 * day },
|
||||
{ id: 'di-3', dictTypeCode: 'sys_sex', label: '男', value: 'male', sort: 1, status: 'enabled', createdAt: now - 9 * day },
|
||||
{ id: 'di-4', dictTypeCode: 'sys_sex', label: '女', value: 'female', sort: 2, status: 'enabled', createdAt: now - 9 * day },
|
||||
{ id: 'di-5', dictTypeCode: 'sys_sex', label: '未知', value: 'unknown', sort: 3, status: 'enabled', createdAt: now - 9 * day },
|
||||
{ id: 'di-6', dictTypeCode: 'menu_type', label: '目录', value: 'dir', sort: 1, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-7', dictTypeCode: 'menu_type', label: '菜单', value: 'menu', sort: 2, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-8', dictTypeCode: 'menu_type', label: '按钮', value: 'button', sort: 3, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-9', dictTypeCode: 'menu_type', label: '接口', value: 'api', sort: 4, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-10', dictTypeCode: 'oper_type', label: '新增', value: 'create', sort: 1, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-11', dictTypeCode: 'oper_type', label: '修改', value: 'update', sort: 2, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-12', dictTypeCode: 'oper_type', label: '删除', value: 'delete', sort: 3, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-13', dictTypeCode: 'oper_type', label: '查询', value: 'query', sort: 4, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-14', dictTypeCode: 'sys_yes_no', label: '是', value: 'yes', sort: 1, status: 'enabled', createdAt: now - 9 * day },
|
||||
{ id: 'di-15', dictTypeCode: 'sys_yes_no', label: '否', value: 'no', sort: 2, status: 'enabled', createdAt: now - 9 * day },
|
||||
{ id: 'di-16', dictTypeCode: 'login_status', label: '成功', value: 'success', sort: 1, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-17', dictTypeCode: 'login_status', label: '失败', value: 'fail', sort: 2, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-18', dictTypeCode: 'audit_status', label: '成功', value: 'success', sort: 1, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-19', dictTypeCode: 'audit_status', label: '失败', value: 'fail', sort: 2, status: 'enabled', createdAt: now - 8 * day },
|
||||
{ id: 'di-20', dictTypeCode: 'notice_type', label: '通知', value: 'notice', sort: 1, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-21', dictTypeCode: 'notice_type', label: '公告', value: 'announcement', sort: 2, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-22', dictTypeCode: 'job_group', label: '系统', value: 'system', sort: 1, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-23', dictTypeCode: 'job_group', label: '默认', value: 'default', sort: 2, status: 'enabled', createdAt: now - 7 * day },
|
||||
{ id: 'di-24', dictTypeCode: 'job_status', label: '运行', value: 'running', sort: 1, status: 'enabled', createdAt: now - 6 * day },
|
||||
{ id: 'di-25', dictTypeCode: 'job_status', label: '暂停', value: 'paused', sort: 2, status: 'enabled', createdAt: now - 6 * day },
|
||||
{ id: 'di-26', dictTypeCode: 'tenant_status', label: '启用', value: 'enabled', sort: 1, status: 'enabled', createdAt: now - 6 * day },
|
||||
{ id: 'di-27', dictTypeCode: 'tenant_status', label: '停用', value: 'disabled', sort: 2, status: 'enabled', createdAt: now - 6 * day },
|
||||
{ id: 'di-28', dictTypeCode: 'data_scope', label: '全部', value: 'all', sort: 1, status: 'enabled', createdAt: now - 5 * day },
|
||||
{ id: 'di-29', dictTypeCode: 'data_scope', label: '本部门', value: 'dept', sort: 2, status: 'enabled', createdAt: now - 5 * day },
|
||||
{ id: 'di-30', dictTypeCode: 'data_scope', label: '本部门及以下', value: 'dept_and_child', sort: 3, status: 'enabled', createdAt: now - 5 * day },
|
||||
{ id: 'di-31', dictTypeCode: 'data_scope', label: '本人', value: 'self', sort: 4, status: 'enabled', createdAt: now - 5 * day },
|
||||
{ id: 'di-32', dictTypeCode: 'data_scope', label: '自定义', value: 'custom', sort: 5, status: 'enabled', createdAt: now - 5 * day },
|
||||
{ id: 'di-33', dictTypeCode: 'log_type', label: '操作', value: 'operate', sort: 1, status: 'enabled', createdAt: now - 5 * day },
|
||||
{ id: 'di-34', dictTypeCode: 'log_type', label: '登录', value: 'login', sort: 2, status: 'enabled', createdAt: now - 5 * day },
|
||||
{ id: 'di-35', dictTypeCode: 'message_status', label: '未读', value: 'unread', sort: 1, status: 'enabled', createdAt: now - 4 * day },
|
||||
{ id: 'di-36', dictTypeCode: 'message_status', label: '已读', value: 'read', sort: 2, status: 'enabled', createdAt: now - 4 * day },
|
||||
{ id: 'di-37', dictTypeCode: 'priority_level', label: '低', value: 'low', sort: 1, status: 'enabled', createdAt: now - 4 * day },
|
||||
{ id: 'di-38', dictTypeCode: 'priority_level', label: '中', value: 'medium', sort: 2, status: 'enabled', createdAt: now - 4 * day },
|
||||
{ id: 'di-39', dictTypeCode: 'priority_level', label: '高', value: 'high', sort: 3, status: 'enabled', createdAt: now - 4 * day },
|
||||
{ id: 'di-40', dictTypeCode: 'deploy_env', label: '开发', value: 'dev', sort: 1, status: 'enabled', createdAt: now - 3 * day },
|
||||
{ id: 'di-41', dictTypeCode: 'deploy_env', label: '测试', value: 'test', sort: 2, status: 'enabled', createdAt: now - 3 * day },
|
||||
{ id: 'di-42', dictTypeCode: 'deploy_env', label: '生产', value: 'prod', sort: 3, status: 'enabled', createdAt: now - 3 * day },
|
||||
{ id: 'di-43', dictTypeCode: 'language', label: '简体中文', value: 'zh_CN', sort: 1, status: 'enabled', createdAt: now - 3 * day },
|
||||
{ id: 'di-44', dictTypeCode: 'language', label: 'English', value: 'en_US', sort: 2, status: 'enabled', createdAt: now - 3 * day },
|
||||
{ id: 'di-45', dictTypeCode: 'cache_type', label: 'Redis', value: 'redis', sort: 1, status: 'enabled', createdAt: now - 2 * day },
|
||||
{ id: 'di-46', dictTypeCode: 'cache_type', label: '本地', value: 'local', sort: 2, status: 'enabled', createdAt: now - 2 * day },
|
||||
{ id: 'di-47', dictTypeCode: 'theme_mode', label: '明亮', value: 'light', sort: 1, status: 'enabled', createdAt: now - 1 * day },
|
||||
{ id: 'di-48', dictTypeCode: 'theme_mode', label: '暗黑', value: 'dark', sort: 2, status: 'enabled', createdAt: now - 1 * day },
|
||||
]
|
||||
|
||||
const typeKeyword = ref('')
|
||||
const selectedType = ref<DictTypeRow | null>(dictTypes[0] ?? null)
|
||||
|
||||
const filteredTypes = computed(() => {
|
||||
const kw = typeKeyword.value.trim().toLowerCase()
|
||||
if (!kw) return dictTypes
|
||||
return dictTypes.filter((t) => t.name.toLowerCase().includes(kw) || t.code.toLowerCase().includes(kw))
|
||||
})
|
||||
|
||||
function selectType(t: DictTypeRow): void {
|
||||
selectedType.value = t
|
||||
}
|
||||
|
||||
// ---- 字典类型表单 ----
|
||||
const typeFormSchema: JsonSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
code: { type: 'string', title: '字典编码', placeholder: '如 sys_status', minLength: 2 },
|
||||
name: { type: 'string', title: '字典名称', placeholder: '如 系统状态' },
|
||||
status: {
|
||||
type: 'string',
|
||||
title: '状态',
|
||||
enum: ['enabled', 'disabled'],
|
||||
enumNames: ['启用', '停用'],
|
||||
default: 'enabled',
|
||||
},
|
||||
remark: { type: 'string', title: '备注', format: 'textarea', placeholder: '请输入备注' },
|
||||
},
|
||||
required: ['code', 'name'],
|
||||
}
|
||||
|
||||
function buildTypeModel(t?: DictTypeRow): Record<string, any> {
|
||||
return { code: t?.code ?? '', name: t?.name ?? '', status: t?.status ?? 'enabled', remark: t?.remark ?? '' }
|
||||
}
|
||||
|
||||
let typeSeq = 0
|
||||
const nextTypeId = () => `dt-${Date.now()}-${++typeSeq}`
|
||||
|
||||
async function openTypeCreate(): Promise<void> {
|
||||
const data = await showDialog({
|
||||
component: TyForm,
|
||||
props: { schema: typeFormSchema, modelValue: buildTypeModel(), mode: 'create' },
|
||||
title: '新增字典类型',
|
||||
width: '480px',
|
||||
})
|
||||
if (data == null) return
|
||||
dictTypes.push({
|
||||
id: nextTypeId(),
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
status: data.status,
|
||||
remark: data.remark,
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
ElMessage.success('新增成功')
|
||||
selectedType.value = dictTypes[dictTypes.length - 1]
|
||||
}
|
||||
|
||||
async function openTypeEdit(t: DictTypeRow): Promise<void> {
|
||||
const data = await showDialog({
|
||||
component: TyForm,
|
||||
props: { schema: typeFormSchema, modelValue: buildTypeModel(t), mode: 'edit', readonlyKeys: ['code'] },
|
||||
title: '编辑字典类型',
|
||||
width: '480px',
|
||||
})
|
||||
if (data == null) return
|
||||
Object.assign(t, data)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
|
||||
async function removeType(t: DictTypeRow): Promise<void> {
|
||||
await ElMessageBox.confirm(`确认删除字典类型「${t.name}」及其全部字典项?`, '提示', { type: 'warning' })
|
||||
const ti = dictTypes.findIndex((x) => x.id === t.id)
|
||||
if (ti >= 0) dictTypes.splice(ti, 1)
|
||||
for (let i = dictItems.length - 1; i >= 0; i--) {
|
||||
if (dictItems[i].dictTypeCode === t.code) dictItems.splice(i, 1)
|
||||
}
|
||||
if (selectedType.value?.id === t.id) selectedType.value = dictTypes[0] ?? null
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
|
||||
// ---- 字典项 ----
|
||||
const itemColumns: TableColumn[] = [
|
||||
{ prop: 'label', label: '标签', minWidth: 120 },
|
||||
{ prop: 'value', label: '值', minWidth: 120 },
|
||||
{ prop: 'sort', label: '排序', width: 80, align: 'center' },
|
||||
{ prop: 'status', label: '状态', width: 90, align: 'center', type: 'tag', tagMap: STATUS_MAP },
|
||||
{ prop: 'createdAt', label: '创建时间', width: 160, type: 'datetime' },
|
||||
]
|
||||
|
||||
const itemSearchSchema: JsonSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: { type: 'string', title: '标签', placeholder: '请输入标签' },
|
||||
value: { type: 'string', title: '值', placeholder: '请输入值' },
|
||||
},
|
||||
}
|
||||
|
||||
const itemFormSchema: JsonSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: { type: 'string', title: '标签', placeholder: '如 启用' },
|
||||
value: { type: 'string', title: '值', placeholder: '如 enabled' },
|
||||
sort: { type: 'integer', title: '排序', minimum: 0, default: 0 },
|
||||
status: {
|
||||
type: 'string',
|
||||
title: '状态',
|
||||
enum: ['enabled', 'disabled'],
|
||||
enumNames: ['启用', '停用'],
|
||||
default: 'enabled',
|
||||
},
|
||||
},
|
||||
required: ['label', 'value'],
|
||||
}
|
||||
|
||||
let itemSeq = 0
|
||||
const nextItemId = () => `di-${Date.now()}-${++itemSeq}`
|
||||
|
||||
function delay<T>(fn: () => T, ms = 300): Promise<T> {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(fn()), ms))
|
||||
}
|
||||
|
||||
const listItems = (query: PageQuery): Promise<PageResult<Record<string, any>>> =>
|
||||
delay(() => {
|
||||
const code = selectedType.value?.code ?? ''
|
||||
const { page, pageSize, label, value } = query
|
||||
const filtered = dictItems.filter((i) => {
|
||||
if (i.dictTypeCode !== code) return false
|
||||
if (label && !i.label.includes(String(label))) return false
|
||||
if (value && !i.value.includes(String(value))) return false
|
||||
return true
|
||||
})
|
||||
const start = (page - 1) * pageSize
|
||||
return {
|
||||
list: filtered.slice(start, start + pageSize) as unknown as Record<string, any>[],
|
||||
total: filtered.length,
|
||||
page,
|
||||
pageSize,
|
||||
}
|
||||
})
|
||||
|
||||
const createItem = (data: Record<string, any>): Promise<unknown> =>
|
||||
delay(() => {
|
||||
dictItems.push({
|
||||
id: nextItemId(),
|
||||
dictTypeCode: selectedType.value?.code ?? '',
|
||||
label: data.label,
|
||||
value: data.value,
|
||||
sort: data.sort ?? 0,
|
||||
status: data.status,
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
return null
|
||||
})
|
||||
|
||||
const updateItem = (id: string, data: Record<string, any>): Promise<unknown> =>
|
||||
delay(() => {
|
||||
const it = dictItems.find((x) => x.id === id)
|
||||
if (it) Object.assign(it, data)
|
||||
return null
|
||||
})
|
||||
|
||||
const removeItem = (id: string): Promise<unknown> =>
|
||||
delay(() => {
|
||||
const i = dictItems.findIndex((x) => x.id === id)
|
||||
if (i >= 0) dictItems.splice(i, 1)
|
||||
return null
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dict-page {
|
||||
@apply flex h-full gap-md;
|
||||
}
|
||||
|
||||
.dict-page__types {
|
||||
@apply w-[280px] flex-shrink-0;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
}
|
||||
|
||||
.dict-page__type-head {
|
||||
@apply mb-sm flex flex-shrink-0 gap-sm;
|
||||
}
|
||||
|
||||
.dict-page__type-list {
|
||||
@apply min-h-0 flex-1;
|
||||
}
|
||||
|
||||
.dict-page__type-item {
|
||||
@apply mb-xs cursor-pointer rounded-sm border border-transparent px-sm py-xs transition-colors;
|
||||
|
||||
&:hover {
|
||||
@apply bg-bg-page;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
@apply border-primary bg-primary-light-7;
|
||||
}
|
||||
}
|
||||
|
||||
.dict-page__type-item-row {
|
||||
@apply flex items-center justify-between;
|
||||
}
|
||||
|
||||
.dict-page__type-item-name {
|
||||
@apply truncate font-medium text-text-primary;
|
||||
}
|
||||
|
||||
.dict-page__type-item-actions {
|
||||
@apply hidden flex-shrink-0;
|
||||
}
|
||||
|
||||
.dict-page__type-item:hover .dict-page__type-item-actions {
|
||||
@apply flex items-center gap-xs;
|
||||
}
|
||||
|
||||
.dict-page__type-item-meta {
|
||||
@apply mt-xs flex items-center justify-between;
|
||||
}
|
||||
|
||||
.dict-page__type-item-code {
|
||||
@apply truncate text-xs text-text-secondary;
|
||||
}
|
||||
|
||||
.dict-page__items {
|
||||
@apply min-w-0 flex-1;
|
||||
}
|
||||
|
||||
.dict-page__items-empty {
|
||||
@apply flex h-full items-center justify-center;
|
||||
}
|
||||
</style>
|
||||
388
src/pages/system/org/index.vue
Normal file
388
src/pages/system/org/index.vue
Normal file
@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<div class="org-page">
|
||||
<el-card class="org-page__search" shadow="never">
|
||||
<ty-search-form :schema="searchSchema" :model-value="searchQuery" @search="handleSearch" @reset="handleReset" />
|
||||
</el-card>
|
||||
|
||||
<el-card class="org-page__table" shadow="never">
|
||||
<div class="org-page__toolbar">
|
||||
<div class="org-page__toolbar-left">
|
||||
<el-button type="primary" @click="openCreate">新增组织</el-button>
|
||||
</div>
|
||||
<div class="org-page__toolbar-right">
|
||||
<el-button type="danger" :disabled="selectedRows.length === 0" @click="batchDelete(selectedRows)">
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="org-page__table-body">
|
||||
<ty-table
|
||||
:columns="columns"
|
||||
:data="displayTree"
|
||||
selection
|
||||
row-key="code"
|
||||
:tree-props="{ children: 'children' }"
|
||||
default-expand-all
|
||||
:actions-width="180"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" @click="openCreateChild(row)">新增子</el-button>
|
||||
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</ty-table>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { JsonSchema, TableColumn } from '@/types/crud'
|
||||
import TyForm from '@/components/ty-form.vue'
|
||||
import { showDialog } from '@/utils/dialog'
|
||||
|
||||
const STATUS_MAP = {
|
||||
enabled: { label: '启用', type: 'success' },
|
||||
disabled: { label: '停用', type: 'info' },
|
||||
} as const
|
||||
|
||||
// ---- 占位数据(后续对接后端时替换为 http 调用)----
|
||||
interface OrgRow {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
parentCode: string | null
|
||||
leader?: string
|
||||
sort: number
|
||||
status: 'enabled' | 'disabled'
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
interface OrgNode extends OrgRow {
|
||||
children: OrgNode[]
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const day = 24 * 60 * 60 * 1000
|
||||
|
||||
const orgs = ref<OrgRow[]>([
|
||||
{ id: 'o-1', code: 'touyun', name: '投云总部', parentCode: null, leader: '王总', sort: 1, status: 'enabled', createdAt: now - 365 * day },
|
||||
{ id: 'o-2', code: 'rd', name: '研发部', parentCode: 'touyun', leader: '张经理', sort: 1, status: 'enabled', createdAt: now - 300 * day },
|
||||
{ id: 'o-3', code: 'fe', name: '前端组', parentCode: 'rd', leader: '李组长', sort: 1, status: 'enabled', createdAt: now - 200 * day },
|
||||
{ id: 'o-4', code: 'be', name: '后端组', parentCode: 'rd', leader: '赵组长', sort: 2, status: 'enabled', createdAt: now - 190 * day },
|
||||
{ id: 'o-5', code: 'qa', name: '测试组', parentCode: 'rd', leader: '孙组长', sort: 3, status: 'enabled', createdAt: now - 180 * day },
|
||||
{ id: 'o-6', code: 'ops', name: '运维部', parentCode: 'touyun', leader: '周经理', sort: 2, status: 'enabled', createdAt: now - 280 * day },
|
||||
{ id: 'o-7', code: 'dba', name: '数据库组', parentCode: 'ops', leader: '吴组长', sort: 1, status: 'enabled', createdAt: now - 150 * day },
|
||||
{ id: 'o-8', code: 'net', name: '网络组', parentCode: 'ops', leader: '郑组长', sort: 2, status: 'enabled', createdAt: now - 140 * day },
|
||||
{ id: 'o-9', code: 'sales', name: '销售部', parentCode: 'touyun', leader: '钱经理', sort: 3, status: 'enabled', createdAt: now - 260 * day },
|
||||
{ id: 'o-10', code: 'east', name: '华东大区', parentCode: 'sales', leader: '陈总监', sort: 1, status: 'enabled', createdAt: now - 120 * day },
|
||||
{ id: 'o-11', code: 'south', name: '华南大区', parentCode: 'sales', leader: '林总监', sort: 2, status: 'enabled', createdAt: now - 110 * day },
|
||||
{ id: 'o-12', code: 'hr', name: '人事部', parentCode: 'touyun', leader: '何经理', sort: 4, status: 'enabled', createdAt: now - 200 * day },
|
||||
{ id: 'o-13', code: 'fin', name: '财务部', parentCode: 'touyun', leader: '许经理', sort: 5, status: 'enabled', createdAt: now - 180 * day },
|
||||
{ id: 'o-14', code: 'pm', name: '产品部', parentCode: 'touyun', leader: '邓经理', sort: 6, status: 'disabled', createdAt: now - 90 * day },
|
||||
{ id: 'o-15', code: 'marketing', name: '市场部', parentCode: 'touyun', leader: '冯经理', sort: 7, status: 'enabled', createdAt: now - 100 * day },
|
||||
{ id: 'o-16', code: 'brand', name: '品牌组', parentCode: 'marketing', leader: '蒋组长', sort: 1, status: 'enabled', createdAt: now - 60 * day },
|
||||
])
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{ prop: 'name', label: '组织名称', minWidth: 220 },
|
||||
{ prop: 'code', label: '组织编码', minWidth: 120 },
|
||||
{ prop: 'leader', label: '负责人', width: 100 },
|
||||
{ prop: 'sort', label: '排序', width: 70, align: 'center' },
|
||||
{ prop: 'status', label: '状态', width: 80, align: 'center', type: 'tag', tagMap: STATUS_MAP },
|
||||
{ prop: 'createdAt', label: '创建时间', width: 160, type: 'datetime' },
|
||||
]
|
||||
|
||||
const searchSchema: JsonSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', title: '组织名称', placeholder: '请输入名称' },
|
||||
code: { type: 'string', title: '组织编码', placeholder: '请输入编码' },
|
||||
status: {
|
||||
type: 'string',
|
||||
title: '状态',
|
||||
enum: ['enabled', 'disabled'],
|
||||
enumNames: ['启用', '停用'],
|
||||
placeholder: '全部',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ---- 树构建 / 过滤 ----
|
||||
function buildTree(rows: OrgRow[]): OrgNode[] {
|
||||
const map = new Map<string, OrgNode>()
|
||||
for (const r of rows) map.set(r.code, { ...r, children: [] })
|
||||
const roots: OrgNode[] = []
|
||||
for (const node of map.values()) {
|
||||
const parent = node.parentCode ? map.get(node.parentCode) : undefined
|
||||
if (parent) parent.children.push(node)
|
||||
else roots.push(node)
|
||||
}
|
||||
const sortRec = (nodes: OrgNode[]): void => {
|
||||
nodes.sort((a, b) => a.sort - b.sort || a.code.localeCompare(b.code))
|
||||
for (const n of nodes) sortRec(n.children)
|
||||
}
|
||||
sortRec(roots)
|
||||
return roots
|
||||
}
|
||||
|
||||
/** 过滤:保留匹配节点 + 其祖先 + 其整棵子树。 */
|
||||
function filterTree(rows: OrgRow[], name: string, code: string, status: string): OrgRow[] {
|
||||
if (!name && !code && !status) return rows
|
||||
const byCode = new Map(rows.map((r) => [r.code, r]))
|
||||
const matches = rows.filter(
|
||||
(r) => (!name || r.name.includes(name)) && (!code || r.code.includes(code)) && (!status || r.status === status),
|
||||
)
|
||||
const kept = new Set<string>()
|
||||
for (const m of matches) {
|
||||
// 祖先链
|
||||
let cur: OrgRow | undefined = m
|
||||
while (cur) {
|
||||
kept.add(cur.code)
|
||||
cur = cur.parentCode ? byCode.get(cur.parentCode) : undefined
|
||||
}
|
||||
// 子树
|
||||
const stack = [m.code]
|
||||
while (stack.length) {
|
||||
const c = stack.pop()!
|
||||
for (const r of rows) {
|
||||
if (r.parentCode === c) {
|
||||
kept.add(r.code)
|
||||
stack.push(r.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows.filter((r) => kept.has(r.code))
|
||||
}
|
||||
|
||||
const searchQuery = reactive({ name: '', code: '', status: '' })
|
||||
const appliedQuery = reactive({ name: '', code: '', status: '' })
|
||||
|
||||
const displayTree = computed<OrgNode[]>(() =>
|
||||
buildTree(filterTree(orgs.value, appliedQuery.name.trim(), appliedQuery.code.trim(), appliedQuery.status)),
|
||||
)
|
||||
|
||||
function handleSearch(): void {
|
||||
appliedQuery.name = searchQuery.name
|
||||
appliedQuery.code = searchQuery.code
|
||||
appliedQuery.status = searchQuery.status
|
||||
}
|
||||
|
||||
function handleReset(): void {
|
||||
appliedQuery.name = ''
|
||||
appliedQuery.code = ''
|
||||
appliedQuery.status = ''
|
||||
}
|
||||
|
||||
// ---- 表单 ----
|
||||
function treeSelectData(excludeCode?: string): OrgNode[] {
|
||||
const tree = buildTree(orgs.value)
|
||||
if (!excludeCode) return tree
|
||||
const prune = (nodes: OrgNode[]): OrgNode[] =>
|
||||
nodes.filter((n) => n.code !== excludeCode).map((n) => ({ ...n, children: prune(n.children) }))
|
||||
return prune(tree)
|
||||
}
|
||||
|
||||
function orgFormSchema(excludeCode?: string): JsonSchema {
|
||||
return {
|
||||
type: 'object',
|
||||
properties: {
|
||||
parentCode: {
|
||||
type: 'string',
|
||||
title: '上级组织',
|
||||
format: 'tree-select',
|
||||
treeData: treeSelectData(excludeCode),
|
||||
treeProps: { label: 'name', value: 'code', children: 'children' },
|
||||
placeholder: '不选则为顶级组织',
|
||||
},
|
||||
code: { type: 'string', title: '组织编码', placeholder: '如 rd', minLength: 2 },
|
||||
name: { type: 'string', title: '组织名称', placeholder: '如 研发部' },
|
||||
leader: { type: 'string', title: '负责人', placeholder: '请输入负责人' },
|
||||
sort: { type: 'integer', title: '排序', minimum: 0, default: 0 },
|
||||
status: {
|
||||
type: 'string',
|
||||
title: '状态',
|
||||
enum: ['enabled', 'disabled'],
|
||||
enumNames: ['启用', '停用'],
|
||||
default: 'enabled',
|
||||
},
|
||||
},
|
||||
required: ['code', 'name'],
|
||||
}
|
||||
}
|
||||
|
||||
function buildOrgModel(o?: OrgRow): Record<string, any> {
|
||||
return {
|
||||
parentCode: o?.parentCode ?? '',
|
||||
code: o?.code ?? '',
|
||||
name: o?.name ?? '',
|
||||
leader: o?.leader ?? '',
|
||||
sort: o?.sort ?? 0,
|
||||
status: o?.status ?? 'enabled',
|
||||
}
|
||||
}
|
||||
|
||||
let seq = 0
|
||||
const nextId = () => `o-${Date.now()}-${++seq}`
|
||||
|
||||
function delay<T>(fn: () => T, ms = 300): Promise<T> {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(fn()), ms))
|
||||
}
|
||||
|
||||
function collectDescendants(code: string): string[] {
|
||||
const result: string[] = []
|
||||
const stack = [code]
|
||||
while (stack.length) {
|
||||
const c = stack.pop()!
|
||||
for (const o of orgs.value) {
|
||||
if (o.parentCode === c) {
|
||||
result.push(o.code)
|
||||
stack.push(o.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function openCreate(): Promise<void> {
|
||||
const data = await showDialog({
|
||||
component: TyForm,
|
||||
props: { schema: orgFormSchema(), modelValue: buildOrgModel(), mode: 'create' },
|
||||
title: '新增组织',
|
||||
width: '480px',
|
||||
})
|
||||
if (data == null) return
|
||||
await delay(() => {
|
||||
orgs.value.push({
|
||||
id: nextId(),
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
parentCode: data.parentCode || null,
|
||||
leader: data.leader,
|
||||
sort: data.sort ?? 0,
|
||||
status: data.status,
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
return null
|
||||
})
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
|
||||
async function openCreateChild(parent: Record<string, any>): Promise<void> {
|
||||
const model = buildOrgModel()
|
||||
model.parentCode = parent.code
|
||||
const data = await showDialog({
|
||||
component: TyForm,
|
||||
props: { schema: orgFormSchema(), modelValue: model, mode: 'create' },
|
||||
title: `新增子组织(${parent.name})`,
|
||||
width: '480px',
|
||||
})
|
||||
if (data == null) return
|
||||
await delay(() => {
|
||||
orgs.value.push({
|
||||
id: nextId(),
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
parentCode: data.parentCode || parent.code,
|
||||
leader: data.leader,
|
||||
sort: data.sort ?? 0,
|
||||
status: data.status,
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
return null
|
||||
})
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
|
||||
async function openEdit(row: Record<string, any>): Promise<void> {
|
||||
const o = row as OrgRow
|
||||
const data = await showDialog({
|
||||
component: TyForm,
|
||||
props: {
|
||||
schema: orgFormSchema(o.code),
|
||||
modelValue: buildOrgModel(o),
|
||||
mode: 'edit',
|
||||
readonlyKeys: ['code'],
|
||||
},
|
||||
title: '编辑组织',
|
||||
width: '480px',
|
||||
})
|
||||
if (data == null) return
|
||||
await delay(() => {
|
||||
const target = orgs.value.find((x) => x.id === o.id)
|
||||
if (target) {
|
||||
Object.assign(target, {
|
||||
parentCode: data.parentCode || null,
|
||||
name: data.name,
|
||||
leader: data.leader,
|
||||
sort: data.sort ?? 0,
|
||||
status: data.status,
|
||||
})
|
||||
}
|
||||
return null
|
||||
})
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
|
||||
async function handleDelete(row: Record<string, any>): Promise<void> {
|
||||
const o = row as OrgRow
|
||||
await ElMessageBox.confirm(`确认删除组织「${o.name}」及其所有下级组织?`, '提示', { type: 'warning' })
|
||||
const codes = new Set([o.code, ...collectDescendants(o.code)])
|
||||
orgs.value = orgs.value.filter((x) => !codes.has(x.code))
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
|
||||
const selectedRows = ref<Record<string, any>[]>([])
|
||||
|
||||
function onSelectionChange(rows: Record<string, any>[]): void {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
async function batchDelete(rows: Record<string, any>[]): Promise<void> {
|
||||
await ElMessageBox.confirm(`确认删除选中的 ${rows.length} 个组织及其下级?`, '提示', { type: 'warning' })
|
||||
const codes = new Set<string>()
|
||||
for (const r of rows) {
|
||||
codes.add(String(r.code))
|
||||
for (const c of collectDescendants(String(r.code))) codes.add(c)
|
||||
}
|
||||
orgs.value = orgs.value.filter((o) => !codes.has(o.code))
|
||||
selectedRows.value = []
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.org-page {
|
||||
@apply flex h-full flex-col;
|
||||
}
|
||||
|
||||
.org-page__search {
|
||||
@apply mb-md flex-shrink-0;
|
||||
}
|
||||
|
||||
.org-page__table {
|
||||
@apply flex min-h-0 flex-1 flex-col;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
@apply flex min-h-0 flex-1 flex-col;
|
||||
}
|
||||
}
|
||||
|
||||
.org-page__toolbar {
|
||||
@apply mb-md flex flex-shrink-0 items-center justify-between;
|
||||
}
|
||||
|
||||
.org-page__toolbar-left,
|
||||
.org-page__toolbar-right {
|
||||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.org-page__table-body {
|
||||
@apply min-h-0 flex-1;
|
||||
}
|
||||
</style>
|
||||
@ -9,7 +9,7 @@ export interface JsonSchemaProperty {
|
||||
title?: string
|
||||
description?: string
|
||||
/** 格式化:password/email/textarea 等。 */
|
||||
format?: 'password' | 'email' | 'textarea' | 'date' | 'datetime'
|
||||
format?: 'password' | 'email' | 'textarea' | 'date' | 'datetime' | 'tree-select'
|
||||
/** 枚举值(渲染为下拉)。 */
|
||||
enum?: (string | number | boolean)[]
|
||||
/** 枚举显示名(与 enum 一一对应)。 */
|
||||
@ -26,6 +26,10 @@ export interface JsonSchemaProperty {
|
||||
placeholder?: string
|
||||
/** 控件宽度(如 '160px');缺省时搜索行内默认 200px、弹窗表单默认 100%。 */
|
||||
width?: string
|
||||
/** format=tree-select 时的树数据([{ label, value, children }])。 */
|
||||
treeData?: Record<string, any>[]
|
||||
/** format=tree-select 时的树字段映射。 */
|
||||
treeProps?: { label?: string; value?: string; children?: string }
|
||||
}
|
||||
|
||||
/** JSON Schema 对象(表单 schema)。 */
|
||||
|
||||
File diff suppressed because one or more lines are too long
26
typed-router.d.ts
vendored
26
typed-router.d.ts
vendored
@ -51,6 +51,20 @@ declare module 'vue-router/auto-routes' {
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/system/dict/': RouteRecordInfo<
|
||||
'/system/dict/',
|
||||
'/system/dict',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/system/org/': RouteRecordInfo<
|
||||
'/system/org/',
|
||||
'/system/org',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/system/user/': RouteRecordInfo<
|
||||
'/system/user/',
|
||||
'/system/user',
|
||||
@ -102,6 +116,18 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/system/dict/index.vue': {
|
||||
routes:
|
||||
| '/system/dict/'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/system/org/index.vue': {
|
||||
routes:
|
||||
| '/system/org/'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/system/user/index.vue': {
|
||||
routes:
|
||||
| '/system/user/'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user