🆕 项目初始化,页面布局待修改完善
79
src/App.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<a-config-provider :theme="themeV" :locale="locale">
|
||||
<div
|
||||
:style="{
|
||||
'--ant-primary-color': useAppStore().theme.primaryColor,
|
||||
'--vxe-primary-color': useAppStore().theme.primaryColor,
|
||||
'--vxe-loading-color': useAppStore().theme.primaryColor,
|
||||
}"
|
||||
>
|
||||
<a-watermark :content="config.layout.waterMark">
|
||||
<router-view />
|
||||
</a-watermark>
|
||||
</div>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { theme } from 'ant-design-vue'
|
||||
import { config } from './settings/application'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import { messages, setI18nLanguage } from './locales'
|
||||
|
||||
const locale = computed(() => {
|
||||
return useAppStore().language === 'zh_CN' ? messages.zh_CN : messages.en_US
|
||||
})
|
||||
setI18nLanguage(useAppStore().language as 'zh_CN' | 'en_US')
|
||||
useI18n().locale.value = useAppStore().language
|
||||
const themeV = computed(() => {
|
||||
return {
|
||||
algorithm: app.layout.value.navTheme === 'real-dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: useAppStore().theme.primaryColor,
|
||||
borderRadius: 2,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const app = storeToRefs(useAppStore())
|
||||
useTitle(app.isCN.value ? config.title : config.name)
|
||||
const colorWeak = computed(() => {
|
||||
return app.layout.value.colorWeak
|
||||
})
|
||||
const gray = computed(() => {
|
||||
return app.layout.value.gray
|
||||
})
|
||||
watch(
|
||||
colorWeak,
|
||||
(newVal) => {
|
||||
const body = document.body
|
||||
if (newVal) {
|
||||
app.layout.value.gray = false
|
||||
body.style.filter = 'invert(80%)'
|
||||
} else {
|
||||
body.style.removeProperty('filter')
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
watch(
|
||||
gray,
|
||||
(newVal) => {
|
||||
const body = document.body
|
||||
if (newVal) {
|
||||
app.layout.value.colorWeak = false
|
||||
body.style.filter = 'grayscale(0.95)'
|
||||
} else {
|
||||
body.style.removeProperty('filter')
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
<style lang="css">
|
||||
:deep(.ant-btn-link) {
|
||||
color: var(--ant-primary-color) !important;
|
||||
}
|
||||
</style>
|
||||
198
src/api/acl/api.d.ts
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
declare namespace acl {
|
||||
/**
|
||||
* 权限
|
||||
*/
|
||||
export interface Permission {
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 权限描述 */
|
||||
description?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 权限key,英文 */
|
||||
key?: string;
|
||||
|
||||
/** 权限keyPath,用来做业务,(父级keyPath.key) */
|
||||
keyPath?: string;
|
||||
|
||||
/** 权限名称,中文用来做标识 */
|
||||
name?: string;
|
||||
|
||||
/** 父权限key */
|
||||
parentKey?: string;
|
||||
|
||||
/** 权限类型 */
|
||||
type?: 'MENU' | 'BUTTON' | 'OTHER';
|
||||
|
||||
/** typeInfo */
|
||||
typeInfo?: acl.Codebook;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限信息,包含是否选中标识
|
||||
*/
|
||||
export interface PermissionInfo {
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 权限描述 */
|
||||
description?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 权限key,英文 */
|
||||
key?: string;
|
||||
|
||||
/** 权限keyPath,用来做业务,(父级keyPath.key) */
|
||||
keyPath?: string;
|
||||
|
||||
/** 权限名称,中文用来做标识 */
|
||||
name?: string;
|
||||
|
||||
/** 父权限key */
|
||||
parentKey?: string;
|
||||
|
||||
/** 权限是否选中标识 */
|
||||
selected: boolean;
|
||||
|
||||
/** 权限类型 */
|
||||
type?: 'MENU' | 'BUTTON' | 'OTHER';
|
||||
|
||||
/** typeInfo */
|
||||
typeInfo?: acl.Codebook;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色
|
||||
*/
|
||||
export interface Role {
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 角色描述 */
|
||||
description?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 角色key,英文,用来做业务 */
|
||||
key: string;
|
||||
|
||||
/** 角色名称,中文用来做标识 */
|
||||
name?: string;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色信息,包含是否选中标识
|
||||
*/
|
||||
export interface RoleInfo {
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 角色描述 */
|
||||
description?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 角色key,英文,用来做业务 */
|
||||
key: string;
|
||||
|
||||
/** 角色名称,中文用来做标识 */
|
||||
name?: string;
|
||||
|
||||
/** 角色是否选中标识 */
|
||||
selected: boolean;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 树
|
||||
*/
|
||||
export interface TreeString {
|
||||
/** 下级列表 */
|
||||
children?: Array<acl.TreeString>;
|
||||
|
||||
/** 描述 */
|
||||
description?: string;
|
||||
|
||||
/** ID */
|
||||
key: string;
|
||||
|
||||
/** 名称 */
|
||||
name: string;
|
||||
|
||||
/** originData */
|
||||
originData: acl.TreeableString;
|
||||
|
||||
/** 父级 ID */
|
||||
parentKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 原始数据
|
||||
*/
|
||||
export interface TreeableString {
|
||||
/** description */
|
||||
description?: string;
|
||||
|
||||
/** key */
|
||||
key?: string;
|
||||
|
||||
/** name */
|
||||
name?: string;
|
||||
|
||||
/** parentKey */
|
||||
parentKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
export interface User {
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 邮箱 */
|
||||
email?: string;
|
||||
|
||||
/** 真实姓名 */
|
||||
fullName?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 手机号 */
|
||||
mobile?: string;
|
||||
|
||||
/** 用户名 */
|
||||
name: string;
|
||||
|
||||
/** 密码 */
|
||||
password?: string;
|
||||
|
||||
/** 性别 */
|
||||
sex?: 'MALE' | 'FEMALE';
|
||||
|
||||
/** sexInfo */
|
||||
sexInfo?: acl.Codebook;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
}
|
||||
11
src/api/acl/mods/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import permission from './permission';
|
||||
|
||||
import role from './role';
|
||||
|
||||
import user from './user';
|
||||
|
||||
export default {
|
||||
permission,
|
||||
role,
|
||||
user,
|
||||
};
|
||||
22
src/api/acl/mods/permission/add.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 增加权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: acl.Permission,
|
||||
|
||||
success: (data: acl.Permission) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'post',
|
||||
url: `/permission`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<acl.Permission, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/acl/mods/permission/batchInitPermissions.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 批量新增权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: Array<acl.Permission>,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'post',
|
||||
url: `/batch-init-permissions`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/acl/mods/permission/batchSyncPermissions.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 增量新增权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: Array<acl.Permission>,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'patch',
|
||||
url: `/batch-sync-permissions`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/permission/deletePermission.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 删除权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 权限key */
|
||||
key: string,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'delete',
|
||||
url: `/permission/${key}`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/permission/detail.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 权限详情
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 权限id */
|
||||
id: number,
|
||||
|
||||
success: (data: acl.Permission) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/permission/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<acl.Permission, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
25
src/api/acl/mods/permission/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @description 权限
|
||||
*
|
||||
*/
|
||||
import batchInitPermissions from './batchInitPermissions';
|
||||
import batchSyncPermissions from './batchSyncPermissions';
|
||||
import add from './add';
|
||||
import update from './update';
|
||||
import types from './types';
|
||||
import detail from './detail';
|
||||
import deletePermission from './deletePermission';
|
||||
import permissions from './permissions';
|
||||
import permissionTree from './permissionTree';
|
||||
|
||||
export default {
|
||||
batchInitPermissions,
|
||||
batchSyncPermissions,
|
||||
add,
|
||||
update,
|
||||
types,
|
||||
detail,
|
||||
deletePermission,
|
||||
permissions,
|
||||
permissionTree,
|
||||
};
|
||||
26
src/api/acl/mods/permission/permissionTree.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @desc 权限树
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export interface Params {
|
||||
/** 强制拉取 */
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
params: Params,
|
||||
success: (data: Array<acl.TreeString>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/permissions/tree`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<acl.TreeString>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
26
src/api/acl/mods/permission/permissions.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @desc 权限列表
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export interface Params {
|
||||
/** 强制拉取 */
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
params: Params,
|
||||
success: (data: Array<acl.Permission>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/permissions`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<acl.Permission>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
20
src/api/acl/mods/permission/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @desc 权限类型
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { Codebook } from '@/api/api';
|
||||
|
||||
export default async function (
|
||||
success: (data: Array<Codebook>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/permission/types`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<Codebook>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/acl/mods/permission/update.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 更新权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: acl.Permission,
|
||||
|
||||
success: (data: acl.Permission) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'patch',
|
||||
url: `/permission`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<acl.Permission, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/role/deleteRole.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 删除角色
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 角色key */
|
||||
key: string,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'delete',
|
||||
url: `/role/${key}`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/role/detail.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 角色详情
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 角色id */
|
||||
id: number,
|
||||
|
||||
success: (data: acl.Role) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/role/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<acl.Role, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
25
src/api/acl/mods/role/grant.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @desc 为指定角色授权
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 角色key */
|
||||
key: string,
|
||||
|
||||
/** 请求体 */
|
||||
requestBody: Array<string>,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'post',
|
||||
url: `/role/${key}/permissions`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/role/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @description 角色
|
||||
*
|
||||
*/
|
||||
import saveOrUpdateRole from './saveOrUpdateRole';
|
||||
import detail from './detail';
|
||||
import deleteRole from './deleteRole';
|
||||
import permissionInfos from './permissionInfos';
|
||||
import permissions from './permissions';
|
||||
import grant from './grant';
|
||||
import roles from './roles';
|
||||
|
||||
export default {
|
||||
saveOrUpdateRole,
|
||||
detail,
|
||||
deleteRole,
|
||||
permissionInfos,
|
||||
permissions,
|
||||
grant,
|
||||
roles,
|
||||
};
|
||||
21
src/api/acl/mods/role/permissionInfos.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 查询用于授权的权限信息
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 角色key */
|
||||
key: string,
|
||||
|
||||
success: (data: Array<acl.PermissionInfo>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/role/${key}/permission-infos`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<acl.PermissionInfo>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/role/permissions.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 查询角色权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 角色key */
|
||||
key: string,
|
||||
|
||||
success: (data: Array<acl.Permission>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/role/${key}/permissions`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<acl.Permission>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
31
src/api/acl/mods/role/roles.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @desc 分页查询角色
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { IPage } from '@/api/api';
|
||||
export interface Params {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 页面大小 */
|
||||
size?: number;
|
||||
/** 搜索关键词 */
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
params: Params,
|
||||
success: (data: IPage<acl.Role>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/roles`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<IPage<acl.Role>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/acl/mods/role/saveOrUpdateRole.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 增加/编辑角色
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: acl.Role,
|
||||
|
||||
success: (data: acl.Role) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'put',
|
||||
url: `/role`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<acl.Role, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/user/deleteUser.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 删除用户
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户id */
|
||||
id: number,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'delete',
|
||||
url: `/user/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/user/detail.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 用户详情
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户id */
|
||||
id: number,
|
||||
|
||||
success: (data: acl.User) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/user/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<acl.User, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
25
src/api/acl/mods/user/grant.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @desc 为指定用户授权
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户名 */
|
||||
name: string,
|
||||
|
||||
/** 请求体 */
|
||||
requestBody: Array<string>,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'post',
|
||||
url: `/user/${name}/permissions`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
25
src/api/acl/mods/user/grantRole.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @desc 为指定用户设置角色
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户名 */
|
||||
name: string,
|
||||
|
||||
/** 请求体 */
|
||||
requestBody: Array<string>,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'post',
|
||||
url: `/user/${name}/roles`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
29
src/api/acl/mods/user/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @description 用户
|
||||
*
|
||||
*/
|
||||
import saveOrUpdateUser from './saveOrUpdateUser';
|
||||
import sexes from './sexes';
|
||||
import detail from './detail';
|
||||
import deleteUser from './deleteUser';
|
||||
import resetPassword from './resetPassword';
|
||||
import permissionInfos from './permissionInfos';
|
||||
import permissions from './permissions';
|
||||
import grant from './grant';
|
||||
import roleInfos from './roleInfos';
|
||||
import grantRole from './grantRole';
|
||||
import users from './users';
|
||||
|
||||
export default {
|
||||
saveOrUpdateUser,
|
||||
sexes,
|
||||
detail,
|
||||
deleteUser,
|
||||
resetPassword,
|
||||
permissionInfos,
|
||||
permissions,
|
||||
grant,
|
||||
roleInfos,
|
||||
grantRole,
|
||||
users,
|
||||
};
|
||||
21
src/api/acl/mods/user/permissionInfos.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 查询用于授权的权限信息
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户名 */
|
||||
name: string,
|
||||
|
||||
success: (data: Array<acl.PermissionInfo>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/user/${name}/permission-infos`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<acl.PermissionInfo>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/user/permissions.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 查询用户权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户名 */
|
||||
name: string,
|
||||
|
||||
success: (data: Array<acl.Permission>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/user/${name}/permissions`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<acl.Permission>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/user/resetPassword.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 重置密码
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户名 */
|
||||
name: string,
|
||||
|
||||
success: (data: string) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'patch',
|
||||
url: `/user/${name}/password`,
|
||||
})
|
||||
.then((data: AxiosResponse<string, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/acl/mods/user/roleInfos.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 查询用户权限
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 用户名 */
|
||||
name: string,
|
||||
|
||||
success: (data: Array<acl.RoleInfo>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/user/${name}/role-infos`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<acl.RoleInfo>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/acl/mods/user/saveOrUpdateUser.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 增加/编辑用户
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: acl.User,
|
||||
|
||||
success: (data: acl.User) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'put',
|
||||
url: `/user`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<acl.User, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
20
src/api/acl/mods/user/sexes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @desc 用户性别
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { Codebook } from '@/api/api';
|
||||
|
||||
export default async function (
|
||||
success: (data: Array<Codebook>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/user/sexes`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<Codebook>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
33
src/api/acl/mods/user/users.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @desc 分页查询用户
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { IPage } from '@/api/api';
|
||||
export interface Params {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 页面大小 */
|
||||
size?: number;
|
||||
/** 性别 */
|
||||
sex?: 'MALE' | 'FEMALE';
|
||||
/** 搜索关键词 */
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
params: Params,
|
||||
success: (data: IPage<acl.User>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/users`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<IPage<acl.User>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
4329
src/api/api-lock.json
Normal file
118
src/api/api.d.ts
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* mybatis-plus 分页对象
|
||||
*/
|
||||
export interface IPage<T> {
|
||||
/**
|
||||
* 当前页面
|
||||
*/
|
||||
current: number;
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
pages?: number;
|
||||
/**
|
||||
* 数据记录
|
||||
*/
|
||||
records?: Array<T>;
|
||||
/**
|
||||
* 分页大小
|
||||
*/
|
||||
size: number;
|
||||
/**
|
||||
* 数据总条数
|
||||
*/
|
||||
total?: number;
|
||||
}
|
||||
/**
|
||||
* Nutz 分页对象
|
||||
*/
|
||||
export interface Pagination<T> {
|
||||
/**
|
||||
* 数据记录
|
||||
*/
|
||||
dataList: Array<T>;
|
||||
/**
|
||||
* 是否首页
|
||||
*/
|
||||
first?: boolean;
|
||||
/**
|
||||
* 是否尾页
|
||||
*/
|
||||
last?: boolean;
|
||||
/**
|
||||
* 偏移量
|
||||
*/
|
||||
offset?: number;
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
pageCount: number;
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
pageNumber: number;
|
||||
/**
|
||||
* 页面大小
|
||||
*/
|
||||
pageSize: number;
|
||||
/**
|
||||
* sql参数
|
||||
*/
|
||||
paras: Record<string, unknown>;
|
||||
/**
|
||||
* 数据记录总数
|
||||
*/
|
||||
recordCount?: number;
|
||||
}
|
||||
/**
|
||||
* VXE 数据结构
|
||||
*/
|
||||
export interface VXETableSaveDTO<T> {
|
||||
/**
|
||||
* 新增记录数据
|
||||
*/
|
||||
insertRecords: Array<T>;
|
||||
/**
|
||||
* 删除记录数据
|
||||
*/
|
||||
removeRecords: Array<T>;
|
||||
/**
|
||||
* 更新记录数据
|
||||
*/
|
||||
updateRecords: Array<T>;
|
||||
}
|
||||
/**
|
||||
* 枚举码本
|
||||
*/
|
||||
export interface Codebook {
|
||||
/** 码本编码 */
|
||||
code: string;
|
||||
|
||||
/** 码本名称 */
|
||||
description: string;
|
||||
|
||||
/** 码本值 */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局错误
|
||||
*/
|
||||
export interface GlobalError {
|
||||
/** 错误码 */
|
||||
code?: number;
|
||||
|
||||
/** 错误信息 */
|
||||
message?: string;
|
||||
}
|
||||
/**
|
||||
* NutMap
|
||||
*/
|
||||
type NutMap<Key extends string, Value = unknown> = {
|
||||
[key in Key]: Value;
|
||||
};
|
||||
// 导入其他模块
|
||||
/// <reference path="./auth/api.d.ts" />
|
||||
/// <reference path="./acl/api.d.ts" />
|
||||
/// <reference path="./dictionary/api.d.ts" />
|
||||
/// <reference path="./material/api.d.ts" />
|
||||
71
src/api/auth/api.d.ts
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
declare namespace auth {
|
||||
/**
|
||||
* AuthUser
|
||||
*/
|
||||
export interface AuthUser {
|
||||
/** anonymous */
|
||||
anonymous?: boolean;
|
||||
|
||||
/** 用户头像 */
|
||||
avatar?: string;
|
||||
|
||||
/** 邮箱 */
|
||||
email?: string;
|
||||
|
||||
/** extInfo */
|
||||
extInfo?: auth.NutMap;
|
||||
|
||||
/** 姓名 */
|
||||
fullName: string;
|
||||
|
||||
/** 电话 */
|
||||
mobile?: string;
|
||||
|
||||
/** 密码 */
|
||||
password: string;
|
||||
|
||||
/** 权限列表 */
|
||||
permissions: Array<string>;
|
||||
|
||||
/** jwt refreshToken */
|
||||
refreshToken: string;
|
||||
|
||||
/** 角色列表 */
|
||||
roles: Array<string>;
|
||||
|
||||
/** 性别 */
|
||||
sex?: 'MALE' | 'FEMALE';
|
||||
|
||||
/** sexInfo */
|
||||
sexInfo?: auth.Codebook;
|
||||
|
||||
/** jwt Token */
|
||||
token: string;
|
||||
|
||||
/** 用户名 */
|
||||
userName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录实体
|
||||
*/
|
||||
export interface LoginDTO {
|
||||
/** 验证码 */
|
||||
captcha?: string;
|
||||
|
||||
/** 手机号 */
|
||||
mobile?: string;
|
||||
|
||||
/** 密码 */
|
||||
password?: string;
|
||||
|
||||
/** 登录类型 */
|
||||
type: 'ACCOUNT' | 'WECHAT';
|
||||
|
||||
/** 用户名 */
|
||||
userName?: string;
|
||||
|
||||
/** uuid */
|
||||
uuid?: string;
|
||||
}
|
||||
}
|
||||
18
src/api/auth/mods/auth/currentUser.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @desc 当前用户
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
success: (data: auth.AuthUser) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/auth/current-user`,
|
||||
})
|
||||
.then((data: AxiosResponse<auth.AuthUser, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
13
src/api/auth/mods/auth/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @description 登录
|
||||
*
|
||||
*/
|
||||
import currentUser from './currentUser';
|
||||
import login from './login';
|
||||
import logout from './logout';
|
||||
|
||||
export default {
|
||||
currentUser,
|
||||
login,
|
||||
logout,
|
||||
};
|
||||
22
src/api/auth/mods/auth/login.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 登录
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: auth.LoginDTO,
|
||||
|
||||
success: (data: auth.AuthUser) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'post',
|
||||
url: `/auth/login`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<auth.AuthUser, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
18
src/api/auth/mods/auth/logout.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @desc 登出
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'post',
|
||||
url: `/auth/logout`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
5
src/api/auth/mods/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import auth from './auth';
|
||||
|
||||
export default {
|
||||
auth,
|
||||
};
|
||||
62
src/api/dictionary/api.d.ts
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
declare namespace dictionary {
|
||||
/**
|
||||
* 码本数据
|
||||
*/
|
||||
export interface Dictionary {
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 描述 */
|
||||
description?: string;
|
||||
|
||||
/** 禁用标识 */
|
||||
disabled?: boolean;
|
||||
|
||||
/** 分组Key */
|
||||
groupKey?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 序号 */
|
||||
index?: number;
|
||||
|
||||
/** key */
|
||||
key?: string;
|
||||
|
||||
/** 上级Key */
|
||||
parentKey?: string;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
|
||||
/** value */
|
||||
value?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 码本分组
|
||||
*/
|
||||
export interface Group {
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 分组描述 */
|
||||
description?: string;
|
||||
|
||||
/** 禁用标识 */
|
||||
disabled?: boolean;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 分组唯一键 */
|
||||
key: string;
|
||||
|
||||
/** 分组名称 */
|
||||
name?: string;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
}
|
||||
23
src/api/dictionary/mods/dictionary/deleteDictionary.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @desc 删除码本数据
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 码本分组key */
|
||||
group: string,
|
||||
/** 码本数据key */
|
||||
key: string,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'delete',
|
||||
url: `/group/${group}/dictionary/${key}`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/dictionary/mods/dictionary/deleteGroup.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 删除码本分组
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 码本分组key */
|
||||
key: string,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'delete',
|
||||
url: `/group/${key}`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/dictionary/mods/dictionary/dictionaries.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 分组下的数据字典列表
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 码本分组key */
|
||||
group: string,
|
||||
|
||||
success: (data: Array<dictionary.Dictionary>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/group/${group}/dictionaries`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<dictionary.Dictionary>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/dictionary/mods/dictionary/groupDetail.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 码本分组详情
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 码本分组id */
|
||||
id: number,
|
||||
|
||||
success: (data: dictionary.Group) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/group/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<dictionary.Group, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
31
src/api/dictionary/mods/dictionary/groups.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @desc 分页查询码本分组
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { IPage } from '@/api/api';
|
||||
export interface Params {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 页面大小 */
|
||||
size?: number;
|
||||
/** 搜索关键词 */
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
params: Params,
|
||||
success: (data: IPage<dictionary.Group>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/groups`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<IPage<dictionary.Group>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/dictionary/mods/dictionary/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @description 码本数据
|
||||
*
|
||||
*/
|
||||
import saveOrUpdateGroup from './saveOrUpdateGroup';
|
||||
import dictionaries from './dictionaries';
|
||||
import saveOrUpdateDictionary from './saveOrUpdateDictionary';
|
||||
import deleteDictionary from './deleteDictionary';
|
||||
import groupDetail from './groupDetail';
|
||||
import deleteGroup from './deleteGroup';
|
||||
import groups from './groups';
|
||||
|
||||
export default {
|
||||
saveOrUpdateGroup,
|
||||
dictionaries,
|
||||
saveOrUpdateDictionary,
|
||||
deleteDictionary,
|
||||
groupDetail,
|
||||
deleteGroup,
|
||||
groups,
|
||||
};
|
||||
25
src/api/dictionary/mods/dictionary/saveOrUpdateDictionary.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @desc 增加/编辑码本数据
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 码本分组key */
|
||||
group: string,
|
||||
|
||||
/** 请求体 */
|
||||
requestBody: dictionary.Dictionary,
|
||||
|
||||
success: (data: dictionary.Dictionary) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'put',
|
||||
url: `/group/${group}/dictionary`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<dictionary.Dictionary, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/dictionary/mods/dictionary/saveOrUpdateGroup.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 增加/编辑码本分组
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: dictionary.Group,
|
||||
|
||||
success: (data: dictionary.Group) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'put',
|
||||
url: `/group`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<dictionary.Group, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
5
src/api/dictionary/mods/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import dictionary from './dictionary';
|
||||
|
||||
export default {
|
||||
dictionary,
|
||||
};
|
||||
19
src/api/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { App } from 'vue';
|
||||
import authApi from './auth/mods';
|
||||
|
||||
import aclApi from './acl/mods';
|
||||
|
||||
import dictionaryApi from './dictionary/mods';
|
||||
|
||||
import materialApi from './material/mods';
|
||||
|
||||
export const api = {
|
||||
authApi,
|
||||
aclApi,
|
||||
dictionaryApi,
|
||||
materialApi,
|
||||
install: (app: App) => {
|
||||
app.config.globalProperties.$api = api;
|
||||
},
|
||||
};
|
||||
export default api;
|
||||
105
src/api/material/api.d.ts
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
declare namespace material {
|
||||
/**
|
||||
* 申请单明细
|
||||
*/
|
||||
export interface ApplyDetail {
|
||||
/** 申请单ID */
|
||||
applyId?: number;
|
||||
|
||||
/** 确认数量 */
|
||||
confirmQuantity?: number;
|
||||
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 异常情况说明 */
|
||||
exceptionRemark?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 物料Id */
|
||||
materialId?: number;
|
||||
|
||||
/** 申请数量 */
|
||||
quantity?: number;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 入库/出库 申请单
|
||||
*/
|
||||
export interface ApplyForm {
|
||||
/** 申请人 */
|
||||
applicant?: string;
|
||||
|
||||
/** 申请日期 */
|
||||
applyDate?: string;
|
||||
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 异常说明 */
|
||||
exceptionExplain?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 是否确认 */
|
||||
isConfirm?: boolean;
|
||||
|
||||
/** 类型 */
|
||||
type?: number;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请单
|
||||
*/
|
||||
export interface ApplyInfo {
|
||||
/** applyDetails */
|
||||
applyDetails?: Array<material.ApplyDetail>;
|
||||
|
||||
/** applyForm */
|
||||
applyForm?: material.ApplyForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 物料信息
|
||||
*/
|
||||
export interface Material {
|
||||
/** 赋码规则 */
|
||||
assignRule?: number;
|
||||
|
||||
/** 编码 */
|
||||
code?: string;
|
||||
|
||||
/** createdTime */
|
||||
createdTime?: string;
|
||||
|
||||
/** 备注 */
|
||||
description?: string;
|
||||
|
||||
/** id */
|
||||
id?: number;
|
||||
|
||||
/** 名称 */
|
||||
name?: string;
|
||||
|
||||
/** 规格 */
|
||||
spec?: string;
|
||||
|
||||
/** 库存数量 */
|
||||
stock?: number;
|
||||
|
||||
/** 类型 */
|
||||
type?: string;
|
||||
|
||||
/** updatedTime */
|
||||
updatedTime?: string;
|
||||
}
|
||||
}
|
||||
33
src/api/material/mods/apply/applies.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @desc 分页查询申请单列表
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { IPage } from '@/api/api';
|
||||
export interface Params {
|
||||
/** 类型 */
|
||||
type: number;
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 页面大小 */
|
||||
size?: number;
|
||||
/** 搜索关键词 */
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
params: Params,
|
||||
success: (data: IPage<material.ApplyForm>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/apply`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<IPage<material.ApplyForm>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/material/mods/apply/detail.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 查询申请单详情
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 类型 */
|
||||
applyId: number,
|
||||
|
||||
success: (data: material.ApplyInfo) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/apply/${applyId}`,
|
||||
})
|
||||
.then((data: AxiosResponse<material.ApplyInfo, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
13
src/api/material/mods/apply/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @description 入库/出库 申请单管理
|
||||
*
|
||||
*/
|
||||
import applies from './applies';
|
||||
import saveOrUpdateApply from './saveOrUpdateApply';
|
||||
import detail from './detail';
|
||||
|
||||
export default {
|
||||
applies,
|
||||
saveOrUpdateApply,
|
||||
detail,
|
||||
};
|
||||
34
src/api/material/mods/apply/materials.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @desc 分页查询申请单列表
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { IPage } from '@/api/api';
|
||||
export interface Params {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 页面大小 */
|
||||
size?: number;
|
||||
/** 搜索关键词 */
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
/** 类型 */
|
||||
type: number,
|
||||
|
||||
params: Params,
|
||||
success: (data: IPage<material.ApplyForm>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/apply/${type}`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<IPage<material.ApplyForm>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/material/mods/apply/saveOrUpdateApply.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 增加/编辑申请单
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: material.ApplyInfo,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'put',
|
||||
url: `/apply`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
8
src/api/material/mods/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import apply from './apply';
|
||||
|
||||
import material from './material';
|
||||
|
||||
export default {
|
||||
apply,
|
||||
material,
|
||||
};
|
||||
18
src/api/material/mods/material/all.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @desc 查询所有物料列表
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
success: (data: Array<material.Material>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/material/list`,
|
||||
})
|
||||
.then((data: AxiosResponse<Array<material.Material>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/material/mods/material/deleteMaterial.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 删除物料
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 物料id */
|
||||
id: number,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'delete',
|
||||
url: `/material/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/material/mods/material/deleteUser.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 删除物料
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 物料id */
|
||||
id: number,
|
||||
|
||||
success: (data: void) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'delete',
|
||||
url: `/material/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<void, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
21
src/api/material/mods/material/detail.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @desc 物料详情
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 物料id */
|
||||
id: number,
|
||||
|
||||
success: (data: material.Material) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/material/${id}`,
|
||||
})
|
||||
.then((data: AxiosResponse<material.Material, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
17
src/api/material/mods/material/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @description 物料管理
|
||||
*
|
||||
*/
|
||||
import saveOrUpdateMaterial from './saveOrUpdateMaterial';
|
||||
import all from './all';
|
||||
import detail from './detail';
|
||||
import deleteMaterial from './deleteMaterial';
|
||||
import materials from './materials';
|
||||
|
||||
export default {
|
||||
saveOrUpdateMaterial,
|
||||
all,
|
||||
detail,
|
||||
deleteMaterial,
|
||||
materials,
|
||||
};
|
||||
33
src/api/material/mods/material/materials.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @desc 分页查询物料列表
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { IPage } from '@/api/api';
|
||||
export interface Params {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
/** 页面大小 */
|
||||
size?: number;
|
||||
/** 类型 */
|
||||
type?: number;
|
||||
/** 搜索关键词 */
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export default async function (
|
||||
params: Params,
|
||||
success: (data: IPage<material.Material>) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'get',
|
||||
url: `/materials`,
|
||||
|
||||
params,
|
||||
})
|
||||
.then((data: AxiosResponse<IPage<material.Material>, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
22
src/api/material/mods/material/saveOrUpdateMaterial.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @desc 增加/编辑物料
|
||||
*/
|
||||
import { defaultSuccess, defaultError, http } from '@/plugins/axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
export default async function (
|
||||
/** 请求体 */
|
||||
requestBody: material.Material,
|
||||
|
||||
success: (data: material.Material) => void = defaultSuccess,
|
||||
fail: (error: { code: string; error?: string }) => void = defaultError,
|
||||
): Promise<void> {
|
||||
return http({
|
||||
method: 'put',
|
||||
url: `/material`,
|
||||
data: requestBody,
|
||||
})
|
||||
.then((data: AxiosResponse<material.Material, unknown>) => {
|
||||
success(data.data);
|
||||
})
|
||||
.catch((error: { code: string; error?: string }) => fail(error));
|
||||
}
|
||||
72
src/api/pontCore.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @description pont内置请求单例
|
||||
*/
|
||||
|
||||
class PontCoreManager {
|
||||
static singleInstance = null as PontCoreManager;
|
||||
|
||||
static getSignleInstance() {
|
||||
if (!PontCoreManager.singleInstance) {
|
||||
PontCoreManager.singleInstance = new PontCoreManager();
|
||||
return PontCoreManager.singleInstance;
|
||||
}
|
||||
return PontCoreManager.singleInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch请求
|
||||
* @param url 请求url
|
||||
* @param options fetch 请求配置
|
||||
*/
|
||||
fetch(url: string, options = {}) {
|
||||
return fetch(url, options).then((res) => {
|
||||
return res.json();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用外部传入的请求方法替换默认的fetch请求
|
||||
*/
|
||||
useFetch(fetch: (url: string, options?: any) => Promise<any>) {
|
||||
if (typeof fetch !== 'function') {
|
||||
console.error('fetch should be a function ');
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetch = fetch;
|
||||
}
|
||||
|
||||
getUrl(path: string, queryParams: any, method: string) {
|
||||
const params = {
|
||||
...(queryParams || ({} as any)),
|
||||
};
|
||||
|
||||
const url = path.replace(/\{([^\\}]*(?:\\.[^\\}]*)*)\}/gm, (match, key) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
key = key.trim();
|
||||
|
||||
if (params[key] !== undefined) {
|
||||
const value = params[key];
|
||||
delete params[key];
|
||||
return value;
|
||||
}
|
||||
console.warn('Please set value for template key: ', key);
|
||||
return '';
|
||||
});
|
||||
|
||||
const paramStr = Object.keys(params)
|
||||
.map((key) => {
|
||||
return params[key] === undefined ? '' : `${key}=${params[key]}`;
|
||||
})
|
||||
.filter((id) => id)
|
||||
.join('&');
|
||||
|
||||
if (paramStr) {
|
||||
return `${url}?${paramStr}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
export const PontCore = PontCoreManager.getSignleInstance();
|
||||
BIN
src/assets/PDF.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/assets/WORD.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/crun.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/excel.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/iam.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
src/assets/image.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/login-bg.png
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
297
src/auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
||||
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
||||
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
||||
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
|
||||
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
|
||||
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
|
||||
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
|
||||
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
|
||||
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
|
||||
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
|
||||
const useArraySome: typeof import('@vueuse/core')['useArraySome']
|
||||
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useImage: typeof import('@vueuse/core')['useImage']
|
||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
||||
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||
const useParentElement: typeof import('@vueuse/core')['useParentElement']
|
||||
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
|
||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSorted: typeof import('@vueuse/core')['useSorted']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||
const useSupported: typeof import('@vueuse/core')['useSupported']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
||||
const useToString: typeof import('@vueuse/core')['useToString']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchArray: typeof import('@vueuse/core')['watchArray']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||
const watchDeep: typeof import('@vueuse/core')['watchDeep']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
99
src/components.d.ts
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
ABadgeRibbon: typeof import('ant-design-vue/es')['BadgeRibbon']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
ACardGrid: typeof import('ant-design-vue/es')['CardGrid']
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
ACol: typeof import('ant-design-vue/es')['Col']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
||||
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
|
||||
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AFlex: typeof import('ant-design-vue/es')['Flex']
|
||||
AFloatButton: typeof import('ant-design-vue/es')['FloatButton']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AImage: typeof import('ant-design-vue/es')['Image']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||
ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter']
|
||||
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
||||
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
||||
AMentionsOption: typeof import('ant-design-vue/es')['MentionsOption']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
AResult: typeof import('ant-design-vue/es')['Result']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASlider: typeof import('ant-design-vue/es')['Slider']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||
ASteps: typeof import('ant-design-vue/es')['Steps']
|
||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
ATable: typeof import('ant-design-vue/es')['Table']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
ATransfer: typeof import('ant-design-vue/es')['Transfer']
|
||||
ATree: typeof import('ant-design-vue/es')['Tree']
|
||||
ATypographyLink: typeof import('ant-design-vue/es')['TypographyLink']
|
||||
ATypographyParagraph: typeof import('ant-design-vue/es')['TypographyParagraph']
|
||||
ATypographyTitle: typeof import('ant-design-vue/es')['TypographyTitle']
|
||||
AuxiliaryItemRender: typeof import('./components/form-render/auxiliary-item-render.vue')['default']
|
||||
AWatermark: typeof import('ant-design-vue/es')['Watermark']
|
||||
CodeGeneratorDrawer: typeof import('./components/form-designer/component-container/code-generator-drawer.vue')['default']
|
||||
ColComponentItem: typeof import('./components/form-designer/component-container/col-component-item.vue')['default']
|
||||
ComponentContainer: typeof import('./components/form-designer/component-container/component-container.vue')['default']
|
||||
ComponentItem: typeof import('./components/form-designer/component-container/component-item.vue')['default']
|
||||
ComponentPanel: typeof import('./components/form-designer/component-panel/component-panel.vue')['default']
|
||||
ConfigPanel: typeof import('./components/form-designer/config-panel/config-panel.vue')['default']
|
||||
DataSourceTable: typeof import('./components/form-designer/config-panel/data-source-table.vue')['default']
|
||||
FormConfigPanel: typeof import('./components/form-designer/config-panel/form-config-panel.vue')['default']
|
||||
FormDesigner: typeof import('./components/form-designer/form-designer.vue')['default']
|
||||
FormDrawer: typeof import('./components/form-render/form-drawer.vue')['default']
|
||||
FormItemConfigPanel: typeof import('./components/form-designer/config-panel/form-item-config-panel.vue')['default']
|
||||
FormItemRender: typeof import('./components/form-render/form-item-render.vue')['default']
|
||||
FormModal: typeof import('./components/form-render/form-modal.vue')['default']
|
||||
FormPreviewDrawer: typeof import('./components/form-designer/component-container/form-preview-drawer.vue')['default']
|
||||
FormRender: typeof import('./components/form-render/form-render.vue')['default']
|
||||
IconFont: typeof import('./components/icon-font/icon-font.vue')['default']
|
||||
ImageUploader: typeof import('./components/image-uploader/image-uploader.vue')['default']
|
||||
InputItem: typeof import('./components/form-designer/component-panel/input-item.vue')['default']
|
||||
OptionTable: typeof import('./components/form-designer/config-panel/option-table.vue')['default']
|
||||
PageContainer: typeof import('./components/page-container/page-container.vue')['default']
|
||||
PropertyConfig: typeof import('./components/form-designer/config-panel/property-config.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
RowComponentItem: typeof import('./components/form-designer/component-container/row-component-item.vue')['default']
|
||||
SearchTree: typeof import('./components/search-tree.vue')['default']
|
||||
SelectedTree: typeof import('./components/search-tree.vue')['default']
|
||||
TreeDataTable: typeof import('./components/form-designer/config-panel/tree-data-table.vue')['default']
|
||||
ValidateRuleConfig: typeof import('./components/form-designer/config-panel/validate-rule-config.vue')['default']
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<a-drawer v-model:open="visible" title="生成结果" width="50%" :closable="false">
|
||||
<a-tabs v-model:active-key="activeKey">
|
||||
<template #rightExtra>
|
||||
<a-button type="primary" size="small" @click="copy(code)">
|
||||
<template #icon>
|
||||
<icon-font type="icon-copy"></icon-font>
|
||||
</template>
|
||||
复制
|
||||
</a-button>
|
||||
</template>
|
||||
<a-tab-pane key="form" tab="form.ts"></a-tab-pane>
|
||||
<a-tab-pane key="render" tab="form.vue"></a-tab-pane>
|
||||
<a-tab-pane key="modal" tab="modal.vue"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<codemirror
|
||||
v-model="code"
|
||||
disabled
|
||||
:style="{ height: 'calc(100vh - 175px)' }"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tab-size="2"
|
||||
:extensions="extensions"
|
||||
/>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { PropType, computed, ref, toRefs, watch } from 'vue'
|
||||
import { Codemirror } from 'vue-codemirror'
|
||||
import { java } from '@codemirror/lang-java'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import XEUtils from 'xe-utils'
|
||||
import { notification } from 'ant-design-vue'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { FormDesignerItem } from '../form-designer-types'
|
||||
import { FormConfig, FormItem } from '@/components/form-render/form-render-types'
|
||||
|
||||
const extensions = [java(), javascript({ jsx: true, typescript: true }), oneDark]
|
||||
const visible = ref(false)
|
||||
|
||||
const show = () => {
|
||||
visible.value = true
|
||||
}
|
||||
defineExpose({
|
||||
show,
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
components: {
|
||||
type: Array as PropType<FormDesignerItem[]>,
|
||||
required: true,
|
||||
},
|
||||
config: {
|
||||
type: Object as PropType<FormConfig>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const { components, config } = toRefs(props)
|
||||
|
||||
const hanldConfig = (item: FormDesignerItem) => {
|
||||
if (item.formItem.config) {
|
||||
delete item.formItem.config.labelColFixedWidthInPx
|
||||
delete item.formItem.config.labelColSpanWidth
|
||||
delete item.formItem.config.labelType
|
||||
delete item.formItem.config.useFormConfig
|
||||
if (item.formItem.config.labelType === 'fixed') {
|
||||
delete item.formItem.config.labelCol?.span
|
||||
delete item.formItem.config.labelCol?.offset
|
||||
} else {
|
||||
delete item.formItem.config.labelCol?.style
|
||||
}
|
||||
}
|
||||
if (item.formItem.properties.options) {
|
||||
item.formItem.properties.options.forEach((item) => {
|
||||
delete item.uuid
|
||||
})
|
||||
}
|
||||
if (item.formItem.rules?.length) {
|
||||
item.formItem.rules.forEach((item) => {
|
||||
;(item as { uuid?: string }).uuid = undefined
|
||||
if (item.enum) {
|
||||
item.enum = String(item.enum).split(',')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const activeKey = ref('form')
|
||||
const configTransfer = (item: FormDesignerItem): FormItem => {
|
||||
if (item.formItem) {
|
||||
delete item.formItem.propertyConfigFormComponents
|
||||
}
|
||||
if (item.formItem?.config) {
|
||||
hanldConfig(item)
|
||||
}
|
||||
if (item.formItem.propertiesTransfer) {
|
||||
item.formItem.properties = item.formItem.propertiesTransfer(item.formItem.properties)
|
||||
}
|
||||
//递归一下
|
||||
if (item.children) {
|
||||
return XEUtils.assign(item.formItem, { children: item.children.map((item) => configTransfer(item)) })
|
||||
}
|
||||
return item.formItem
|
||||
}
|
||||
const code = computed(() => {
|
||||
const formConfig = XEUtils.clone(config.value, true)
|
||||
if (formConfig.labelType === 'fixed') {
|
||||
delete formConfig.labelCol?.span
|
||||
delete formConfig.labelCol?.offset
|
||||
} else {
|
||||
delete formConfig.labelCol?.style
|
||||
}
|
||||
delete formConfig.labelColFixedWidthInPx
|
||||
delete formConfig.labelColSpanWidth
|
||||
delete formConfig.labelType
|
||||
switch (activeKey.value) {
|
||||
case 'form':
|
||||
return `import { FormItem, FormConfig } from '@/components/form-render/form-render-types';
|
||||
|
||||
export const config: FormConfig = ${JSON.stringify(formConfig, undefined, 2)}
|
||||
|
||||
export const formItems: FormItem[] = ${JSON.stringify(
|
||||
XEUtils.clone(components.value, true).map((item) => configTransfer(item)),
|
||||
undefined,
|
||||
2,
|
||||
)}
|
||||
`
|
||||
case 'modal':
|
||||
return `<template>
|
||||
<div>
|
||||
<a-button type="primary" @click="add">添加</a-button>
|
||||
<form-modal
|
||||
ref="formModal"
|
||||
v-model="data"
|
||||
:form-items="items"
|
||||
:config="formConfig"
|
||||
:title="title"
|
||||
:hidden-fields="hiddenFields"
|
||||
:disabled-fields="disabledFields"
|
||||
@ok="hanldOk"
|
||||
></form-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import FormModal from '@/components/form-render/form-modal.vue';
|
||||
import { config, formItems } from './form';
|
||||
import { FormDataType } from '@/components/form-render/form-render-types';
|
||||
const items = computed(() => {
|
||||
return formItems;
|
||||
});
|
||||
|
||||
const formConfig = computed(() => {
|
||||
return config;
|
||||
});
|
||||
|
||||
const data = ref<FormDataType>({});
|
||||
const title = computed(() => {
|
||||
//TODO 根据数据情况设置标题
|
||||
return '添加';
|
||||
});
|
||||
const hiddenFields = computed(() => {
|
||||
//TODO 根据数据情况设置隐藏字段
|
||||
return [];
|
||||
});
|
||||
const hanldOk = (_data: FormDataType) => {
|
||||
//TODO 根据业务情况处理
|
||||
console.log(_data);
|
||||
formModal.value?.close(); //关闭弹窗
|
||||
};
|
||||
const disabledFields = computed(() => {
|
||||
//TODO 根据数据情况设置隐藏字段
|
||||
return [];
|
||||
});
|
||||
const formModal = ref<typeof FormModal>();
|
||||
|
||||
const add = () => {
|
||||
formModal.value?.open(); //通过ref可调用的其他方法有 open, close,reset, validateFields,resetFields,getModalContentWrapper, getPopupContainerId, closeLoading,
|
||||
};
|
||||
// 其他属性包含 maxHeight,width,wrapClassName,modalContentWrapperClassName
|
||||
<\\/script>
|
||||
`.replace('\\', '')
|
||||
case 'render':
|
||||
return `<template>
|
||||
<form-render
|
||||
v-model="data"
|
||||
:items="items"
|
||||
:config="formConfig"
|
||||
:hidden-fields="hiddenFields"
|
||||
:disabled-fields="disabledFields"
|
||||
></form-render>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { config, formItems } from './form';
|
||||
import { FormDataType } from '@/components/form-render/form-render-types';
|
||||
const items = computed(() => {
|
||||
return formItems;
|
||||
});
|
||||
|
||||
const formConfig = computed(() => {
|
||||
return config;
|
||||
});
|
||||
|
||||
const data = ref<FormDataType>({});
|
||||
const hiddenFields = computed(() => {
|
||||
//TODO 根据数据情况设置隐藏字段
|
||||
return [];
|
||||
});
|
||||
const disabledFields = computed(() => {
|
||||
//TODO 根据数据情况设置隐藏字段
|
||||
return [];
|
||||
});
|
||||
// 可通过ref调用render的方法有 validateFields, resetFields, clearValidate
|
||||
<\\/script>
|
||||
`.replace('\\', '')
|
||||
default:
|
||||
return `
|
||||
<template>
|
||||
<form-render v-model="formData" :form-config="formConfig" :form-items="formItems" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FormRender from '@/components/form-render/form-render.vue';
|
||||
import { useFormItems } from './form-items';
|
||||
import type { FormConfig } from '@/components/form-render/types/form-designer';
|
||||
import type { FormData } from '@/components/form-render/types/form-render';
|
||||
|
||||
const formData = ref<FormData>({
|
||||
layout: 'horizontal',
|
||||
colon: true,
|
||||
hideRequiredMark: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'fixed',
|
||||
scrollToFirstError: false,
|
||||
validateOnRuleChange: true,
|
||||
labelCol: {}
|
||||
});
|
||||
const formConfig = ref<FormConfig>();
|
||||
const { formItems } = useFormItems();
|
||||
<\\/script>
|
||||
`.replace('\\', '')
|
||||
}
|
||||
})
|
||||
const { copy, text } = useClipboard()
|
||||
|
||||
watch(text, (newVal) => {
|
||||
if (newVal) {
|
||||
notification.success({
|
||||
message: '操作成功',
|
||||
description: '代码已经成功复制',
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<a-col :id="id" v-bind="Object.assign({}, $attrs, item.formItem.properties)" :data-id="item.uuid">
|
||||
<component-item
|
||||
v-for="(c, index) in item.children"
|
||||
:key="c.uuid"
|
||||
:data-id="c.uuid"
|
||||
:item="c"
|
||||
:selected="selected"
|
||||
@selected="selectedChange"
|
||||
@copy="copyItem(index)"
|
||||
@delete="deleteItem(index, $event)"
|
||||
/>
|
||||
</a-col>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormDesignerItem } from '../form-designer-types'
|
||||
import Sortable from 'sortablejs'
|
||||
import allComponents from '../component-panel/components'
|
||||
import XEUtils from 'xe-utils'
|
||||
import { notification } from 'ant-design-vue'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<FormDesignerItem>,
|
||||
required: true,
|
||||
},
|
||||
selected: {
|
||||
type: Object as PropType<FormDesignerItem>,
|
||||
required: false,
|
||||
default: () => null,
|
||||
},
|
||||
components: {
|
||||
type: Array as PropType<Array<FormDesignerItem>>,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const { item, selected, components } = toRefs(props)
|
||||
|
||||
// 复制表单项
|
||||
const copyItem = (index: number) => {
|
||||
if (item.value.children) {
|
||||
const copyItem = XEUtils.assign(XEUtils.clone(item.value.children[index], true), { uuid: XEUtils.uniqueId() })
|
||||
if (copyItem.formItem.config?.name)
|
||||
copyItem.formItem.config.name = copyItem.formItem.config.name.split('_')[0] + '_' + XEUtils.uniqueId()
|
||||
// 必须深拷贝,否则对象类型的字段会拷贝引用
|
||||
item.value.children.splice(index + 1, 0, copyItem)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除表单项
|
||||
const deleteItem = (index: number, _item: FormDesignerItem) => {
|
||||
if (item.value.children) {
|
||||
item.value.children.splice(index, 1)
|
||||
console.log(_item) //需要判定删除的组件是不是选中的组件
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
selectedChange: [config?: FormDesignerItem]
|
||||
}>()
|
||||
|
||||
const selectedChange = (_config?: FormDesignerItem) => {
|
||||
emit('selectedChange', _config)
|
||||
}
|
||||
|
||||
const id = computed(() => {
|
||||
return `drag-col-${item.value.uuid}`
|
||||
})
|
||||
nextTick(() => {
|
||||
const dom = document.getElementById(id.value)
|
||||
if (dom) {
|
||||
Sortable.create(dom, {
|
||||
animation: 150,
|
||||
sort: true,
|
||||
group: { name: 'components', pull: true, put: true },
|
||||
onEnd: (evt) => {
|
||||
item.value.children = [...evt.from.children]
|
||||
.filter((c) => (c as HTMLElement).style.display !== 'none')
|
||||
.map((c) => {
|
||||
const id = (c as HTMLElement).dataset.id
|
||||
return (item.value.children ?? []).find((c) => c.uuid === id)
|
||||
}) as FormDesignerItem[]
|
||||
},
|
||||
onAdd: (evt) => {
|
||||
;[...evt.to.children]
|
||||
.filter((item) => item.classList.contains('ant-card-grid'))
|
||||
.forEach((item) => {
|
||||
;(item as HTMLElement).style.display = 'none'
|
||||
}) //原始元素都别给我显示出来了
|
||||
const uuid = evt.clone.dataset.id
|
||||
const type = evt.clone.dataset.type
|
||||
const newItem = type
|
||||
? allComponents.find((item) => item.type === type)
|
||||
: XEUtils.findTree(components.value, (item) => item.uuid === uuid).item
|
||||
|
||||
if (newItem?.formItem.group === 'layout') {
|
||||
notification.warning({
|
||||
message: '提示',
|
||||
description: '暂不支持布局嵌套,请拖入表单组件',
|
||||
duration: 3,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (newItem) {
|
||||
item.value.children?.push(XEUtils.assign(XEUtils.clone(newItem, true), { uuid: XEUtils.uniqueId() }))
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.ant-col,
|
||||
.ant-row {
|
||||
min-height: 50px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<a-card size="small">
|
||||
<template #title>
|
||||
<a-tabs centered size="small">
|
||||
<a-tab-pane key="form" tab="预览区" />
|
||||
</a-tabs>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-space v-if="notEmpty">
|
||||
<a-button size="small" type="primary" @click="generate">
|
||||
<template #icon>
|
||||
<icon-font type="icon-code" />
|
||||
</template>
|
||||
生成
|
||||
</a-button>
|
||||
<a-button size="small" type="primary" @click="preview">
|
||||
<template #icon>
|
||||
<icon-font type="icon-preview" />
|
||||
</template>
|
||||
预览
|
||||
</a-button>
|
||||
<a-popconfirm title="确定清空设计?" ok-text="确定" cancel-text="取消" @confirm="reset">
|
||||
<a-button size="small" type="primary" danger>
|
||||
<template #icon>
|
||||
<icon-font type="icon-reset" />
|
||||
</template>
|
||||
清空
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
<div :style="{ minHeight: 'calc(100vh - 190px)', maxHeight: 'calc(100vh - 190px)', overflowY: 'auto' }">
|
||||
<a-watermark :content="waterMark" :rotate="22" :font="{ color: 'rgba(0,0,0,.3)', fontSize: 20 }">
|
||||
<a-form
|
||||
v-bind="formConfig"
|
||||
ref="formRef"
|
||||
:style="{ '--ant-primary-color': token.colorPrimaryBorder }"
|
||||
@click="selectedChange()"
|
||||
>
|
||||
<component-item
|
||||
v-for="(c, index) in components"
|
||||
:key="c.uuid"
|
||||
:data-id="c.uuid"
|
||||
:item="c"
|
||||
:components="components"
|
||||
:disabled-fields="disabledFields"
|
||||
:selected="selected"
|
||||
@selected="selectedChange"
|
||||
@copy="copyItem(index)"
|
||||
@delete="deleteItem(index, $event)"
|
||||
/>
|
||||
</a-form>
|
||||
</a-watermark>
|
||||
</div>
|
||||
<code-generator-drawer
|
||||
ref="codeGeneratorDrawer"
|
||||
:components="components"
|
||||
:config="formConfig"
|
||||
></code-generator-drawer>
|
||||
<form-preview-drawer ref="formPreviewModal" :components="components" :config="formConfig"></form-preview-drawer>
|
||||
</a-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormDesignerItem } from '../form-designer-types'
|
||||
import Sortable from 'sortablejs'
|
||||
import allComponents from '../component-panel/components'
|
||||
import { FormConfig, FormItem } from '@/components/form-render/form-render-types'
|
||||
import XEUtils from 'xe-utils'
|
||||
import { notification, theme } from 'ant-design-vue'
|
||||
import FormPreviewDrawer from './form-preview-drawer.vue'
|
||||
import CodeGeneratorDrawer from './code-generator-drawer.vue'
|
||||
const { useToken } = theme
|
||||
const { token } = useToken()
|
||||
const props = defineProps({
|
||||
formConfig: {
|
||||
type: Object as PropType<FormConfig>,
|
||||
required: true,
|
||||
},
|
||||
// 选中的组件
|
||||
selected: {
|
||||
type: Object as PropType<FormDesignerItem>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
// 禁用的表单项
|
||||
disabledFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
},
|
||||
},
|
||||
isGenerate: {
|
||||
type: Boolean,
|
||||
default: () => true,
|
||||
},
|
||||
})
|
||||
const { selected } = toRefs(props)
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
return selected?.value?.uuid
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
selectedChange: [config?: FormDesignerItem]
|
||||
'update:form-items': [formItems: Array<FormItem>]
|
||||
}>()
|
||||
|
||||
const selectedChange = (config?: FormDesignerItem) => {
|
||||
emit('selectedChange', config)
|
||||
}
|
||||
|
||||
// 复制表单项
|
||||
const copyItem = (index: number) => {
|
||||
const item = XEUtils.assign(XEUtils.clone(components.value[index], true), { uuid: XEUtils.uniqueId() })
|
||||
if (item.formItem.config?.name)
|
||||
item.formItem.config.name = item.formItem.config.name.split('_')[0] + '_' + XEUtils.uniqueId()
|
||||
// 必须深拷贝,否则对象类型的字段会拷贝引用
|
||||
components.value.splice(index + 1, 0, item)
|
||||
}
|
||||
|
||||
// 删除表单项
|
||||
const deleteItem = (index: number, item: FormDesignerItem) => {
|
||||
components.value.splice(index, 1)
|
||||
if (item.uuid == selectedKey.value) {
|
||||
// 如果删除的是当前选中的组件,则要清理选中组件
|
||||
selectedChange()
|
||||
}
|
||||
}
|
||||
|
||||
const components = ref<Array<FormDesignerItem>>([])
|
||||
const notEmpty = computed<boolean>(() => {
|
||||
return components.value.length > 0
|
||||
})
|
||||
|
||||
const waterMark = computed<string>(() => {
|
||||
return notEmpty.value ? ' ' : '拖动组件到这里'
|
||||
})
|
||||
const codeGeneratorDrawer = ref<typeof CodeGeneratorDrawer>()
|
||||
// 生成
|
||||
const generate = () => {
|
||||
codeGeneratorDrawer.value?.show()
|
||||
}
|
||||
const formPreviewModal = ref<typeof FormPreviewDrawer>()
|
||||
// 预览
|
||||
const preview = () => {
|
||||
formPreviewModal.value?.show()
|
||||
}
|
||||
// 清空
|
||||
const reset = () => {
|
||||
// 清空容器中的组件
|
||||
components.value = []
|
||||
selectedChange()
|
||||
}
|
||||
const sort = ref<Sortable>()
|
||||
nextTick(() => {
|
||||
const form = document.querySelector('.ant-form')
|
||||
// 整体的组件
|
||||
if (form) {
|
||||
sort.value = Sortable.create(form as HTMLElement, {
|
||||
animation: 150,
|
||||
sort: true,
|
||||
group: { name: 'components', pull: true, put: true },
|
||||
onEnd: (evt) => {
|
||||
components.value = [...evt.from.children]
|
||||
.filter((item) => (item as HTMLElement).style.display !== 'none')
|
||||
.map((item) => {
|
||||
const id = (item as HTMLElement).dataset.id
|
||||
return components.value.find((item) => item.uuid === id)
|
||||
}) as FormDesignerItem[]
|
||||
},
|
||||
onAdd: (evt) => {
|
||||
;[...evt.to.children]
|
||||
.filter((item) => item.classList.contains('ant-card-grid'))
|
||||
.forEach((item) => {
|
||||
;(item as HTMLElement).style.display = 'none'
|
||||
})
|
||||
const uuid = evt.clone.dataset.id
|
||||
const type = evt.clone.dataset.type
|
||||
if (type === 'col') {
|
||||
notification.warning({
|
||||
message: '提示',
|
||||
description: '列组件需要放入行组件',
|
||||
duration: 3,
|
||||
})
|
||||
return
|
||||
}
|
||||
const item = type
|
||||
? allComponents.find((item) => item.type === type)
|
||||
: XEUtils.findTree(components.value, (item) => item.uuid === uuid).item
|
||||
if (item) {
|
||||
components.value.push(XEUtils.assign(XEUtils.clone(item, true), { uuid: XEUtils.uniqueId() }))
|
||||
}
|
||||
nextTick(() => {
|
||||
rowDrag()
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
const rowDrag = () => {
|
||||
;[...document.querySelectorAll('.list-group-item .drag-row')].forEach((item) => {
|
||||
Sortable.create(item as HTMLElement, {
|
||||
animation: 150,
|
||||
sort: true,
|
||||
group: { name: 'components', pull: true, put: true },
|
||||
onEnd: (evt) => {
|
||||
const item = components.value.find((a) => a.uuid === evt.to.dataset.id)
|
||||
if (item) {
|
||||
item.children = [...evt.from.children]
|
||||
.filter((c) => (c as HTMLElement).style.display !== 'none')
|
||||
.map((c) => {
|
||||
const id = (c as HTMLElement).dataset.id
|
||||
return (item.children ?? []).find((c) => c.uuid === id)
|
||||
}) as FormDesignerItem[]
|
||||
}
|
||||
},
|
||||
onAdd: (evt) => {
|
||||
;[...evt.to.children]
|
||||
.filter((item) => item.classList.contains('ant-card-grid'))
|
||||
.forEach((item) => {
|
||||
;(item as HTMLElement).style.display = 'none'
|
||||
}) //原始元素都别给我显示出来了
|
||||
|
||||
//获取拖到了哪个行,添加拖入的列
|
||||
const item = components.value.find((a) => a.uuid === evt.to.dataset.id)
|
||||
const type = evt.clone.dataset.type
|
||||
if (!type) {
|
||||
notification.warning({
|
||||
message: '提示',
|
||||
description: '已设置组件放入布局将无法恢复!',
|
||||
duration: 3,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (type !== 'col') {
|
||||
notification.warning({
|
||||
message: '提示',
|
||||
description: '行组件只接受放入列组件',
|
||||
duration: 3,
|
||||
})
|
||||
return
|
||||
}
|
||||
const newItem = allComponents.find((item) => item.type === type) //仅仅找到原始组件
|
||||
if (newItem) {
|
||||
item?.children?.push(XEUtils.assign(XEUtils.clone(newItem, true), { uuid: XEUtils.uniqueId() }))
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.ant-form {
|
||||
height: calc(100vh - 190px);
|
||||
padding: 15px;
|
||||
overflow-x: hidden;
|
||||
background-color: #fff;
|
||||
border: 1px dashed var(--ant-primary-color);
|
||||
}
|
||||
|
||||
.ant-pro-pro-layout-watermark-wrapper {
|
||||
min-height: calc(100vh - 190px) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<a-badge-ribbon color="volcano">
|
||||
<template #text>
|
||||
<a-space>
|
||||
<icon-font type="icon-copy" @click.stop="onCopy" />
|
||||
<icon-font type="icon-delete" style="color: #fff" @click.stop="onDelete" />
|
||||
</a-space>
|
||||
</template>
|
||||
<div
|
||||
:style="{ '--ant-primary-color': token.colorPrimaryBorder }"
|
||||
:class="`list-group-item list-group-item-masked designer-item ${
|
||||
selected?.uuid === item.uuid ? 'designer-item-active' : ''
|
||||
}`"
|
||||
@click.stop="onClick"
|
||||
>
|
||||
<form-item-render v-if="item.formItem?.group === 'form'" :item="item.formItem" :disabled="true" />
|
||||
<auxiliary-item-render
|
||||
v-else-if="item.formItem?.group === 'auxiliary'"
|
||||
:item="item.formItem"
|
||||
></auxiliary-item-render>
|
||||
<row-component-item
|
||||
v-else-if="item.formItem.group === 'layout' && item.formItem.type === FormItemTypeEnum.ROW"
|
||||
:data-id="item.uuid"
|
||||
:item="XEUtils.assign(item.formItem, { children: item.children?.map((item) => item.formItem) })"
|
||||
>
|
||||
<template v-if="item.children">
|
||||
<col-component-item
|
||||
v-for="(c, index) in item.children"
|
||||
:key="index"
|
||||
:item="c"
|
||||
:components="components"
|
||||
:style="{
|
||||
borderStyle: selected && selected.uuid === c.uuid ? 'solid' : 'dashed',
|
||||
borderWidth: selected && selected.uuid === c.uuid ? '2px' : '1px',
|
||||
borderColor: token.colorPrimaryBorder,
|
||||
}"
|
||||
:selected="selected"
|
||||
@click.stop="emit('selected', c)"
|
||||
@selected-change="(_c?: FormDesignerItem) => emit('selected', _c)"
|
||||
></col-component-item>
|
||||
</template>
|
||||
</row-component-item>
|
||||
</div>
|
||||
</a-badge-ribbon>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import { FormDesignerItem } from '../form-designer-types'
|
||||
import { theme } from 'ant-design-vue'
|
||||
import { FormItemTypeEnum } from '@/components/form-render/form-render-types'
|
||||
import XEUtils from 'xe-utils'
|
||||
const { useToken } = theme
|
||||
const { token } = useToken()
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<FormDesignerItem>,
|
||||
required: true,
|
||||
},
|
||||
selected: {
|
||||
type: Object as PropType<FormDesignerItem>,
|
||||
required: false,
|
||||
default: () => null,
|
||||
},
|
||||
components: {
|
||||
type: Array as PropType<Array<FormDesignerItem>>,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const { item, selected, components } = toRefs(props)
|
||||
|
||||
watch(
|
||||
selected,
|
||||
(v) => {
|
||||
if (v && item.value.uuid !== v.uuid && v.formItem.type === FormItemTypeEnum.COL) {
|
||||
//嵌套的列
|
||||
if (item.value.children) {
|
||||
const index = item.value.children?.findIndex((i) => i.uuid === v.uuid)
|
||||
item.value.children.splice(index, 1, v)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
const emit = defineEmits<{
|
||||
selected: [itemConfig?: FormDesignerItem]
|
||||
copy: [itemConfig: FormDesignerItem]
|
||||
delete: [itemConfig: FormDesignerItem]
|
||||
}>()
|
||||
|
||||
const onClick = () => {
|
||||
emit('selected', item.value)
|
||||
}
|
||||
const onCopy = () => {
|
||||
emit('copy', item.value)
|
||||
}
|
||||
const onDelete = () => {
|
||||
emit('delete', item.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.designer-item {
|
||||
padding: 10px 50px 10px 10px;
|
||||
cursor: pointer;
|
||||
border: 1px dashed var(--ant-primary-color);
|
||||
}
|
||||
|
||||
.designer-item-active {
|
||||
border: 2px solid var(--ant-primary-color);
|
||||
}
|
||||
|
||||
.list-group-item-masked {
|
||||
z-index: 3000;
|
||||
background: rgb(238 236 236 / 30%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<a-drawer v-model:open="visible" title="表单预览" width="50%" :closable="false">
|
||||
<form-render v-model="formData" :items="items" :config="config"></form-render>
|
||||
<template #extra>
|
||||
<a-button type="primary" size="small" @click="handleOk">
|
||||
<template #icon>
|
||||
<icon-font type="icon-submit"></icon-font>
|
||||
</template>
|
||||
确定
|
||||
</a-button>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormConfig } from '@/components/form-render/form-render-types'
|
||||
import { FormDesignerItem } from '../form-designer-types'
|
||||
import { PropType, computed, ref, toRefs } from 'vue'
|
||||
import { notification } from 'ant-design-vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
const visible = ref<boolean>(false)
|
||||
const show = () => {
|
||||
visible.value = true
|
||||
formData.value = {}
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
|
||||
const handleOk = () => {
|
||||
notification.open({
|
||||
message: '表单数据',
|
||||
description: JSON.stringify(formData.value),
|
||||
duration: 0,
|
||||
})
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
components: {
|
||||
type: Array as PropType<FormDesignerItem[]>,
|
||||
required: true,
|
||||
},
|
||||
config: {
|
||||
type: Object as PropType<FormConfig>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const { components, config } = toRefs(props)
|
||||
|
||||
const items = computed(() => {
|
||||
return XEUtils.mapTree(components.value, (item) => item.formItem)
|
||||
})
|
||||
|
||||
const formData = ref({})
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<a-row
|
||||
class="drag-row"
|
||||
v-bind="Object.assign({}, $attrs, item.properties)"
|
||||
:style="{ '--ant-primary-color': token.colorPrimaryBorder }"
|
||||
>
|
||||
<slot></slot>
|
||||
</a-row>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormItem } from '@/components/form-render/form-render-types'
|
||||
import { theme } from 'ant-design-vue'
|
||||
const { useToken } = theme
|
||||
const { token } = useToken()
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<FormItem>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const { item } = toRefs(props)
|
||||
</script>
|
||||
<style scoped>
|
||||
.ant-row {
|
||||
min-height: 50px;
|
||||
border: 1px dashed var(--ant-primary-color);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<a-card size="small">
|
||||
<template #title>
|
||||
<a-tabs centered size="small">
|
||||
<a-tab-pane key="form" tab="组件区" />
|
||||
</a-tabs>
|
||||
</template>
|
||||
<div :style="{ minHeight: 'calc(100vh - 190px)', maxHeight: 'calc(100vh - 190px)', overflowY: 'auto' }">
|
||||
<a-card id="form-items" title="表单组件" size="small">
|
||||
<a-card-grid
|
||||
v-for="element in formComponents"
|
||||
:key="element.uuid"
|
||||
:data-id="element.uuid"
|
||||
:data-type="element.type"
|
||||
class="list-group-item"
|
||||
style="width: 25%; padding: 0 5px; text-align: center"
|
||||
>
|
||||
<input-item :name="element.name" :icon="element.icon" />
|
||||
</a-card-grid>
|
||||
</a-card>
|
||||
<a-card id="auxiliary-items" title="辅助组件" size="small">
|
||||
<a-card-grid
|
||||
v-for="element in auxiliaryComponents"
|
||||
:key="element.uuid"
|
||||
:data-id="element.uuid"
|
||||
:data-type="element.type"
|
||||
class="list-group-item"
|
||||
style="width: 25%; padding: 0 5px; text-align: center"
|
||||
>
|
||||
<input-item :name="element.name" :icon="element.icon" />
|
||||
</a-card-grid>
|
||||
</a-card>
|
||||
<a-card id="layout-items" title="布局组件" size="small">
|
||||
<a-card-grid
|
||||
v-for="element in layoutComponents"
|
||||
:key="element.uuid"
|
||||
:data-id="element.uuid"
|
||||
:data-type="element.type"
|
||||
class="list-group-item"
|
||||
style="width: 25%; padding: 0 5px; text-align: center"
|
||||
>
|
||||
<input-item :name="element.name" :icon="element.icon" />
|
||||
</a-card-grid>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import components from './components'
|
||||
import Sortable, { Options } from 'sortablejs'
|
||||
|
||||
const formComponents = computed(() => {
|
||||
return components.filter((item) => item.formItem?.group === 'form')
|
||||
})
|
||||
const auxiliaryComponents = computed(() => {
|
||||
return components.filter((item) => item.formItem?.group === 'auxiliary')
|
||||
})
|
||||
|
||||
const layoutComponents = computed(() => {
|
||||
return components.filter((item) => item.formItem?.group === 'layout')
|
||||
})
|
||||
|
||||
const auxiliarySort = ref<Sortable>()
|
||||
const formSort = ref<Sortable>()
|
||||
const layoutSort = ref<Sortable>()
|
||||
|
||||
const dragOptions = (): Options => {
|
||||
return {
|
||||
animation: 150,
|
||||
sort: false,
|
||||
group: { name: 'components', pull: 'clone', put: false },
|
||||
}
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
const formElement = document.querySelector('#form-items .ant-card-body')
|
||||
if (formElement) {
|
||||
formSort.value = Sortable.create(formElement as HTMLElement, dragOptions())
|
||||
}
|
||||
|
||||
const auxiliaryElement = document.querySelector('#auxiliary-items .ant-card-body')
|
||||
if (auxiliaryElement) {
|
||||
auxiliarySort.value = Sortable.create(auxiliaryElement as HTMLElement, dragOptions())
|
||||
}
|
||||
|
||||
const layoutElement = document.querySelector('#layout-items .ant-card-body')
|
||||
if (layoutElement) {
|
||||
layoutSort.value = Sortable.create(layoutElement as HTMLElement, dragOptions())
|
||||
}
|
||||
})
|
||||
</script>
|
||||
4703
src/components/form-designer/component-panel/components.ts
Normal file
23
src/components/form-designer/component-panel/input-item.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<a-space direction="vertical" style="margin-top: 10px">
|
||||
<a-avatar shape="square">
|
||||
<template #icon>
|
||||
<icon-font :type="icon"></icon-font>
|
||||
</template>
|
||||
</a-avatar>
|
||||
<a-typography-paragraph>{{ name }}</a-typography-paragraph>
|
||||
</a-space>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const { icon, name } = toRefs(props)
|
||||
</script>
|
||||
107
src/components/form-designer/config-panel/config-panel.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<a-card size="small">
|
||||
<template #title>
|
||||
<a-tabs v-model:active-key="activeKey" centered size="small">
|
||||
<a-tab-pane v-if="currentItem.uuid" key="item" tab="组件配置" />
|
||||
<a-tab-pane key="form" tab="表单配置" />
|
||||
</a-tabs>
|
||||
</template>
|
||||
<div :style="{ minHeight: 'calc(100vh - 190px)', maxHeight: 'calc(100vh - 190px)', overflowY: 'auto' }">
|
||||
<div v-if="activeKey === 'form'">
|
||||
<form-config-panel :form-config="formConfig" @update:form-config="onUpdateFormConfig"></form-config-panel>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-tabs v-model:active-key="currentSection" size="small" animated>
|
||||
<a-tab-pane v-if="currentItem.formItem?.group === 'form'" key="config" tab="组件配置">
|
||||
<form-item-config-panel
|
||||
:form-item="currentItem.formItem"
|
||||
:form-config="formConfig"
|
||||
@update:form-item-config="onUpdateFormItemConfig"
|
||||
></form-item-config-panel>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="attribute" tab="组件属性">
|
||||
<property-config :form-item="currentItem.formItem" @update:form-item="onUpdateFormItem"></property-config>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane v-if="currentItem.formItem?.group === 'form'" key="rule" tab="验证规则">
|
||||
<validate-rule-config
|
||||
:rules="currentItem.formItem.rules"
|
||||
@update:rules="onUpdateRules"
|
||||
></validate-rule-config>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormConfig, FormItem, FormItemConfig } from '@/components/form-render/form-render-types'
|
||||
import { FormDesignerItem } from '../form-designer-types'
|
||||
import XEUtils from 'xe-utils'
|
||||
import { Rule } from 'ant-design-vue/es/form'
|
||||
|
||||
const props = defineProps({
|
||||
currentItem: {
|
||||
type: Object as PropType<FormDesignerItem>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
formConfig: {
|
||||
type: Object as PropType<FormConfig>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { currentItem, formConfig } = toRefs(props)
|
||||
const emit = defineEmits<{
|
||||
'update:form-item': [formItem: FormDesignerItem]
|
||||
'update:form-config': [formConfig: FormConfig]
|
||||
}>()
|
||||
|
||||
const activeKey = ref('form')
|
||||
const currentSection = ref('config')
|
||||
watch(
|
||||
currentItem,
|
||||
(newVal) => {
|
||||
activeKey.value = newVal.uuid ? 'item' : 'form'
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
const itemType = computed(() => {
|
||||
return currentItem.value.formItem.group
|
||||
})
|
||||
|
||||
watch(
|
||||
itemType,
|
||||
(v) => {
|
||||
currentSection.value = v === 'form' ? 'config' : 'attribute'
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
const onUpdateFormConfig = (newFormConfig: FormConfig) => {
|
||||
emit('update:form-config', newFormConfig)
|
||||
}
|
||||
const onUpdateFormItemConfig = (_formItemConfig: FormItemConfig) => {
|
||||
emit(
|
||||
'update:form-item',
|
||||
XEUtils.assign(currentItem.value, {
|
||||
formItem: XEUtils.assign(currentItem.value.formItem, { config: _formItemConfig }),
|
||||
}),
|
||||
)
|
||||
}
|
||||
const onUpdateFormItem = (formItem: FormItem) => {
|
||||
emit('update:form-item', XEUtils.assign(currentItem.value, { formItem }))
|
||||
}
|
||||
|
||||
const onUpdateRules = (_rules: Rule[]) => {
|
||||
emit(
|
||||
'update:form-item',
|
||||
XEUtils.assign(currentItem.value, {
|
||||
formItem: XEUtils.assign(currentItem.value.formItem, { rules: _rules }),
|
||||
}),
|
||||
)
|
||||
}
|
||||
</script>
|
||||
187
src/components/form-designer/config-panel/data-source-table.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<a-table size="small" :data-source="shownDataSource" :columns="columns" :pagination="false" row-key="uuid">
|
||||
<template #title>
|
||||
<a-button type="primary" size="small" @click="add">
|
||||
<template #icon>
|
||||
<icon-font type="icon-plus"></icon-font>
|
||||
</template>
|
||||
添加
|
||||
</a-button>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'ops'">
|
||||
<a-space>
|
||||
<a-button type="primary" size="small" @click="edit(record as DataSource)">
|
||||
<template #icon>
|
||||
<icon-font type="icon-edit"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
<a-popconfirm title="确定删除这个选项?" @confirm="deleteDataSource(record as DataSource)">
|
||||
<a-button type="primary" size="small" danger>
|
||||
<template #icon>
|
||||
<icon-font type="icon-delete"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<form-modal ref="formModal" v-model="data" :form-items="formItems" :title="title" @ok="hanldOk"></form-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import FormModal from '@/components/form-render/form-modal.vue'
|
||||
import { DataSource, FormDataType, FormItem, FormItemTypeEnum } from '@/components/form-render/form-render-types'
|
||||
import { PropType } from 'vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
const props = defineProps({
|
||||
dataSource: {
|
||||
type: Array as PropType<Array<DataSource>>,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const { dataSource } = toRefs(props)
|
||||
|
||||
const shownDataSource = computed(() => {
|
||||
return XEUtils.mapTree(XEUtils.clone(dataSource.value, true), (item) => {
|
||||
return XEUtils.assign(XEUtils.clone(item, true), { uuid: XEUtils.uniqueId() })
|
||||
})
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '键',
|
||||
dataIndex: 'key',
|
||||
key: 'key',
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'ops',
|
||||
width: 100,
|
||||
},
|
||||
]
|
||||
|
||||
const data = ref<Partial<DataSource>>({})
|
||||
const isAdd = ref(true)
|
||||
const title = computed(() => {
|
||||
return isAdd.value ? '添加数据源' : '编辑数据源'
|
||||
})
|
||||
const formItems = reactive<FormItem[]>([
|
||||
{
|
||||
group: 'form',
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '键',
|
||||
name: 'key',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入Key',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
group: 'form',
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '标题',
|
||||
name: 'title',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入标题',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.TEXTAREA,
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '描述',
|
||||
name: 'description',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
visibilityToggle: true,
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
autoSize: {
|
||||
minRows: 2,
|
||||
maxRows: 6,
|
||||
},
|
||||
minRows: 2,
|
||||
maxRows: 6,
|
||||
placeholder: '请输入描述',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
])
|
||||
|
||||
const formModal = ref<typeof FormModal>()
|
||||
|
||||
const add = () => {
|
||||
data.value = {}
|
||||
isAdd.value = true
|
||||
formModal.value?.open()
|
||||
}
|
||||
|
||||
const edit = (_dataSource: DataSource) => {
|
||||
data.value = _dataSource
|
||||
isAdd.value = false
|
||||
formModal.value?.open()
|
||||
}
|
||||
const emit = defineEmits<{
|
||||
'update:data-source': [value: DataSource[]]
|
||||
}>()
|
||||
|
||||
const deleteDataSource = (_dataSource: DataSource & { uuid?: string }) => {
|
||||
emit(
|
||||
'update:data-source',
|
||||
XEUtils.filter(shownDataSource.value, (item) => item.uuid !== _dataSource.uuid),
|
||||
)
|
||||
}
|
||||
|
||||
const hanldOk = (_data: FormDataType) => {
|
||||
let d = XEUtils.clone(shownDataSource.value, true)
|
||||
if (_data.uuid) {
|
||||
d = XEUtils.map(d, (item: DataSource & { uuid: string }) => {
|
||||
if (item.uuid === _data.uuid) {
|
||||
return _data as DataSource & { uuid: string }
|
||||
}
|
||||
return item
|
||||
})
|
||||
} else {
|
||||
d.push(_data as DataSource & { uuid: string })
|
||||
}
|
||||
emit('update:data-source', d)
|
||||
formModal.value?.close()
|
||||
}
|
||||
</script>
|
||||
222
src/components/form-designer/config-panel/form-config-panel.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<form-render
|
||||
v-model="formData"
|
||||
:config="{
|
||||
layout: 'vertical',
|
||||
colon: true,
|
||||
hideRequiredMark: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'fixed',
|
||||
scrollToFirstError: false,
|
||||
validateOnRuleChange: true,
|
||||
labelCol: {},
|
||||
}"
|
||||
:hidden-fields="hiddenFields"
|
||||
:items="formItems"
|
||||
></form-render>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormConfig, FormItem, FormItemTypeEnum } from '@/components/form-render/form-render-types'
|
||||
import { PropType, computed, ref, toRefs } from 'vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:form-config': [formConfig: FormConfig]
|
||||
}>()
|
||||
|
||||
const props = defineProps({
|
||||
formConfig: {
|
||||
type: Object as PropType<FormConfig>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const { formConfig } = toRefs(props)
|
||||
const formItems = ref<FormItem[]>([
|
||||
{
|
||||
type: FormItemTypeEnum.RADIO,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '布局形式',
|
||||
name: 'layout',
|
||||
},
|
||||
properties: {
|
||||
optionType: 'button',
|
||||
buttonStyle: 'solid',
|
||||
size: 'small',
|
||||
options: [
|
||||
{
|
||||
label: '水平',
|
||||
value: 'horizontal',
|
||||
},
|
||||
{
|
||||
label: '垂直',
|
||||
value: 'vertical',
|
||||
},
|
||||
{
|
||||
label: '行内',
|
||||
value: 'inline',
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '显示冒号',
|
||||
name: 'colon',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
checkedChildren: '显示',
|
||||
unCheckedChildren: '隐藏',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '隐藏必选标记',
|
||||
name: 'hideRequiredMark',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
checkedChildren: '隐藏',
|
||||
unCheckedChildren: '显示',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.RADIO,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签对齐方式',
|
||||
name: 'labelAlign',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
optionType: 'button',
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{
|
||||
label: '左对齐',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: '右对齐',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.RADIO,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签模式',
|
||||
name: 'labelType',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
optionType: 'button',
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{
|
||||
label: '固定宽度',
|
||||
value: 'fixed',
|
||||
},
|
||||
{
|
||||
label: '响应式',
|
||||
value: 'responsive',
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT_NUMBER,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签宽度',
|
||||
name: 'labelColFixedWidthInPx',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
addonAfter: 'px',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT_NUMBER,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签宽度',
|
||||
name: 'labelColSpanWidth',
|
||||
},
|
||||
properties: {
|
||||
placeholder: '请输入标签宽度',
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
addonBefore: 'span',
|
||||
max: 12,
|
||||
min: 1,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '提交失败自动滚动到第一个错误字段 ',
|
||||
name: 'scrollToFirstError',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '是否在 rules 属性改变后立即触发一次验证 ',
|
||||
name: 'validateOnRuleChange',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
])
|
||||
|
||||
const hiddenFields = computed(() => {
|
||||
return [
|
||||
...(formConfig.value.layout === 'horizontal' ? [] : ['colon']),
|
||||
...(formConfig.value.labelType === 'fixed' ? ['labelColSpanWidth'] : ['labelColFixedWidthInPx']),
|
||||
]
|
||||
})
|
||||
|
||||
const formData = computed({
|
||||
get: () => {
|
||||
return formConfig.value
|
||||
},
|
||||
set: (val) => {
|
||||
if (val.labelType === 'fixed') {
|
||||
val.labelCol = { style: { width: `${val.labelColFixedWidthInPx}px` } }
|
||||
} else {
|
||||
val.labelCol = { span: val.labelColSpanWidth }
|
||||
}
|
||||
emit('update:form-config', val)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<form-render
|
||||
v-model="formData"
|
||||
:config="{
|
||||
layout: 'vertical',
|
||||
colon: true,
|
||||
hideRequiredMark: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'fixed',
|
||||
scrollToFirstError: false,
|
||||
validateOnRuleChange: true,
|
||||
labelCol: {},
|
||||
}"
|
||||
:hidden-fields="hiddenFields"
|
||||
:items="formItems"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import XEUtils from 'xe-utils'
|
||||
import { PropType } from 'vue'
|
||||
import { FormConfig, FormItem, FormItemConfig, FormItemTypeEnum } from '@/components/form-render/form-render-types'
|
||||
const props = defineProps({
|
||||
formItem: {
|
||||
type: Object as PropType<FormItem>,
|
||||
required: true,
|
||||
},
|
||||
formConfig: {
|
||||
type: Object as PropType<FormConfig>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
const { formItem, formConfig } = toRefs(props)
|
||||
|
||||
const formItems = ref<FormItem[]>([
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '表单域',
|
||||
name: 'name',
|
||||
},
|
||||
properties: {
|
||||
placeholder: '请输入表单域',
|
||||
disabled: false,
|
||||
required: true,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签文本',
|
||||
name: 'label',
|
||||
},
|
||||
properties: { placeholder: '请输入标签文本' },
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '是否必填',
|
||||
name: 'required',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '使用表单配置',
|
||||
name: 'useFormConfig',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.CHECKBOX,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '字段校验的时机',
|
||||
name: 'validateTrigger',
|
||||
},
|
||||
properties: {
|
||||
options: [
|
||||
{
|
||||
label: '失去焦点',
|
||||
value: 'blur',
|
||||
},
|
||||
{
|
||||
label: '值变化',
|
||||
value: 'change',
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '检验不通过停止校验下一项',
|
||||
name: 'validateFirst',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '展示校验状态图标',
|
||||
name: 'hasFeedback',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.RADIO,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '校验状态',
|
||||
name: 'validateStatus',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
optionType: 'button',
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{
|
||||
label: '成功',
|
||||
value: 'success',
|
||||
},
|
||||
{
|
||||
label: '警告',
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
label: '错误',
|
||||
value: 'error',
|
||||
},
|
||||
{
|
||||
label: '验证中',
|
||||
value: 'validating',
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '提示信息',
|
||||
name: 'help',
|
||||
},
|
||||
properties: {
|
||||
disabled: false,
|
||||
placeholder: '请输入提示信息',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '额外的提示信息',
|
||||
name: 'extra',
|
||||
},
|
||||
properties: {
|
||||
disabled: false,
|
||||
placeholder: '请输入额外的提示信息',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '显示冒号',
|
||||
name: 'colon',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
checkedChildren: '显示',
|
||||
unCheckedChildren: '隐藏',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.SWITCH,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '是否自动关联表单域',
|
||||
name: 'autoLink',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '子元素 label htmlFor 属性',
|
||||
name: 'htmlFor',
|
||||
},
|
||||
properties: {
|
||||
disabled: false,
|
||||
placeholder: '请输入子元素 label htmlFor 属性',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.RADIO,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签对齐方式',
|
||||
name: 'labelAlign',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
optionType: 'button',
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{
|
||||
label: '左对齐',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
label: '右对齐',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.RADIO,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签模式',
|
||||
name: 'labelType',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
optionType: 'button',
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{
|
||||
label: '固定宽度',
|
||||
value: 'fixed',
|
||||
},
|
||||
{
|
||||
label: '响应式',
|
||||
value: 'responsive',
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT_NUMBER,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签宽度',
|
||||
name: 'labelColFixedWidthInPx',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
placeholder: '请输入标签宽度,单位(px)',
|
||||
addonAfter: 'px',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: FormItemTypeEnum.INPUT_NUMBER,
|
||||
group: 'form',
|
||||
config: {
|
||||
label: '标签宽度',
|
||||
name: 'labelColSpanWidth',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
addonBefore: 'span',
|
||||
placeholder: '请输入标签宽度.响应式span数值',
|
||||
max: 12,
|
||||
min: 1,
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
])
|
||||
|
||||
const hiddenFields = computed(() => {
|
||||
return [
|
||||
...(formData.value.useFormConfig
|
||||
? ['validateTrigger', 'colon', 'labelAlign', 'labelType', 'labelColSpanWidth', 'labelColFixedWidthInPx']
|
||||
: []),
|
||||
...(formData.value.labelType === 'fixed' ? ['labelColSpanWidth'] : ['labelColFixedWidthInPx']),
|
||||
]
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:form-item-config': [formConfig: FormItemConfig]
|
||||
}>()
|
||||
const formData = computed({
|
||||
get: (): FormItemConfig => {
|
||||
return XEUtils.assign(
|
||||
{
|
||||
autoLink: true,
|
||||
colon: true,
|
||||
hasFeedback: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'responsive',
|
||||
useFormConfig: true,
|
||||
validateTrigger: ['blur'],
|
||||
labelCol: {
|
||||
style: {
|
||||
width: '120px',
|
||||
},
|
||||
span: 4,
|
||||
offset: 0,
|
||||
},
|
||||
labelColFixedWidthInPx: 120,
|
||||
labelColSpanWidth: 4,
|
||||
},
|
||||
XEUtils.clone(formConfig.value, true),
|
||||
formItem.value.config,
|
||||
)
|
||||
},
|
||||
set: (val) => {
|
||||
if (val.labelType === 'fixed') {
|
||||
val.labelCol = { style: { width: `${val.labelColFixedWidthInPx}px` } }
|
||||
} else {
|
||||
val.labelCol = { span: val.labelColSpanWidth }
|
||||
}
|
||||
if (val.useFormConfig) {
|
||||
delete val.validateTrigger
|
||||
delete val.colon
|
||||
delete val.labelAlign
|
||||
delete val.labelType
|
||||
delete val.labelColFixedWidthInPx
|
||||
delete val.labelColSpanWidth
|
||||
delete val.labelCol
|
||||
}
|
||||
delete (val as FormConfig).layout
|
||||
delete (val as FormConfig).hideRequiredMark
|
||||
delete (val as FormConfig).scrollToFirstError
|
||||
delete (val as FormConfig).validateOnRuleChange
|
||||
emit('update:form-item-config', val)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
188
src/components/form-designer/config-panel/option-table.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<a-table size="small" :data-source="shownOptions" :columns="columns" :pagination="false" row-key="uuid">
|
||||
<template #title>
|
||||
<a-button type="primary" size="small" @click="addOption">
|
||||
<template #icon>
|
||||
<icon-font type="icon-plus"></icon-font>
|
||||
</template>
|
||||
添加
|
||||
</a-button>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'ops'">
|
||||
<a-space>
|
||||
<a-button type="primary" size="small" @click="editOption(record)">
|
||||
<template #icon>
|
||||
<icon-font type="icon-edit"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button v-if="tree" type="primary" size="small" @click="addClildOption(record)">
|
||||
<template #icon>
|
||||
<icon-font type="icon-tree"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
<a-popconfirm title="确定删除这个选项?" @confirm="deleteOption(record)">
|
||||
<a-button type="primary" size="small" danger>
|
||||
<template #icon>
|
||||
<icon-font type="icon-delete"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<form-modal ref="formModal" v-model="option" :form-items="formItems" :title="title" @ok="hanldOk"></form-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import FormModal from '@/components/form-render/form-modal.vue'
|
||||
import { FormDataType, FormItem, FormItemTypeEnum, Option } from '@/components/form-render/form-render-types'
|
||||
import { PropType } from 'vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array as PropType<Option[]>,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
tree: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: () => false,
|
||||
},
|
||||
})
|
||||
const { options, tree } = toRefs(props)
|
||||
const columns = [
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'label',
|
||||
key: 'label',
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'ops',
|
||||
width: 100,
|
||||
},
|
||||
]
|
||||
const emit = defineEmits<{
|
||||
'update:options': [value: Option[]]
|
||||
}>()
|
||||
|
||||
const shownOptions = computed(() => {
|
||||
return XEUtils.mapTree(XEUtils.clone(options.value, true), (item) => {
|
||||
return XEUtils.assign(XEUtils.clone(item, true), { uuid: XEUtils.uniqueId() })
|
||||
})
|
||||
})
|
||||
|
||||
const formItems = reactive<FormItem[]>([
|
||||
{
|
||||
group: 'form',
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '标签',
|
||||
name: 'label',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'default',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入选项标签',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
group: 'form',
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '值',
|
||||
name: 'value',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'default',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入选项值',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
])
|
||||
const selected = ref<Partial<Option & { uuid: string }>>({})
|
||||
const option = ref({})
|
||||
const formModal = ref<typeof FormModal>()
|
||||
const title = ref('添加选项')
|
||||
const addClildOption = (_option: Option) => {
|
||||
title.value = '添加子选项'
|
||||
option.value = {}
|
||||
selected.value = _option
|
||||
formModal.value?.open()
|
||||
}
|
||||
|
||||
const addOption = () => {
|
||||
option.value = {}
|
||||
selected.value = {}
|
||||
title.value = '添加选项'
|
||||
formModal.value?.open()
|
||||
}
|
||||
|
||||
const editOption = (_option: Option) => {
|
||||
selected.value = {}
|
||||
option.value = _option
|
||||
title.value = '编辑选项'
|
||||
formModal.value?.open()
|
||||
}
|
||||
const deleteOption = (_option: Option) => {
|
||||
emit(
|
||||
'update:options',
|
||||
XEUtils.mapTree(
|
||||
XEUtils.searchTree(shownOptions.value, (item) => item.uuid !== _option.uuid),
|
||||
(item) => {
|
||||
if (item.children && (item.children as Array<unknown>).length === 0) {
|
||||
delete item.children
|
||||
}
|
||||
return item
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
const hanldOk = (_option: FormDataType) => {
|
||||
let options = XEUtils.clone(shownOptions.value, true)
|
||||
if (_option.uuid) {
|
||||
options = XEUtils.mapTree(options, (item) => {
|
||||
if (item.uuid === _option.uuid) {
|
||||
item = _option as Option & { uuid: string }
|
||||
}
|
||||
return item
|
||||
})
|
||||
} else if (!selected.value.uuid) {
|
||||
options.push(_option as Option & { uuid: string })
|
||||
} else {
|
||||
options = XEUtils.mapTree(options, (item) => {
|
||||
if (item.uuid === selected.value.uuid) {
|
||||
if (item.children) {
|
||||
item.children = (item.children as Array<unknown>).concat([_option])
|
||||
} else {
|
||||
item.children = [_option]
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
emit('update:options', options)
|
||||
formModal.value?.close()
|
||||
}
|
||||
</script>
|
||||
108
src/components/form-designer/config-panel/property-config.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div>
|
||||
<form-render
|
||||
v-model="formData"
|
||||
:config="{
|
||||
layout: 'vertical',
|
||||
colon: true,
|
||||
hideRequiredMark: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'fixed',
|
||||
scrollToFirstError: false,
|
||||
validateOnRuleChange: true,
|
||||
labelCol: {},
|
||||
}"
|
||||
:hidden-fields="hiddenFields"
|
||||
:items="formItems"
|
||||
>
|
||||
<template v-if="formItem.type === FormItemTypeEnum.TRANSFER" #before-oneWay>
|
||||
<a-form-item label="数据源">
|
||||
<data-source-table
|
||||
:data-source="formItem.properties.dataSource || []"
|
||||
@update:data-source="dataSourceChange"
|
||||
></data-source-table>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="formItem.type === FormItemTypeEnum.TREE_SELECT" #after-size>
|
||||
<a-form-item label="数据源">
|
||||
<tree-data-table
|
||||
:tree-data="formItem.properties.treeData || []"
|
||||
@update:tree-data="treeDataChange"
|
||||
></tree-data-table>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</form-render>
|
||||
<option-table
|
||||
v-if="showOptionsTable"
|
||||
:options="formItem.properties.options"
|
||||
:tree="tree"
|
||||
@update:options="optionsChange"
|
||||
></option-table>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DataSource, FormItem, FormItemTypeEnum, Option } from '@/components/form-render/form-render-types'
|
||||
import { TreeDataNode } from 'ant-design-vue/es/vc-tree-select/interface'
|
||||
|
||||
const props = defineProps({
|
||||
formItem: {
|
||||
type: Object as PropType<FormItem>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const { formItem } = toRefs(props)
|
||||
|
||||
const tree = computed(() => {
|
||||
return formItem.value.type === FormItemTypeEnum.CASCADER
|
||||
})
|
||||
|
||||
const showOptionsTable = computed(() => {
|
||||
return (
|
||||
formItem.value.type === FormItemTypeEnum.RADIO ||
|
||||
formItem.value.type === FormItemTypeEnum.SELECT ||
|
||||
formItem.value.type === FormItemTypeEnum.AUTO_COMPLETE ||
|
||||
formItem.value.type === FormItemTypeEnum.MENTION ||
|
||||
formItem.value.type === FormItemTypeEnum.CHECKBOX ||
|
||||
formItem.value.type === FormItemTypeEnum.CASCADER
|
||||
)
|
||||
})
|
||||
const formItems = computed(() => {
|
||||
return formItem.value.propertyConfigFormComponents ?? []
|
||||
})
|
||||
|
||||
const hiddenFields = computed(() => {
|
||||
if (formItem.value.hiddenFieldsFun) {
|
||||
return formItem.value.hiddenFieldsFun(formData.value)
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:form-item': [item: FormItem]
|
||||
}>()
|
||||
|
||||
const optionsChange = (_options: Option[]) => {
|
||||
formItem.value.properties.options = _options
|
||||
emit('update:form-item', formItem.value)
|
||||
}
|
||||
|
||||
const dataSourceChange = (_dataSource: DataSource[]) => {
|
||||
formItem.value.properties.dataSource = _dataSource
|
||||
emit('update:form-item', formItem.value)
|
||||
}
|
||||
|
||||
const treeDataChange = (_treeData: TreeDataNode[]) => {
|
||||
formItem.value.properties.treeData = _treeData
|
||||
emit('update:form-item', formItem.value)
|
||||
}
|
||||
|
||||
const formData = computed({
|
||||
get: () => {
|
||||
return formItem.value.properties
|
||||
},
|
||||
set: (val) => {
|
||||
formItem.value.properties = val
|
||||
emit('update:form-item', formItem.value)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
194
src/components/form-designer/config-panel/tree-data-table.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<a-table size="small" :data-source="shownTreeData" :columns="columns" :pagination="false" row-key="uuid">
|
||||
<template #title>
|
||||
<a-button type="primary" size="small" @click="add">
|
||||
<template #icon>
|
||||
<icon-font type="icon-plus"></icon-font>
|
||||
</template>
|
||||
添加
|
||||
</a-button>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'ops'">
|
||||
<a-space>
|
||||
<a-button type="primary" size="small" @click="edit(record as DataSource)">
|
||||
<template #icon>
|
||||
<icon-font type="icon-edit"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button type="primary" size="small" @click="addChild(record as DataSource)">
|
||||
<template #icon>
|
||||
<icon-font type="icon-tree"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
<a-popconfirm title="确定删除这个选项?" @confirm="deleteData(record as DataSource)">
|
||||
<a-button type="primary" size="small" danger>
|
||||
<template #icon>
|
||||
<icon-font type="icon-delete"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<form-modal ref="formModal" v-model="data" :form-items="formItems" :title="title" @ok="hanldOk"></form-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DataSource, FormDataType, FormItem, FormItemTypeEnum } from '@/components/form-render/form-render-types'
|
||||
import { TreeDataNode } from 'ant-design-vue/es/vc-tree-select/interface'
|
||||
import FormModal from '@/components/form-render/form-modal.vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
const props = defineProps({
|
||||
treeData: {
|
||||
type: Array as PropType<Array<TreeDataNode>>,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const { treeData } = toRefs(props)
|
||||
|
||||
const shownTreeData = computed(() => {
|
||||
return XEUtils.mapTree(XEUtils.clone(treeData.value, true), (item) => {
|
||||
return XEUtils.assign(XEUtils.clone(item, true), { uuid: XEUtils.uniqueId() })
|
||||
})
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'label',
|
||||
key: 'label',
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'ops',
|
||||
width: 100,
|
||||
},
|
||||
]
|
||||
|
||||
const formItems = reactive<FormItem[]>([
|
||||
{
|
||||
group: 'form',
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '标签',
|
||||
name: 'label',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入标签',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
group: 'form',
|
||||
type: FormItemTypeEnum.INPUT,
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '值',
|
||||
name: 'value',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入值',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
])
|
||||
|
||||
const formModal = ref<typeof FormModal>()
|
||||
const data = ref<Partial<TreeDataNode>>({})
|
||||
const state = ref<'add' | 'child' | 'edit'>('add')
|
||||
const title = computed(() => {
|
||||
switch (state.value) {
|
||||
case 'add':
|
||||
return '添加数据项'
|
||||
case 'child':
|
||||
return '添加数据子项'
|
||||
default:
|
||||
return '编辑数据项'
|
||||
}
|
||||
})
|
||||
const add = () => {
|
||||
data.value = {}
|
||||
state.value = 'add'
|
||||
formModal.value?.open()
|
||||
}
|
||||
const selected = ref<TreeDataNode>()
|
||||
const addChild = (_data: TreeDataNode) => {
|
||||
data.value = {}
|
||||
state.value = 'child'
|
||||
selected.value = _data
|
||||
formModal.value?.open()
|
||||
}
|
||||
const edit = (_data: TreeDataNode) => {
|
||||
data.value = _data
|
||||
state.value = 'edit'
|
||||
formModal.value?.open()
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:tree-data': [value: TreeDataNode[]]
|
||||
}>()
|
||||
|
||||
const hanldOk = (_data: FormDataType) => {
|
||||
let options = XEUtils.clone(shownTreeData.value, true)
|
||||
if (_data.uuid) {
|
||||
options = XEUtils.mapTree(options, (item) => {
|
||||
if (item.uuid === _data.uuid) {
|
||||
item = _data as TreeDataNode & { uuid: string }
|
||||
}
|
||||
return item
|
||||
})
|
||||
} else if (!selected.value?.uuid) {
|
||||
options.push(_data as TreeDataNode & { uuid: string })
|
||||
} else {
|
||||
options = XEUtils.mapTree(options, (item) => {
|
||||
if (item.uuid === selected.value?.uuid) {
|
||||
if (item.children) {
|
||||
item.children = (item.children as Array<TreeDataNode>).concat([_data as TreeDataNode])
|
||||
} else {
|
||||
item.children = [_data as TreeDataNode]
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
emit('update:tree-data', options)
|
||||
formModal.value?.close()
|
||||
}
|
||||
const deleteData = (_data: TreeDataNode) => {
|
||||
emit(
|
||||
'update:tree-data',
|
||||
XEUtils.mapTree(
|
||||
XEUtils.searchTree(shownTreeData.value, (item) => item.uuid !== _data.uuid),
|
||||
(item) => {
|
||||
if (item.children && (item.children as Array<unknown>).length === 0) {
|
||||
delete item.children
|
||||
}
|
||||
return item
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,413 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-list item-layout="vertical" :data-source="shownRules">
|
||||
<template #header>
|
||||
<a-button type="primary" size="small" @click="add">
|
||||
<template #icon>
|
||||
<icon-font type="icon-plus"></icon-font>
|
||||
</template>
|
||||
添加
|
||||
</a-button>
|
||||
</template>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a-space>
|
||||
<span v-if="item.len">
|
||||
长度:{{ item.len }}
|
||||
<a-divider type="vertical" />
|
||||
</span>
|
||||
<span v-if="item.max">
|
||||
最大:{{ item.max }}
|
||||
<a-divider type="vertical" />
|
||||
</span>
|
||||
<span v-if="item.min">
|
||||
最小:{{ item.min }}
|
||||
<a-divider type="vertical" />
|
||||
</span>
|
||||
<span v-if="item.required">
|
||||
必填
|
||||
<a-divider type="vertical" />
|
||||
</span>
|
||||
<span v-if="item.whitespace">空格判错</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-list-item-meta>
|
||||
<template #title>类型: {{ typeTransfer(item.type) }}</template>
|
||||
<template #description>
|
||||
<span v-if="item.trigger">触发: {{ triggerTransfer(item.trigger) }}</span>
|
||||
<br />
|
||||
<span v-if="item.enum">枚举值: {{ item.enum }}</span>
|
||||
<br />
|
||||
验证消息: {{ item.message }}
|
||||
<br />
|
||||
<span v-if="item.pattern">验证正则: {{ item.pattern }}</span>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" size="small" @click="edit(item)">
|
||||
<template #icon>
|
||||
<icon-font type="icon-edit"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
<a-popconfirm title="确定删除这个校验规则?" @confirm="deleteRule(item)">
|
||||
<a-button type="primary" danger size="small">
|
||||
<template #icon>
|
||||
<icon-font type="icon-delete"></icon-font>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<form-modal
|
||||
ref="formModal"
|
||||
v-model="rule"
|
||||
:form-items="formItems"
|
||||
:config="config"
|
||||
:title="title"
|
||||
:hidden-fields="hiddenFields"
|
||||
@ok="hanldOk"
|
||||
></form-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormConfig, FormDataType, FormItem } from '@/components/form-render/form-render-types'
|
||||
import { Rule } from 'ant-design-vue/lib/form'
|
||||
import { PropType } from 'vue'
|
||||
import FormModal from '@/components/form-render/form-modal.vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
const props = defineProps({
|
||||
rules: {
|
||||
type: Array as PropType<Rule[]>,
|
||||
requried: false,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const { rules } = toRefs(props)
|
||||
|
||||
const shownRules = computed(() => {
|
||||
return XEUtils.mapTree(XEUtils.clone(rules.value, true), (item) => {
|
||||
return XEUtils.assign(XEUtils.clone(item, true), { uuid: XEUtils.uniqueId() })
|
||||
})
|
||||
})
|
||||
|
||||
const formItems = reactive<FormItem[]>([
|
||||
{
|
||||
type: 'radio',
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '校验类型',
|
||||
name: 'type',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
optionType: 'button',
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{
|
||||
label: '字符串',
|
||||
value: 'string',
|
||||
},
|
||||
{
|
||||
value: 'date',
|
||||
label: '日期',
|
||||
},
|
||||
{
|
||||
value: 'url',
|
||||
label: 'URL',
|
||||
},
|
||||
{
|
||||
value: 'hex',
|
||||
label: 'HEX',
|
||||
},
|
||||
{
|
||||
value: 'email',
|
||||
label: '邮件',
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: 'number',
|
||||
},
|
||||
{
|
||||
value: 'integer',
|
||||
label: '整数',
|
||||
},
|
||||
{
|
||||
value: 'float',
|
||||
label: '小数',
|
||||
},
|
||||
{
|
||||
value: 'enum',
|
||||
label: '枚举',
|
||||
},
|
||||
{
|
||||
value: 'boolean',
|
||||
label: '布尔',
|
||||
},
|
||||
{
|
||||
value: 'method',
|
||||
label: '方法',
|
||||
},
|
||||
{
|
||||
value: 'regexp',
|
||||
label: '正则',
|
||||
},
|
||||
{
|
||||
value: 'array',
|
||||
label: '数组',
|
||||
},
|
||||
{
|
||||
value: 'object',
|
||||
label: '对象',
|
||||
},
|
||||
],
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '校验触发时机',
|
||||
name: 'trigger',
|
||||
},
|
||||
properties: {
|
||||
options: [
|
||||
{
|
||||
label: '失焦',
|
||||
value: 'blur',
|
||||
},
|
||||
{
|
||||
label: '变化',
|
||||
value: 'change',
|
||||
},
|
||||
],
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '是否必填',
|
||||
name: 'required',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '空格视为错误',
|
||||
name: 'whitespace',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
group: 'form',
|
||||
type: 'input',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '枚举类型',
|
||||
name: 'enum',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: "请输入枚举类型,','分隔",
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
group: 'form',
|
||||
type: 'input',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '校验文案',
|
||||
name: 'message',
|
||||
required: true,
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入校验文案',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: 'input-number',
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '字段长度',
|
||||
name: 'len',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
controls: true,
|
||||
placeholder: '请输入字段长度',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: 'input-number',
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '最大长度',
|
||||
name: 'max',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
controls: true,
|
||||
placeholder: '请输入最大长度',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
type: 'input-number',
|
||||
group: 'form',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '最小长度',
|
||||
name: 'min',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
controls: true,
|
||||
placeholder: '请输入最小长度',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
{
|
||||
group: 'form',
|
||||
type: 'input',
|
||||
config: {
|
||||
autoLink: true,
|
||||
hasFeedback: false,
|
||||
label: '检验表达式',
|
||||
name: 'pattern',
|
||||
},
|
||||
properties: {
|
||||
size: 'small',
|
||||
type: 'text',
|
||||
allowClear: true,
|
||||
bordered: true,
|
||||
showCount: true,
|
||||
placeholder: '请输入检验表达式',
|
||||
},
|
||||
rules: [],
|
||||
},
|
||||
])
|
||||
|
||||
const typeTransfer = (type: string) => {
|
||||
const option = formItems[0].properties.options?.find((item) => item.value === type)
|
||||
return option ? option.label : type
|
||||
}
|
||||
|
||||
const triggerTransfer = (_trigger: string[]) => {
|
||||
return _trigger
|
||||
.map((item) => {
|
||||
return [
|
||||
{
|
||||
label: '失焦',
|
||||
value: 'blur',
|
||||
},
|
||||
{
|
||||
label: '变化',
|
||||
value: 'change',
|
||||
},
|
||||
].find((o) => o.value === item)?.label
|
||||
})
|
||||
.join(',')
|
||||
}
|
||||
|
||||
const config = reactive<FormConfig>({
|
||||
layout: 'horizontal',
|
||||
colon: true,
|
||||
hideRequiredMark: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'responsive',
|
||||
scrollToFirstError: false,
|
||||
validateOnRuleChange: true,
|
||||
labelCol: { span: 6 },
|
||||
})
|
||||
const rule = ref<FormDataType>({ required: true, whitespace: true, trigger: ['blur'] })
|
||||
|
||||
const title = ref('添加规则')
|
||||
|
||||
const hiddenFields = computed(() => {
|
||||
return [...(rule.value.required ? [] : ['whitespace']), ...(rule.value.type === 'enum' ? [] : ['enum'])]
|
||||
})
|
||||
const formModal = ref<typeof FormModal>()
|
||||
|
||||
const add = () => {
|
||||
rule.value = { type: 'string', required: true, whitespace: true, trigger: ['blur'] }
|
||||
title.value = '添加规则'
|
||||
formModal.value?.open()
|
||||
}
|
||||
|
||||
const edit = (_rule: FormDataType) => {
|
||||
rule.value = _rule
|
||||
title.value = '编辑规则'
|
||||
formModal.value?.open()
|
||||
}
|
||||
const emit = defineEmits<{
|
||||
'update:rules': [value: Rule[]]
|
||||
}>()
|
||||
|
||||
const deleteRule = (_rule: FormDataType) => {
|
||||
emit(
|
||||
'update:rules',
|
||||
XEUtils.filter(shownRules.value, (item) => item.uuid !== _rule.uuid),
|
||||
)
|
||||
}
|
||||
|
||||
const hanldOk = (_data: FormDataType) => {
|
||||
let d = XEUtils.clone(shownRules.value, true)
|
||||
if (_data.uuid) {
|
||||
d = XEUtils.map(d, (item: Rule & { uuid: string }) => {
|
||||
if (item.uuid === _data.uuid) {
|
||||
return _data as unknown as Rule & { uuid: string }
|
||||
}
|
||||
return item
|
||||
})
|
||||
} else {
|
||||
d.push(_data as unknown as Rule & { uuid: string })
|
||||
}
|
||||
emit('update:rules', d)
|
||||
formModal.value?.close()
|
||||
}
|
||||
</script>
|
||||
10
src/components/form-designer/form-designer-types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FormItem } from '../form-render/form-render-types'
|
||||
|
||||
export type FormDesignerItem = {
|
||||
uuid?: string
|
||||
type: string
|
||||
name: string
|
||||
icon: string
|
||||
formItem: FormItem
|
||||
children?: FormDesignerItem[]
|
||||
}
|
||||
57
src/components/form-designer/form-designer.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<a-row :gutter="[8, 8]">
|
||||
<a-col v-if="showDrag" :span="8">
|
||||
<ComponentPanel />
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<ComponentContainer :form-config="formConfig" :selected="selected" @selected-change="selectedChange" />
|
||||
</a-col>
|
||||
<a-col v-if="!showDrag" :span="8">
|
||||
<ConfigPanel
|
||||
:form-config="formConfig"
|
||||
:current-item="selected"
|
||||
@update:form-config="onUpdateFormConfig"
|
||||
@update:form-item="onUpdateFormItem"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import XEUtils from 'xe-utils'
|
||||
import { FormConfig } from '../form-render/form-render-types'
|
||||
import { FormDesignerItem } from './form-designer-types'
|
||||
|
||||
const showDrag = ref(true)
|
||||
|
||||
const formConfig = ref<FormConfig>({
|
||||
layout: 'horizontal',
|
||||
colon: true,
|
||||
hideRequiredMark: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'responsive',
|
||||
scrollToFirstError: false,
|
||||
validateOnRuleChange: true,
|
||||
labelCol: {
|
||||
style: {
|
||||
width: '120px',
|
||||
},
|
||||
span: 4,
|
||||
offset: 0,
|
||||
},
|
||||
labelColFixedWidthInPx: 120,
|
||||
labelColSpanWidth: 4,
|
||||
})
|
||||
|
||||
const selected = ref<FormDesignerItem>()
|
||||
const selectedChange = (formDesignerItem?: FormDesignerItem) => {
|
||||
showDrag.value = !formDesignerItem
|
||||
selected.value = formDesignerItem
|
||||
}
|
||||
|
||||
const onUpdateFormConfig = (_formConfig: FormConfig) => {
|
||||
formConfig.value = _formConfig
|
||||
}
|
||||
const onUpdateFormItem = (_formItem: FormDesignerItem) => {
|
||||
selected.value = XEUtils.assign(selected.value, _formItem)
|
||||
}
|
||||
</script>
|
||||
52
src/components/form-render/auxiliary-item-render.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="auxiliary-item-wrapper">
|
||||
<component :is="typeMap(item.type)" v-bind="Object.assign({}, $attrs, item.properties)">
|
||||
<template v-if="item.type === FormItemTypeEnum.DIVIDER">
|
||||
{{ item.properties.text }}
|
||||
</template>
|
||||
<template v-if="item.type === FormItemTypeEnum.SPACE">
|
||||
<span> </span>
|
||||
</template>
|
||||
<template v-if="item.type === FormItemTypeEnum.TYPOGRAPHY_TEXT && !item.properties.editable">
|
||||
{{ item.properties.content }}
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// 取消自动继承到跟节点的属性,否则会导致弹出两次事件(父节点与子节点分别弹出)
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, type PropType } from 'vue'
|
||||
import { FormItem, FormItemTypeEnum } from './form-render-types'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<FormItem>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const { item } = toRefs(props)
|
||||
|
||||
/**
|
||||
* 将组件类型映射为辅助项组件名称
|
||||
* 可作为扩展点,自定义映射关系,这里先按照 antd 写死
|
||||
*/
|
||||
const typeMap = (type: FormItemTypeEnum | string): string => {
|
||||
return 'a-' + type
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.auxiliary-item-wrapper {
|
||||
.text-item {
|
||||
padding: 10px 0;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
198
src/components/form-render/form-drawer.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:open="open"
|
||||
class="custom-class"
|
||||
:root-class-name="wrapClassName"
|
||||
:title="title"
|
||||
placement="right"
|
||||
:width="width"
|
||||
:mask-closable="false"
|
||||
:footer-style="{ textAlign: 'right' }"
|
||||
@after-open-change="afterOpenChange"
|
||||
>
|
||||
<form-render
|
||||
ref="formRender"
|
||||
v-model="bindValue"
|
||||
:config="config"
|
||||
:popup-container-id="wrapClassId"
|
||||
:items="formItems"
|
||||
:disabled-fields="disabledFields"
|
||||
:hidden-fields="hiddenFields"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<!-- 透传插槽 -->
|
||||
<template v-for="(item, key, index) in $slots" :key="index" #[key]>
|
||||
<template v-if="key != 'modal-footer'"><slot :name="key" :item="item" /></template>
|
||||
</template>
|
||||
</form-render>
|
||||
<template #footer>
|
||||
<a-button style="margin-right: 8px" @click="open = false">
|
||||
<template #icon>
|
||||
<icon-font type="icon-reset"></icon-font>
|
||||
</template>
|
||||
{{ $t('form.drawer.cancel') }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleOk">
|
||||
<template #icon>
|
||||
<icon-font type="icon-send"></icon-font>
|
||||
</template>
|
||||
{{ $t('form.drawer.submit') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import XEUtils from 'xe-utils'
|
||||
import FormRender from './form-render.vue'
|
||||
import { FormValueType, FormConfig, FormItem } from './form-render-types'
|
||||
import { NamePath } from 'ant-design-vue/es/form/interface'
|
||||
|
||||
const props = defineProps({
|
||||
wrapClassName: { type: String, default: '' },
|
||||
modalContentWrapperClassName: { type: String, default: '' },
|
||||
modelValue: {
|
||||
type: Object as PropType<Record<string, FormValueType>>,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: () => {
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
// 表单项
|
||||
formItems: {
|
||||
type: Array as PropType<Array<FormItem>>,
|
||||
default: () => {
|
||||
return []
|
||||
},
|
||||
},
|
||||
config: {
|
||||
type: Object as PropType<FormConfig>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {
|
||||
layout: 'horizontal',
|
||||
colon: true,
|
||||
hideRequiredMark: false,
|
||||
labelAlign: 'right',
|
||||
labelType: 'fixed',
|
||||
scrollToFirstError: false,
|
||||
validateOnRuleChange: true,
|
||||
labelCol: { span: 4 },
|
||||
}
|
||||
},
|
||||
},
|
||||
disabledFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
},
|
||||
},
|
||||
// 要隐藏的表单项
|
||||
hiddenFields: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
},
|
||||
},
|
||||
// 最大高度
|
||||
maxHeight: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: 'none',
|
||||
},
|
||||
// 弹框的宽
|
||||
width: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '750px',
|
||||
},
|
||||
})
|
||||
const { modelValue, title, width, wrapClassName, config, disabledFields, hiddenFields } = toRefs(props)
|
||||
const open = ref<boolean>(false)
|
||||
|
||||
const afterOpenChange = (bool: boolean) => {
|
||||
if (bool) {
|
||||
formRender.value?.clearValidate()
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:model-value': [value: Record<string, FormValueType>]
|
||||
ok: [value: Record<string, FormValueType>]
|
||||
}>()
|
||||
const wrapClassId = ref(XEUtils.uniqueId())
|
||||
const bindValue = computed({
|
||||
get: function () {
|
||||
return modelValue.value
|
||||
},
|
||||
set: function (value) {
|
||||
emit('update:model-value', value)
|
||||
},
|
||||
})
|
||||
const formRender = ref<typeof FormRender>()
|
||||
|
||||
// 清空表单状态
|
||||
const reset = () => {
|
||||
bindValue.value = {}
|
||||
formRender.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 打开弹框
|
||||
const show = () => {
|
||||
open.value = true
|
||||
}
|
||||
|
||||
// 关闭弹框,并清空表单状态
|
||||
const close = () => {
|
||||
reset()
|
||||
open.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
open,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
formRender.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const handleOk = async () => {
|
||||
if (!formRender.value) {
|
||||
throw new Error('获取 formRender 失败')
|
||||
}
|
||||
try {
|
||||
await formRender.value.validateFields()
|
||||
emit('ok', bindValue.value)
|
||||
} catch (errorInfo) {
|
||||
console.error('errorInfo', errorInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// 校验表单
|
||||
const validateFields = async (nameList?: Array<NamePath>) => {
|
||||
if (!formRender.value) {
|
||||
throw new Error('获取 formRender 失败')
|
||||
}
|
||||
return await formRender.value.validateFields(nameList)
|
||||
}
|
||||
// 重置表单
|
||||
const resetFields = () => {
|
||||
formRender.value?.resetFields()
|
||||
}
|
||||
defineExpose({
|
||||
show,
|
||||
close,
|
||||
reset,
|
||||
validateFields,
|
||||
resetFields,
|
||||
})
|
||||
</script>
|
||||
118
src/components/form-render/form-item-render.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<a-form-item v-bind="item.config" :rules="item.rules">
|
||||
<slot v-if="item.type == FormItemTypeEnum.CUSTOM" :name="item.config?.name">
|
||||
{{ item.config?.name }}
|
||||
</slot>
|
||||
<component
|
||||
:is="typeMap(item.type)"
|
||||
v-else-if="
|
||||
item.type == FormItemTypeEnum.UPLOAD ||
|
||||
item.type == FormItemTypeEnum.SELECT ||
|
||||
item.type == FormItemTypeEnum.MENTION
|
||||
"
|
||||
v-model:[bindPropName(item.type)]="bindValue"
|
||||
v-bind="Object.assign({}, $attrs, properties)"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<a-button v-if="item.type == FormItemTypeEnum.UPLOAD">
|
||||
<upload-outlined />
|
||||
</a-button>
|
||||
<template v-else-if="item.type == FormItemTypeEnum.SELECT && properties.options">
|
||||
<a-select-option v-for="(option, index2) in properties.options" :key="index2" :value="option.value">
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</template>
|
||||
<template v-else-if="item.type == FormItemTypeEnum.MENTION && properties.options">
|
||||
<a-mentions-option v-for="(option, index2) in properties.options" :key="index2" :value="String(option.value)">
|
||||
{{ option.label }}
|
||||
</a-mentions-option>
|
||||
</template>
|
||||
</component>
|
||||
<component
|
||||
:is="typeMap(item.type)"
|
||||
v-else
|
||||
v-model:[bindPropName(item.type)]="bindValue"
|
||||
v-bind="Object.assign({}, $attrs, properties)"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { UploadOutlined } from '@ant-design/icons-vue'
|
||||
import { PropType } from 'vue'
|
||||
import { FormItem, FormItemTypeEnum, FormValueType } from './form-render-types'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<FormItem>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean, Array, Symbol, Object] as PropType<FormValueType>,
|
||||
required: false,
|
||||
default: () => {
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
})
|
||||
const { item, value, disabled } = toRefs(props)
|
||||
|
||||
const typeMap = (type: FormItemTypeEnum | string): string => {
|
||||
if (type == FormItemTypeEnum.RADIO) {
|
||||
return 'a-radio-group'
|
||||
}
|
||||
if (type == FormItemTypeEnum.CHECKBOX) {
|
||||
return 'a-checkbox-group'
|
||||
}
|
||||
return 'a-' + type
|
||||
}
|
||||
|
||||
const properties = computed(() => {
|
||||
const p = item.value.properties
|
||||
if (item.value.type === FormItemTypeEnum.TRANSFER && !item.value.properties.render) {
|
||||
p.render = (item) => item.title
|
||||
}
|
||||
if (item.value.propertiesTransfer) {
|
||||
return item.value.propertiesTransfer(XEUtils.clone(p, true))
|
||||
}
|
||||
return p
|
||||
})
|
||||
|
||||
const bindPropName = (type: string): string => {
|
||||
if (type == 'switch') {
|
||||
return 'checked'
|
||||
}
|
||||
if (type == 'transfer') {
|
||||
return 'target-keys'
|
||||
}
|
||||
return 'value'
|
||||
}
|
||||
const emit = defineEmits<{
|
||||
'update:value': [value: FormValueType]
|
||||
}>()
|
||||
// 双向绑定
|
||||
const bindValue = computed({
|
||||
get: function (): FormValueType {
|
||||
return value?.value ? value.value : undefined
|
||||
},
|
||||
set: function (value: FormValueType) {
|
||||
emit('update:value', value)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.ant-input-number,
|
||||
.ant-input-number-affix-wrapper,
|
||||
.ant-input-number-group-wrapper,
|
||||
.ant-picker {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||