feat: 完善功能
This commit is contained in:
parent
a4aa133db5
commit
40803462ac
@ -1,8 +1,8 @@
|
|||||||
# 应用标题
|
# 应用标题
|
||||||
VITE_APP_TITLE=Vben Admin Tdesign
|
VITE_APP_TITLE=星戎物联
|
||||||
|
|
||||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||||
VITE_APP_NAMESPACE=vben-web-tdesign
|
VITE_APP_NAMESPACE=iot
|
||||||
|
|
||||||
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
||||||
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
|
VITE_APP_STORE_SECURE_KEY=xrit2doa1q3swe
|
||||||
|
|||||||
24
apps/web-tdesign/src/api/core/admin.ts
Normal file
24
apps/web-tdesign/src/api/core/admin.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export async function getAdminInfoApi() {
|
||||||
|
return requestClient.post('/admin/info');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAdminListByPage(data: any) {
|
||||||
|
return requestClient.post('/admin/queryByPage', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAdminOptions() {
|
||||||
|
return requestClient.post('/admin/options');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAdmin(data: any) {
|
||||||
|
return requestClient.post('/admin/create', data);
|
||||||
|
}
|
||||||
|
export async function updateAdmin(data: any) {
|
||||||
|
return requestClient.post('/admin/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changeAdminStatus(data: any) {
|
||||||
|
return requestClient.post('/admin/changeStatus', data);
|
||||||
|
}
|
||||||
@ -47,5 +47,5 @@ export async function logoutApi() {
|
|||||||
* 获取用户权限码
|
* 获取用户权限码
|
||||||
*/
|
*/
|
||||||
export async function getAccessCodesApi() {
|
export async function getAccessCodesApi() {
|
||||||
return requestClient.get<string[]>('/auth/codes');
|
return requestClient.post<string[]>('/auth/codes');
|
||||||
}
|
}
|
||||||
|
|||||||
5
apps/web-tdesign/src/api/core/card.ts
Normal file
5
apps/web-tdesign/src/api/core/card.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export function getCardOptions() {
|
||||||
|
return requestClient.post('/card/options');
|
||||||
|
}
|
||||||
21
apps/web-tdesign/src/api/core/dept.ts
Normal file
21
apps/web-tdesign/src/api/core/dept.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export function getDeptList() {
|
||||||
|
return requestClient.post('/dept/queryAll');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeptOptions() {
|
||||||
|
return requestClient.post('/dept/options');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDept(data: any) {
|
||||||
|
return requestClient.post('/dept/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDept(data: any) {
|
||||||
|
return requestClient.post('/dept/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeDeptStatus(data: any) {
|
||||||
|
return requestClient.post('/dept/changeStatus', data);
|
||||||
|
}
|
||||||
17
apps/web-tdesign/src/api/core/device.ts
Normal file
17
apps/web-tdesign/src/api/core/device.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export function getDeviceListByPage(data: any) {
|
||||||
|
return requestClient.post('/device/queryByPage', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeviceOptions() {
|
||||||
|
return requestClient.post('/device/options');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDevice(data: any) {
|
||||||
|
return requestClient.post('/device/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDevice(data: any) {
|
||||||
|
return requestClient.post('/device/update', data);
|
||||||
|
}
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
export * from './admin';
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
|
export * from './dept';
|
||||||
export * from './menu';
|
export * from './menu';
|
||||||
export * from './user';
|
export * from './point';
|
||||||
|
export * from './role';
|
||||||
|
|||||||
@ -1,10 +1,24 @@
|
|||||||
import type { RouteRecordStringComponent } from '@vben/types';
|
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户所有菜单
|
|
||||||
*/
|
|
||||||
export async function getAllMenusApi() {
|
export async function getAllMenusApi() {
|
||||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
return requestClient.get('/menu/all');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMenuList() {
|
||||||
|
return requestClient.post('/menu/queryAll');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMenuTree() {
|
||||||
|
return requestClient.post('/menu/queryByTree');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createMenu(data: any) {
|
||||||
|
return requestClient.post('/menu/create', data);
|
||||||
|
}
|
||||||
|
export async function updateMenu(data: any) {
|
||||||
|
return requestClient.post('/menu/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changeMenuStatus(data: any) {
|
||||||
|
return requestClient.post('/menu/changeStatus', data);
|
||||||
}
|
}
|
||||||
|
|||||||
17
apps/web-tdesign/src/api/core/point.ts
Normal file
17
apps/web-tdesign/src/api/core/point.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export function getPointListByPage(data: any) {
|
||||||
|
return requestClient.post('/point/queryByPage', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPointOptions() {
|
||||||
|
return requestClient.post('/point/options');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPoint(data: any) {
|
||||||
|
return requestClient.post('/point/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePoint(data: any) {
|
||||||
|
return requestClient.post('/point/update', data);
|
||||||
|
}
|
||||||
5
apps/web-tdesign/src/api/core/product.ts
Normal file
5
apps/web-tdesign/src/api/core/product.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export function getProductOptions() {
|
||||||
|
return requestClient.post('/product/options');
|
||||||
|
}
|
||||||
21
apps/web-tdesign/src/api/core/role.ts
Normal file
21
apps/web-tdesign/src/api/core/role.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export function getRoleList() {
|
||||||
|
return requestClient.post('/role/list');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRoleOptions() {
|
||||||
|
return requestClient.post('/role/options');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRole(data: any) {
|
||||||
|
return requestClient.post('/role/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRole(data: any) {
|
||||||
|
return requestClient.post('/role/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeRoleStatus(data: any) {
|
||||||
|
return requestClient.post('/role/changeStatus', data);
|
||||||
|
}
|
||||||
10
apps/web-tdesign/src/enum/CommonStatus.ts
Normal file
10
apps/web-tdesign/src/enum/CommonStatus.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const CommonStatusEnum = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: '启用',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
label: '禁用',
|
||||||
|
},
|
||||||
|
];
|
||||||
14
apps/web-tdesign/src/enum/Gender.ts
Normal file
14
apps/web-tdesign/src/enum/Gender.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { toObject } from '#/enum/index';
|
||||||
|
|
||||||
|
export const GenderEnum = [
|
||||||
|
{
|
||||||
|
label: '男',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '女',
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GenderEnumMap = toObject(GenderEnum, 'value', 'label');
|
||||||
42
apps/web-tdesign/src/enum/Grade.ts
Normal file
42
apps/web-tdesign/src/enum/Grade.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { toObject } from '#/enum/index';
|
||||||
|
|
||||||
|
export const GradeEnum = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: '一年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: '二年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
label: '三年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 4,
|
||||||
|
label: '四年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
label: '五年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 6,
|
||||||
|
label: '六年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 7,
|
||||||
|
label: '七年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 8,
|
||||||
|
label: '八年级',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 9,
|
||||||
|
label: '九年级',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GradeEnumMap = toObject(GradeEnum, 'value', 'label');
|
||||||
18
apps/web-tdesign/src/enum/MenuType.ts
Normal file
18
apps/web-tdesign/src/enum/MenuType.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { toObject } from '#/enum/index';
|
||||||
|
|
||||||
|
export const MenuTypeEnum = [
|
||||||
|
{
|
||||||
|
label: '目录',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '菜单',
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '按钮',
|
||||||
|
value: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MenuTypeEnumMap = toObject(MenuTypeEnum, 'value', 'label');
|
||||||
15
apps/web-tdesign/src/enum/RoleCode.ts
Normal file
15
apps/web-tdesign/src/enum/RoleCode.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { toObject } from '#/enum/index';
|
||||||
|
|
||||||
|
export const RoleCodeEnum = [
|
||||||
|
{
|
||||||
|
value: 'root',
|
||||||
|
label: '管理员',
|
||||||
|
color: {
|
||||||
|
color: '#FCE8E6', // 背景浅红
|
||||||
|
textColor: '#D32F2F', // 文字深红
|
||||||
|
borderColor: '#E57373', // 边框柔红
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const RoleCodeEnumMap = toObject(RoleCodeEnum, 'value', 'label');
|
||||||
|
export const RoleCodeEnumColorMap = toObject(RoleCodeEnum, 'value', 'color');
|
||||||
9
apps/web-tdesign/src/enum/SendHandler.ts
Normal file
9
apps/web-tdesign/src/enum/SendHandler.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { toObject } from '#/enum/index';
|
||||||
|
|
||||||
|
export const SendHandlerEnum = [
|
||||||
|
{
|
||||||
|
label: '地灾平台',
|
||||||
|
value: 'DZPTSendHandler',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const SendHandlerEnumMap = toObject(SendHandlerEnum, 'value', 'label');
|
||||||
10
apps/web-tdesign/src/enum/Subject.ts
Normal file
10
apps/web-tdesign/src/enum/Subject.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { toObject } from '#/enum/index';
|
||||||
|
|
||||||
|
export const SubjectEnum = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: '数学',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SubjectEnumMap = toObject(SubjectEnum, 'value', 'label');
|
||||||
13
apps/web-tdesign/src/enum/index.ts
Normal file
13
apps/web-tdesign/src/enum/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const toObject = <T extends Record<string, any>>(
|
||||||
|
arr: T[],
|
||||||
|
keyField: keyof T,
|
||||||
|
valueField: keyof T,
|
||||||
|
): Record<string, any> => {
|
||||||
|
const result: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const item of arr) {
|
||||||
|
result[item[keyField] as any] = item[valueField];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
53
apps/web-tdesign/src/router/routes/modules/system.ts
Normal file
53
apps/web-tdesign/src/router/routes/modules/system.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:layout-dashboard',
|
||||||
|
order: -1,
|
||||||
|
title: '系统管理',
|
||||||
|
},
|
||||||
|
name: 'System',
|
||||||
|
path: '/system',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'User',
|
||||||
|
path: '/admin',
|
||||||
|
component: () => import('#/views/system/admin/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:area-chart',
|
||||||
|
title: '用户管理',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Role',
|
||||||
|
path: '/role',
|
||||||
|
component: () => import('#/views/system/role/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:area-chart',
|
||||||
|
title: '角色管理',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Menu',
|
||||||
|
path: '/menu',
|
||||||
|
component: () => import('#/views/system/menu/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:area-chart',
|
||||||
|
title: '权限管理',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Dept',
|
||||||
|
path: '/dept',
|
||||||
|
component: () => import('#/views/system/dept/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:area-chart',
|
||||||
|
title: '部门管理',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
46
apps/web-tdesign/src/router/routes/modules/things.ts
Normal file
46
apps/web-tdesign/src/router/routes/modules/things.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:layout-dashboard',
|
||||||
|
order: -1,
|
||||||
|
title: '运维管理',
|
||||||
|
},
|
||||||
|
name: 'Things',
|
||||||
|
path: '/things',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'Point',
|
||||||
|
path: '/point',
|
||||||
|
component: () => import('#/views/things/point/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:area-chart',
|
||||||
|
title: '监测点管理',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Device',
|
||||||
|
path: '/device',
|
||||||
|
component: () => import('#/views/things/device/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:area-chart',
|
||||||
|
title: '设备管理',
|
||||||
|
fullPathKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Flow',
|
||||||
|
path: '/flow',
|
||||||
|
component: () => import('#/views/things/flow/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:area-chart',
|
||||||
|
title: '同步管理',
|
||||||
|
fullPathKey: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@ -10,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { notification } from '#/adapter/tdesign';
|
import { notification } from '#/adapter/tdesign';
|
||||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
import { getAccessCodesApi, getAdminInfoApi, loginApi, logoutApi } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
@ -98,7 +98,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
|
|
||||||
async function fetchUserInfo() {
|
async function fetchUserInfo() {
|
||||||
let userInfo: null | UserInfo = null;
|
let userInfo: null | UserInfo = null;
|
||||||
userInfo = await getUserInfoApi();
|
userInfo = await getAdminInfoApi();
|
||||||
userStore.setUserInfo(userInfo);
|
userStore.setUserInfo(userInfo);
|
||||||
return userInfo;
|
return userInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
import type { BasicOption } from '@vben/types';
|
|
||||||
|
|
||||||
import { computed, markRaw } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
@ -13,58 +12,13 @@ defineOptions({ name: 'Login' });
|
|||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
|
||||||
{
|
|
||||||
label: 'Super',
|
|
||||||
value: 'vben',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Admin',
|
|
||||||
value: 'admin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'User',
|
|
||||||
value: 'jack',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
{
|
|
||||||
component: 'VbenSelect',
|
|
||||||
componentProps: {
|
|
||||||
options: MOCK_USER_OPTIONS,
|
|
||||||
placeholder: $t('authentication.selectAccount'),
|
|
||||||
},
|
|
||||||
fieldName: 'selectAccount',
|
|
||||||
label: $t('authentication.selectAccount'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.selectAccount') })
|
|
||||||
.optional()
|
|
||||||
.default('vben'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: $t('authentication.usernameTip'),
|
placeholder: $t('authentication.usernameTip'),
|
||||||
},
|
},
|
||||||
dependencies: {
|
|
||||||
trigger(values, form) {
|
|
||||||
if (values.selectAccount) {
|
|
||||||
const findUser = MOCK_USER_OPTIONS.find(
|
|
||||||
(item) => item.value === values.selectAccount,
|
|
||||||
);
|
|
||||||
if (findUser) {
|
|
||||||
form.setValues({
|
|
||||||
password: '123456',
|
|
||||||
username: findUser.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerFields: ['selectAccount'],
|
|
||||||
},
|
|
||||||
fieldName: 'username',
|
fieldName: 'username',
|
||||||
label: $t('authentication.username'),
|
label: $t('authentication.username'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||||
@ -78,14 +32,6 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
label: $t('authentication.password'),
|
label: $t('authentication.password'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
component: markRaw(SliderCaptcha),
|
|
||||||
fieldName: 'captcha',
|
|
||||||
rules: z.boolean().refine((value) => value, {
|
|
||||||
message: $t('authentication.verifyRequiredTip'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -94,6 +40,12 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
:form-schema="formSchema"
|
:form-schema="formSchema"
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
|
:show-forget-password="false"
|
||||||
|
:show-code-login="false"
|
||||||
|
:show-qrcode-login="false"
|
||||||
|
:show-register="false"
|
||||||
|
:show-remember-me="false"
|
||||||
|
:show-third-party-login="false"
|
||||||
@submit="authStore.authLogin"
|
@submit="authStore.authLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
147
apps/web-tdesign/src/views/system/admin/data.ts
Normal file
147
apps/web-tdesign/src/views/system/admin/data.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { getDeptOptions, getRoleOptions } from '#/api';
|
||||||
|
|
||||||
|
export function useSubmitFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'displayName',
|
||||||
|
label: '姓名',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'account',
|
||||||
|
label: '账户',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'roleIds',
|
||||||
|
componentProps: {
|
||||||
|
api: getRoleOptions,
|
||||||
|
multiple: true,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'roleName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'deptId',
|
||||||
|
componentProps: {
|
||||||
|
api: getDeptOptions,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'deptName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '部门',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'email',
|
||||||
|
label: '邮箱',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'phone',
|
||||||
|
label: '手机号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSearchFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
defaultValue: '',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入姓名',
|
||||||
|
},
|
||||||
|
fieldName: 'displayName',
|
||||||
|
label: '姓名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'roleIds',
|
||||||
|
defaultValue: [],
|
||||||
|
componentProps: {
|
||||||
|
api: getRoleOptions,
|
||||||
|
multiple: true,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'roleName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'deptId',
|
||||||
|
componentProps: {
|
||||||
|
api: getDeptOptions,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'deptName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '部门',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
export function useGridSchema() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
slots: { default: 'status' },
|
||||||
|
title: '状态',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'displayName',
|
||||||
|
title: '姓名',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'phone',
|
||||||
|
title: '手机号',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'email',
|
||||||
|
title: '邮箱',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'roleIds',
|
||||||
|
slots: { default: 'roleIds' },
|
||||||
|
width: 200,
|
||||||
|
title: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'deptId',
|
||||||
|
slots: { default: 'deptId' },
|
||||||
|
title: '部门',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120,
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
73
apps/web-tdesign/src/views/system/admin/form.vue
Normal file
73
apps/web-tdesign/src/views/system/admin/form.vue
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createAdmin, updateAdmin } from '#/api';
|
||||||
|
|
||||||
|
import { useSubmitFormSchema } from './data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useSubmitFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = ref();
|
||||||
|
const [Model, modelApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
modelApi.lock();
|
||||||
|
(id.value
|
||||||
|
? updateAdmin({
|
||||||
|
id: id.value,
|
||||||
|
...values,
|
||||||
|
})
|
||||||
|
: createAdmin(values)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
modelApi.close();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
modelApi.unlock();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modelApi.getData();
|
||||||
|
await formApi.resetForm();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Vue to flush DOM updates (form fields mounted)
|
||||||
|
await nextTick();
|
||||||
|
if (data) {
|
||||||
|
await formApi.setValues(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDrawerTitle = computed(() => {
|
||||||
|
return formData.value?.id ? '编辑用户' : '新建用户';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Model :title="getDrawerTitle">
|
||||||
|
<Form />
|
||||||
|
</Model>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
133
apps/web-tdesign/src/views/system/admin/index.vue
Normal file
133
apps/web-tdesign/src/views/system/admin/index.vue
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { changeAdminStatus, getAdminListByPage } from '#/api';
|
||||||
|
import { toObject } from '#/enum';
|
||||||
|
import { CommonStatusEnum } from '#/enum/CommonStatus';
|
||||||
|
|
||||||
|
import { useGridSchema, useSearchFormSchema } from './data';
|
||||||
|
import Form from './form.vue';
|
||||||
|
|
||||||
|
const [FormModel, formModelApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
import { message } from '#/adapter/tdesign';
|
||||||
|
|
||||||
|
import { Button as TButton, Space as TSpace, Switch as TSwitch, Link as TLink } from 'tdesign-vue-next';
|
||||||
|
|
||||||
|
function onStatusChange(value: any, row: any) {
|
||||||
|
confirm(`确认${value === 1 ? '启用' : '禁用'}用户`, `切换状态`)
|
||||||
|
.then(async () => {
|
||||||
|
await changeAdminStatus({ id: row.id, status: value });
|
||||||
|
row.status = value;
|
||||||
|
message.success(`${value === 1 ? '启用' : '禁用'}成功`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.warning('取消操作');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, GridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useSearchFormSchema(),
|
||||||
|
showCollapseButton: false,
|
||||||
|
submitButtonOptions: {
|
||||||
|
content: '查询',
|
||||||
|
},
|
||||||
|
submitOnChange: true,
|
||||||
|
submitOnEnter: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
minHeight: 600,
|
||||||
|
columns: useGridSchema(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getAdminListByPage({
|
||||||
|
current: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 完全移除分隔条
|
||||||
|
separator: false,
|
||||||
|
// 你也可以使用下面的代码来移除分隔条
|
||||||
|
// separator: { show: false },
|
||||||
|
// 或者使用下面的代码来改变分隔条的颜色
|
||||||
|
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addEvent = () => {
|
||||||
|
formModelApi.setData({}).open();
|
||||||
|
};
|
||||||
|
const editEvent = (row) => {
|
||||||
|
formModelApi.setData(row).open();
|
||||||
|
};
|
||||||
|
const onRefresh = () => {
|
||||||
|
GridApi.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const roleOptionsMap = computed(() => {
|
||||||
|
return toObject(
|
||||||
|
GridApi.formApi.getFieldComponentRef('roleIds')?.getOptions() ?? [],
|
||||||
|
'value',
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const deptOptionsMap = computed(() => {
|
||||||
|
return toObject(
|
||||||
|
GridApi.formApi.getFieldComponentRef('deptId')?.getOptions() ?? [],
|
||||||
|
'value',
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="用户列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<TButton class="mr-2" type="primary" @click="addEvent">
|
||||||
|
新增用户
|
||||||
|
</TButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #status="{ row }">
|
||||||
|
<TSwitch
|
||||||
|
:value="row.status"
|
||||||
|
:checked-value="true"
|
||||||
|
:unchecked-value="false"
|
||||||
|
:on-update:value="(value) => onStatusChange(value, row)"
|
||||||
|
>
|
||||||
|
{{ toObject(CommonStatusEnum, 'value', 'label')[row.status] }}
|
||||||
|
</TSwitch>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #roleIds="{ row }">
|
||||||
|
<TSpace>
|
||||||
|
<TTag v-for="id in row.roleIds" :key="id">
|
||||||
|
{{ roleOptionsMap[id] }}
|
||||||
|
</TTag>
|
||||||
|
</TSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #deptId="{ row }"> {{ deptOptionsMap[row.deptId] }} </template>
|
||||||
|
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TSpace>
|
||||||
|
<TLink theme="primary" @click="editEvent(row)">编辑</TLink>
|
||||||
|
</TSpace>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<FormModel @success="onRefresh" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
80
apps/web-tdesign/src/views/system/dept/data.ts
Normal file
80
apps/web-tdesign/src/views/system/dept/data.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { getAdminOptions } from '#/api';
|
||||||
|
import { getPointOptions } from '#/api/core/point';
|
||||||
|
|
||||||
|
export function useSubmitFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'deptName',
|
||||||
|
label: '部门名称',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'adminIds',
|
||||||
|
label: '用户',
|
||||||
|
defaultValue: [],
|
||||||
|
componentProps: {
|
||||||
|
api: getAdminOptions,
|
||||||
|
resultField: '',
|
||||||
|
multiple: true,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'displayName',
|
||||||
|
valueField: 'id',
|
||||||
|
optionsPropName: 'options',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'pointIds',
|
||||||
|
label: '观测点',
|
||||||
|
defaultValue: [],
|
||||||
|
componentProps: {
|
||||||
|
api: getPointOptions,
|
||||||
|
resultField: '',
|
||||||
|
multiple: true,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'displayName',
|
||||||
|
valueField: 'id',
|
||||||
|
optionsPropName: 'options',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGridSchema() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'deptName',
|
||||||
|
title: '部门名称',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'adminIds',
|
||||||
|
title: '用户',
|
||||||
|
slots: { default: 'adminIds' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 150,
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
70
apps/web-tdesign/src/views/system/dept/form.vue
Normal file
70
apps/web-tdesign/src/views/system/dept/form.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createDept, updateDept } from '#/api/core';
|
||||||
|
|
||||||
|
import { useSubmitFormSchema } from './data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
const id = ref();
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useSubmitFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Model, modelApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
modelApi.lock();
|
||||||
|
(id.value
|
||||||
|
? updateDept({
|
||||||
|
id: id.value,
|
||||||
|
...values,
|
||||||
|
})
|
||||||
|
: createDept({
|
||||||
|
...values,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
modelApi.close();
|
||||||
|
})
|
||||||
|
.finally(() => modelApi.unlock());
|
||||||
|
},
|
||||||
|
|
||||||
|
async onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modelApi.getData();
|
||||||
|
await formApi.resetForm();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Vue to flush DOM updates (form fields mounted)
|
||||||
|
await nextTick();
|
||||||
|
if (data) {
|
||||||
|
await formApi.setValues(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTitle = computed(() => (id.value ? '编辑角色' : '新增角色'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Model :title="getTitle">
|
||||||
|
<Form />
|
||||||
|
</Model>
|
||||||
|
</template>
|
||||||
88
apps/web-tdesign/src/views/system/dept/index.vue
Normal file
88
apps/web-tdesign/src/views/system/dept/index.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NSpace, NTag } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getAdminOptions } from '#/api';
|
||||||
|
import { getDeptList } from '#/api/core/dept';
|
||||||
|
import { toObject } from '#/enum';
|
||||||
|
|
||||||
|
import { useGridSchema } from './data';
|
||||||
|
import Form from './form.vue';
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
const [FormModel, formModelApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格
|
||||||
|
const [Grid, GridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
minHeight: 600,
|
||||||
|
columns: useGridSchema(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async () => await getDeptList(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
separator: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刷新
|
||||||
|
const refresh = () => GridApi.reload();
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const addEvent = () => formModelApi.setData({}).open();
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const editEvent = (row) => formModelApi.setData(row).open();
|
||||||
|
|
||||||
|
const adminOptions = ref([]);
|
||||||
|
|
||||||
|
const queryAdminOptions = async () => {
|
||||||
|
adminOptions.value = await getAdminOptions();
|
||||||
|
};
|
||||||
|
const adminOptionsMap = computed(() =>
|
||||||
|
toObject(adminOptions.value ?? [], 'id', 'displayName'),
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
queryAdminOptions();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="部门列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<NSpace>
|
||||||
|
<NButton type="primary" @click="addEvent">新增部门</NButton>
|
||||||
|
<NButton @click="refresh">查询</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #adminIds="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NTag v-for="id in row.adminIds" :key="id">
|
||||||
|
{{ adminOptionsMap[id] }}
|
||||||
|
</NTag>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NButton text type="primary" @click="editEvent(row)">编辑</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<FormModel @success="refresh" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
113
apps/web-tdesign/src/views/system/menu/data.ts
Normal file
113
apps/web-tdesign/src/views/system/menu/data.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { getMenuTree, getRoleOptions } from '#/api';
|
||||||
|
import { MenuTypeEnum } from '#/enum/MenuType';
|
||||||
|
|
||||||
|
export function useSubmitFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'menuType',
|
||||||
|
defaultValue: 1,
|
||||||
|
componentProps: {
|
||||||
|
options: MenuTypeEnum,
|
||||||
|
},
|
||||||
|
label: '权限类型',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
fieldName: 'parentId',
|
||||||
|
label: '上级权限',
|
||||||
|
componentProps: {
|
||||||
|
api: getMenuTree,
|
||||||
|
resultField: 'items',
|
||||||
|
childrenField: 'children',
|
||||||
|
labelField: 'menuName',
|
||||||
|
valueField: 'id',
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
defaultValue: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'menuName',
|
||||||
|
label: '权限名称',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'menuCode',
|
||||||
|
label: '权限编码',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'roleIds',
|
||||||
|
componentProps: {
|
||||||
|
api: getRoleOptions,
|
||||||
|
multiple: true,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'roleName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSearchFormSchema(): VbenFormSchema[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
export function useGridSchema() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
slots: { default: 'status' },
|
||||||
|
title: '状态',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'menuName',
|
||||||
|
title: '权限名称',
|
||||||
|
width: 200,
|
||||||
|
treeNode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'menuCode',
|
||||||
|
title: '权限编码',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'menuType',
|
||||||
|
title: '权限类型',
|
||||||
|
slots: { default: 'menuType' },
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'roleIds',
|
||||||
|
slots: { default: 'roleIds' },
|
||||||
|
title: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 200,
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
73
apps/web-tdesign/src/views/system/menu/form.vue
Normal file
73
apps/web-tdesign/src/views/system/menu/form.vue
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createMenu, updateMenu } from '#/api';
|
||||||
|
|
||||||
|
import { useSubmitFormSchema } from './data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useSubmitFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = ref();
|
||||||
|
const [Model, modelApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
modelApi.lock();
|
||||||
|
(id.value
|
||||||
|
? updateMenu({
|
||||||
|
id: id.value,
|
||||||
|
...values,
|
||||||
|
})
|
||||||
|
: createMenu(values)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
modelApi.close();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
modelApi.unlock();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modelApi.getData();
|
||||||
|
await formApi.resetForm();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Vue to flush DOM updates (form fields mounted)
|
||||||
|
await nextTick();
|
||||||
|
if (data) {
|
||||||
|
await formApi.setValues(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDrawerTitle = computed(() => {
|
||||||
|
return formData.value?.id ? '编辑用户' : '新建用户';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Model :title="getDrawerTitle">
|
||||||
|
<Form />
|
||||||
|
</Model>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
157
apps/web-tdesign/src/views/system/menu/index.vue
Normal file
157
apps/web-tdesign/src/views/system/menu/index.vue
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NSpace, NSwitch, NTag, useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { changeMenuStatus, getMenuList, getRoleOptions } from '#/api';
|
||||||
|
import { toObject } from '#/enum';
|
||||||
|
import { CommonStatusEnum } from '#/enum/CommonStatus';
|
||||||
|
import { MenuTypeEnumMap } from '#/enum/MenuType';
|
||||||
|
|
||||||
|
import { useGridSchema, useSearchFormSchema } from './data';
|
||||||
|
import Form from './form.vue';
|
||||||
|
|
||||||
|
const [FormModel, formModelApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
|
function onStatusChange(value: any, row: any) {
|
||||||
|
confirm(`确认${value === 1 ? '启用' : '禁用'}权限`, `切换状态`)
|
||||||
|
.then(async () => {
|
||||||
|
await changeMenuStatus({ id: row.id, status: value });
|
||||||
|
row.status = value;
|
||||||
|
message.success(`${value === 1 ? '启用' : '禁用'}成功`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.warning('取消操作');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, GridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useSearchFormSchema(),
|
||||||
|
showCollapseButton: false,
|
||||||
|
submitButtonOptions: {
|
||||||
|
content: '查询',
|
||||||
|
},
|
||||||
|
submitOnChange: true,
|
||||||
|
submitOnEnter: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
minHeight: 600,
|
||||||
|
columns: useGridSchema(),
|
||||||
|
treeConfig: {
|
||||||
|
transform: true,
|
||||||
|
parentField: 'parentId',
|
||||||
|
rowField: 'id',
|
||||||
|
showIcon: true,
|
||||||
|
expandAll: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async () => {
|
||||||
|
return await getMenuList();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 完全移除分隔条
|
||||||
|
separator: false,
|
||||||
|
// 你也可以使用下面的代码来移除分隔条
|
||||||
|
// separator: { show: false },
|
||||||
|
// 或者使用下面的代码来改变分隔条的颜色
|
||||||
|
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addEvent = () => {
|
||||||
|
formModelApi.setData({}).open();
|
||||||
|
};
|
||||||
|
const addChildMenu = (row) => {
|
||||||
|
formModelApi.setData({ parentId: row.id }).open();
|
||||||
|
};
|
||||||
|
const editEvent = (row) => {
|
||||||
|
formModelApi.setData(row).open();
|
||||||
|
};
|
||||||
|
const onRefresh = () => {
|
||||||
|
GridApi.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const roleOptions = ref([]);
|
||||||
|
|
||||||
|
const queryRoleOptions = async () => {
|
||||||
|
roleOptions.value = await getRoleOptions();
|
||||||
|
GridApi.formApi.updateSchema([
|
||||||
|
{
|
||||||
|
fieldName: 'roleIds',
|
||||||
|
componentProps: {
|
||||||
|
options: roleOptions.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const roleOptionsMap = computed(() =>
|
||||||
|
toObject(roleOptions.value ?? [], 'id', 'roleName'),
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queryRoleOptions();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="权限列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<NButton class="mr-2" type="primary" @click="addEvent">
|
||||||
|
新增权限
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #status="{ row }">
|
||||||
|
<NSwitch
|
||||||
|
:value="row.status"
|
||||||
|
:checked-value="true"
|
||||||
|
:unchecked-value="false"
|
||||||
|
:on-update:value="(value) => onStatusChange(value, row)"
|
||||||
|
>
|
||||||
|
{{ toObject(CommonStatusEnum, 'value', 'label')[row.status] }}
|
||||||
|
</NSwitch>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #menuType="{ row }">
|
||||||
|
<NTag>
|
||||||
|
{{ MenuTypeEnumMap[row.menuType] }}
|
||||||
|
</NTag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #roleIds="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NTag v-for="id in row.roleIds" :key="id">
|
||||||
|
{{ roleOptionsMap[id] }}
|
||||||
|
</NTag>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NButton text type="primary" @click="addChildMenu(row)">
|
||||||
|
增加下级菜单
|
||||||
|
</NButton>
|
||||||
|
<NButton text type="primary" @click="editEvent(row)">编辑</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<FormModel @success="onRefresh" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
99
apps/web-tdesign/src/views/system/role/data.ts
Normal file
99
apps/web-tdesign/src/views/system/role/data.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { getAdminOptions, getMenuTree } from '#/api';
|
||||||
|
|
||||||
|
export function useSubmitFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'roleName',
|
||||||
|
label: '角色名称',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'roleCode',
|
||||||
|
label: '角色编码',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'adminIds',
|
||||||
|
label: '用户',
|
||||||
|
componentProps: {
|
||||||
|
api: getAdminOptions,
|
||||||
|
resultField: '',
|
||||||
|
multiple: true,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'displayName',
|
||||||
|
valueField: 'id',
|
||||||
|
optionsPropName: 'options',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getMenuTree,
|
||||||
|
resultField: 'items',
|
||||||
|
childrenField: 'children',
|
||||||
|
labelField: 'menuName',
|
||||||
|
valueField: 'id',
|
||||||
|
maxTagCount: 'responsive',
|
||||||
|
clearable: true,
|
||||||
|
multiple: true,
|
||||||
|
cascade: true,
|
||||||
|
checkable: true,
|
||||||
|
},
|
||||||
|
fieldName: 'menuIds',
|
||||||
|
label: '权限',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGridSchema() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
slots: { default: 'status' },
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'roleName',
|
||||||
|
title: '角色名称',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'roleCode',
|
||||||
|
title: '角色编码',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'adminIds',
|
||||||
|
title: '用户',
|
||||||
|
slots: { default: 'adminIds' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 150,
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
78
apps/web-tdesign/src/views/system/role/form.vue
Normal file
78
apps/web-tdesign/src/views/system/role/form.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createRole, updateRole } from '#/api';
|
||||||
|
|
||||||
|
import { useSubmitFormSchema } from './data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
const id = ref();
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useSubmitFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Model, modelApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
|
||||||
|
const menuIds = formApi
|
||||||
|
.getFieldComponentRef('menuIds')
|
||||||
|
.getComponentRef()
|
||||||
|
.getIndeterminateData().keys;
|
||||||
|
|
||||||
|
modelApi.lock();
|
||||||
|
(id.value
|
||||||
|
? updateRole({
|
||||||
|
id: id.value,
|
||||||
|
...values,
|
||||||
|
menuIds: [...values.menuIds, ...menuIds],
|
||||||
|
})
|
||||||
|
: createRole({
|
||||||
|
...values,
|
||||||
|
menuIds: [...values.menuIds, ...menuIds],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
modelApi.close();
|
||||||
|
})
|
||||||
|
.finally(() => modelApi.unlock());
|
||||||
|
},
|
||||||
|
|
||||||
|
async onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modelApi.getData();
|
||||||
|
await formApi.resetForm();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Vue to flush DOM updates (form fields mounted)
|
||||||
|
await nextTick();
|
||||||
|
if (data) {
|
||||||
|
await formApi.setValues(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTitle = computed(() => (id.value ? '编辑角色' : '新增角色'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Model :title="getTitle">
|
||||||
|
<Form />
|
||||||
|
</Model>
|
||||||
|
</template>
|
||||||
110
apps/web-tdesign/src/views/system/role/index.vue
Normal file
110
apps/web-tdesign/src/views/system/role/index.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NSpace, NSwitch, NTag, useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { changeRoleStatus, getAdminOptions, getRoleList } from '#/api';
|
||||||
|
import { toObject } from '#/enum';
|
||||||
|
|
||||||
|
import { useGridSchema } from './data';
|
||||||
|
import Form from './form.vue';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
const [FormModel, formModelApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格
|
||||||
|
const [Grid, GridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
minHeight: 600,
|
||||||
|
columns: useGridSchema(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
// 关键:不做分页,一次性加载
|
||||||
|
query: async () => await getRoleList(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
separator: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刷新
|
||||||
|
const refresh = () => GridApi.reload();
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const addEvent = () => formModelApi.setData({}).open();
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const editEvent = (row) => formModelApi.setData(row).open();
|
||||||
|
|
||||||
|
// 状态切换
|
||||||
|
const onStatusChange = (value, row) => {
|
||||||
|
confirm(`确认${value ? '启用' : '禁用'}角色?`, '提示')
|
||||||
|
.then(async () => {
|
||||||
|
await changeRoleStatus({ id: row.id, status: value });
|
||||||
|
row.status = value;
|
||||||
|
message.success('状态更新成功');
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const adminOptions = ref([]);
|
||||||
|
|
||||||
|
const queryAdminOptions = async () => {
|
||||||
|
adminOptions.value = await getAdminOptions();
|
||||||
|
};
|
||||||
|
const adminOptionsMap = computed(() =>
|
||||||
|
toObject(adminOptions.value ?? [], 'id', 'displayName'),
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
queryAdminOptions();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="角色列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<NSpace>
|
||||||
|
<NButton type="primary" @click="addEvent">新增角色</NButton>
|
||||||
|
<NButton @click="refresh">查询</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #status="{ row }">
|
||||||
|
<NSwitch
|
||||||
|
:value="row.status"
|
||||||
|
:checked-value="true"
|
||||||
|
:unchecked-value="false"
|
||||||
|
:on-update:value="(value) => onStatusChange(value, row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #adminIds="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NTag v-for="id in row.adminIds" :key="id">
|
||||||
|
{{ adminOptionsMap[id] }}
|
||||||
|
</NTag>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NButton text type="primary" @click="editEvent(row)">编辑</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<FormModel @success="refresh" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
193
apps/web-tdesign/src/views/things/device/data.ts
Normal file
193
apps/web-tdesign/src/views/things/device/data.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { getPointOptions } from '#/api';
|
||||||
|
import { getCardOptions } from '#/api/core/card';
|
||||||
|
import { getProductOptions } from '#/api/core/product';
|
||||||
|
|
||||||
|
export function useSubmitFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'pointId',
|
||||||
|
componentProps: {
|
||||||
|
api: getPointOptions,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'displayName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '监测点',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'parentId',
|
||||||
|
dependencies: {
|
||||||
|
show: false,
|
||||||
|
triggerFields: ['productIdSwitch'],
|
||||||
|
},
|
||||||
|
label: '父级设备',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'productId',
|
||||||
|
componentProps: {
|
||||||
|
api: getProductOptions,
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
label: '设备型号',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'displayName',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
label: '设备名称',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'msgType',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请选择设备消息类型',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'rs485ValueRpt',
|
||||||
|
value: 'rs485ValueRpt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'diValueRpt',
|
||||||
|
value: 'diValueRpt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'aiValueRpt',
|
||||||
|
value: 'aiValueRpt',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
label: '设备消息类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'sn',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
label: '设备编码',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'card',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
label: '设备卡号',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'longitude',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
label: '经度',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'latitude',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
label: '纬度',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSearchFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'pointId',
|
||||||
|
componentProps: {
|
||||||
|
api: getPointOptions,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'displayName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '监测点',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'productId',
|
||||||
|
componentProps: {
|
||||||
|
api: getProductOptions,
|
||||||
|
clearable: true,
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
label: '监测点',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'cardId',
|
||||||
|
componentProps: {
|
||||||
|
api: getCardOptions,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'cardNumber',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '卡号',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
export function useGridSchema() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: '序号',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'pointId',
|
||||||
|
title: '点位名称',
|
||||||
|
slots: { default: 'pointId' },
|
||||||
|
width: 300,
|
||||||
|
treeNode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'displayName',
|
||||||
|
title: '设备名称',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'productId',
|
||||||
|
title: '设备型号',
|
||||||
|
slots: { default: 'productId' },
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'cardId',
|
||||||
|
title: '设备卡号',
|
||||||
|
slots: { default: 'cardId' },
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'longitude',
|
||||||
|
title: '经度',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'latitude',
|
||||||
|
title: '纬度',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 200,
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
75
apps/web-tdesign/src/views/things/device/form.vue
Normal file
75
apps/web-tdesign/src/views/things/device/form.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createDevice, updateDevice } from '#/api/core/device';
|
||||||
|
|
||||||
|
import { useSubmitFormSchema } from './data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useSubmitFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = ref();
|
||||||
|
const [Model, modelApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
modelApi.lock();
|
||||||
|
(id.value
|
||||||
|
? updateDevice({
|
||||||
|
id: id.value,
|
||||||
|
...values,
|
||||||
|
})
|
||||||
|
: createDevice({
|
||||||
|
...values,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
modelApi.close();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
modelApi.unlock();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modelApi.getData();
|
||||||
|
await formApi.resetForm();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Vue to flush DOM updates (form fields mounted)
|
||||||
|
await nextTick();
|
||||||
|
if (data) {
|
||||||
|
await formApi.setValues(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDrawerTitle = computed(() => {
|
||||||
|
return formData.value?.id ? '编辑设备' : '新建设备';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Model :title="getDrawerTitle">
|
||||||
|
<Form />
|
||||||
|
</Model>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
149
apps/web-tdesign/src/views/things/device/index.vue
Normal file
149
apps/web-tdesign/src/views/things/device/index.vue
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NSpace } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getDeviceListByPage } from '#/api/core/device';
|
||||||
|
import { toObject } from '#/enum';
|
||||||
|
|
||||||
|
import { useGridSchema, useSearchFormSchema } from './data';
|
||||||
|
import Form from './form.vue';
|
||||||
|
|
||||||
|
const [FormModel, formModelApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Grid, GridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useSearchFormSchema(),
|
||||||
|
showCollapseButton: false,
|
||||||
|
submitButtonOptions: {
|
||||||
|
content: '查询',
|
||||||
|
},
|
||||||
|
submitOnChange: true,
|
||||||
|
submitOnEnter: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
minHeight: 600,
|
||||||
|
columns: useGridSchema(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getDeviceListByPage({
|
||||||
|
current: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'parentId',
|
||||||
|
rowField: 'id',
|
||||||
|
childrenField: 'children',
|
||||||
|
showIcon: true,
|
||||||
|
expandAll: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 完全移除分隔条
|
||||||
|
separator: false,
|
||||||
|
// 你也可以使用下面的代码来移除分隔条
|
||||||
|
// separator: { show: false },
|
||||||
|
// 或者使用下面的代码来改变分隔条的颜色
|
||||||
|
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addEvent = () => {
|
||||||
|
formModelApi
|
||||||
|
.setData({
|
||||||
|
pointId:
|
||||||
|
GridApi.formApi.getFieldComponentRef('pointId').getValue() ?? undefined,
|
||||||
|
})
|
||||||
|
.open();
|
||||||
|
};
|
||||||
|
const addChildEvent = (row) => {
|
||||||
|
formModelApi
|
||||||
|
.setData({
|
||||||
|
parentId: row.id,
|
||||||
|
pointId: row.pointId,
|
||||||
|
})
|
||||||
|
.open();
|
||||||
|
};
|
||||||
|
const editEvent = (row) => {
|
||||||
|
formModelApi
|
||||||
|
.setData({
|
||||||
|
...row,
|
||||||
|
card: row.cardId ? cardOptionsMap.value[row.cardId] : undefined,
|
||||||
|
})
|
||||||
|
.open();
|
||||||
|
};
|
||||||
|
const onRefresh = () => {
|
||||||
|
GridApi.formApi.getFieldComponentRef('cardId')?.fetchApi();
|
||||||
|
GridApi.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const pointOptionsMap = computed(() => {
|
||||||
|
return toObject(
|
||||||
|
GridApi.formApi.getFieldComponentRef('pointId')?.getOptions() ?? [],
|
||||||
|
'value',
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const productOptionsMap = computed(() => {
|
||||||
|
return toObject(
|
||||||
|
GridApi.formApi.getFieldComponentRef('productId')?.getOptions() ?? [],
|
||||||
|
'value',
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const cardOptionsMap = computed(() => {
|
||||||
|
return toObject(
|
||||||
|
GridApi.formApi.getFieldComponentRef('cardId')?.getOptions() ?? [],
|
||||||
|
'value',
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
const route = useRoute();
|
||||||
|
GridApi.formApi.setFieldValue('pointId', route.query.pointId);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="监测点列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<NButton class="mr-2" type="primary" @click="addEvent">
|
||||||
|
新增设备
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
<template #pointId="{ row }">
|
||||||
|
{{ pointOptionsMap[row.pointId] }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #productId="{ row }">
|
||||||
|
{{ productOptionsMap[row.productId] }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cardId="{ row }">
|
||||||
|
{{ cardOptionsMap[row.cardId] }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NButton text type="primary" @click="editEvent(row)">编辑</NButton>
|
||||||
|
<NButton text type="primary" @click="addChildEvent(row)">
|
||||||
|
增加子设备
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<FormModel @success="onRefresh" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
103
apps/web-tdesign/src/views/things/flow/data.ts
Normal file
103
apps/web-tdesign/src/views/things/flow/data.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { getDeptOptions } from '#/api';
|
||||||
|
import { SendHandlerEnum } from '#/enum/SendHandler';
|
||||||
|
|
||||||
|
export function useSubmitFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'deviceId',
|
||||||
|
label: '设备id',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入同步key',
|
||||||
|
},
|
||||||
|
fieldName: 'syncKey',
|
||||||
|
label: '同步key',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入同步密钥',
|
||||||
|
},
|
||||||
|
fieldName: 'syncSecret',
|
||||||
|
label: '同步密钥',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择同步平台',
|
||||||
|
options: SendHandlerEnum,
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
fieldName: 'sendHandlerClass',
|
||||||
|
label: '同步平台',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSearchFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入观测点名称',
|
||||||
|
},
|
||||||
|
fieldName: 'displayName',
|
||||||
|
label: '观测点名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'deptId',
|
||||||
|
componentProps: {
|
||||||
|
api: getDeptOptions,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'deptName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '部门',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
export function useGridSchema() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: '序号',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'displayName',
|
||||||
|
title: '点位名称',
|
||||||
|
slots: { default: 'displayName' },
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'locationName',
|
||||||
|
title: '行政区划',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'deptIds',
|
||||||
|
title: '部门',
|
||||||
|
slots: { default: 'deptIds' },
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120,
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
101
apps/web-tdesign/src/views/things/flow/form.vue
Normal file
101
apps/web-tdesign/src/views/things/flow/form.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { areaList } from '@vant/area-data';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createPoint, updatePoint } from '#/api';
|
||||||
|
|
||||||
|
import { useSubmitFormSchema } from './data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const codeToText = (code: string): string => {
|
||||||
|
const { province_list, city_list, county_list } = areaList;
|
||||||
|
if (!code) return '';
|
||||||
|
|
||||||
|
// 自动根据位数推断 (代码通常为6位)
|
||||||
|
const provinceCode = `${code.slice(0, 2)}0000`;
|
||||||
|
const cityCode = `${code.slice(0, 4)}00`;
|
||||||
|
const countyCode = code;
|
||||||
|
|
||||||
|
const province = province_list[provinceCode] || '';
|
||||||
|
const city = city_list[cityCode] || '';
|
||||||
|
const county = county_list[countyCode] || '';
|
||||||
|
|
||||||
|
// 过滤空值并用空格拼接
|
||||||
|
return [province, city, county].filter(Boolean).join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useSubmitFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = ref();
|
||||||
|
const [Model, modelApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
|
||||||
|
modelApi.lock();
|
||||||
|
(id.value
|
||||||
|
? updatePoint({
|
||||||
|
id: id.value,
|
||||||
|
...values,
|
||||||
|
...(!!values.locationCode && {
|
||||||
|
locationName: codeToText(values.locationCode),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
: createPoint({
|
||||||
|
...values,
|
||||||
|
...(!!values.locationCode && {
|
||||||
|
locationName: codeToText(values.locationCode),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
modelApi.close();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
modelApi.unlock();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modelApi.getData();
|
||||||
|
await formApi.resetForm();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Vue to flush DOM updates (form fields mounted)
|
||||||
|
await nextTick();
|
||||||
|
if (data) {
|
||||||
|
await formApi.setValues(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDrawerTitle = computed(() => {
|
||||||
|
return formData.value?.id ? '编辑用户' : '新建用户';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Model :title="getDrawerTitle">
|
||||||
|
<Form />
|
||||||
|
</Model>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
113
apps/web-tdesign/src/views/things/flow/index.vue
Normal file
113
apps/web-tdesign/src/views/things/flow/index.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NSpace, NTag } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getPointListByPage } from '#/api';
|
||||||
|
import { toObject } from '#/enum';
|
||||||
|
import { router } from '#/router';
|
||||||
|
|
||||||
|
import { useGridSchema, useSearchFormSchema } from './data';
|
||||||
|
import Form from './form.vue';
|
||||||
|
|
||||||
|
const [FormModel, formModelApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Grid, GridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useSearchFormSchema(),
|
||||||
|
showCollapseButton: false,
|
||||||
|
submitButtonOptions: {
|
||||||
|
content: '查询',
|
||||||
|
},
|
||||||
|
submitOnChange: true,
|
||||||
|
submitOnEnter: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
minHeight: 600,
|
||||||
|
columns: useGridSchema(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getPointListByPage({
|
||||||
|
current: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 完全移除分隔条
|
||||||
|
separator: false,
|
||||||
|
// 你也可以使用下面的代码来移除分隔条
|
||||||
|
// separator: { show: false },
|
||||||
|
// 或者使用下面的代码来改变分隔条的颜色
|
||||||
|
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addEvent = () => {
|
||||||
|
formModelApi.setData({}).open();
|
||||||
|
};
|
||||||
|
const editEvent = (row) => {
|
||||||
|
formModelApi.setData(row).open();
|
||||||
|
};
|
||||||
|
const onRefresh = () => {
|
||||||
|
GridApi.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deptOptionsMap = computed(() => {
|
||||||
|
return toObject(
|
||||||
|
GridApi.formApi.getFieldComponentRef('deptId')?.getOptions() ?? [],
|
||||||
|
'value',
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const jumpToDevice = (row: any) => {
|
||||||
|
router.push({
|
||||||
|
name: 'Device',
|
||||||
|
query: {
|
||||||
|
pointId: row.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="监测点列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<NButton class="mr-2" type="primary" @click="addEvent">
|
||||||
|
新增监测点
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #displayName="{ row }">
|
||||||
|
<NButton type="primary" text @click="jumpToDevice(row)">
|
||||||
|
{{ row.displayName }}
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
<template #deptIds="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NTag v-for="id in row.deptIds" :key="id">
|
||||||
|
{{ deptOptionsMap[id] }}
|
||||||
|
</NTag>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NButton text type="primary" @click="editEvent(row)">编辑</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<FormModel @success="onRefresh" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
113
apps/web-tdesign/src/views/things/point/data.ts
Normal file
113
apps/web-tdesign/src/views/things/point/data.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { useCascaderAreaData } from '@vant/area-data';
|
||||||
|
|
||||||
|
import { getDeptOptions } from '#/api';
|
||||||
|
|
||||||
|
export function useSubmitFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Cascader',
|
||||||
|
componentProps: {
|
||||||
|
options: useCascaderAreaData(),
|
||||||
|
labelField: 'text',
|
||||||
|
valueField: 'value',
|
||||||
|
filterable: true,
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请选择行政区划',
|
||||||
|
},
|
||||||
|
defaultValue: [],
|
||||||
|
fieldName: 'locationCode',
|
||||||
|
label: '行政区划',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'displayName',
|
||||||
|
label: '观测点名称',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'deptIds',
|
||||||
|
defaultValue: [],
|
||||||
|
componentProps: {
|
||||||
|
api: getDeptOptions,
|
||||||
|
clearable: true,
|
||||||
|
multiple: true,
|
||||||
|
labelField: 'deptName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '部门',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSearchFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入观测点名称',
|
||||||
|
},
|
||||||
|
fieldName: 'displayName',
|
||||||
|
label: '观测点名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiSelect',
|
||||||
|
fieldName: 'deptId',
|
||||||
|
componentProps: {
|
||||||
|
api: getDeptOptions,
|
||||||
|
clearable: true,
|
||||||
|
labelField: 'deptName',
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
label: '部门',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
export function useGridSchema() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: '序号',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'displayName',
|
||||||
|
title: '点位名称',
|
||||||
|
slots: { default: 'displayName' },
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'locationName',
|
||||||
|
title: '行政区划',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'deptIds',
|
||||||
|
title: '部门',
|
||||||
|
slots: { default: 'deptIds' },
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
title: '操作',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120,
|
||||||
|
slots: { default: 'actions' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
101
apps/web-tdesign/src/views/things/point/form.vue
Normal file
101
apps/web-tdesign/src/views/things/point/form.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { areaList } from '@vant/area-data';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createPoint, updatePoint } from '#/api';
|
||||||
|
|
||||||
|
import { useSubmitFormSchema } from './data';
|
||||||
|
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
|
const formData = ref();
|
||||||
|
|
||||||
|
const codeToText = (code: string): string => {
|
||||||
|
const { province_list, city_list, county_list } = areaList;
|
||||||
|
if (!code) return '';
|
||||||
|
|
||||||
|
// 自动根据位数推断 (代码通常为6位)
|
||||||
|
const provinceCode = `${code.slice(0, 2)}0000`;
|
||||||
|
const cityCode = `${code.slice(0, 4)}00`;
|
||||||
|
const countyCode = code;
|
||||||
|
|
||||||
|
const province = province_list[provinceCode] || '';
|
||||||
|
const city = city_list[cityCode] || '';
|
||||||
|
const county = county_list[countyCode] || '';
|
||||||
|
|
||||||
|
// 过滤空值并用空格拼接
|
||||||
|
return [province, city, county].filter(Boolean).join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
schema: useSubmitFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = ref();
|
||||||
|
const [Model, modelApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) return;
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
|
||||||
|
modelApi.lock();
|
||||||
|
(id.value
|
||||||
|
? updatePoint({
|
||||||
|
id: id.value,
|
||||||
|
...values,
|
||||||
|
...(!!values.locationCode && {
|
||||||
|
locationName: codeToText(values.locationCode),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
: createPoint({
|
||||||
|
...values,
|
||||||
|
...(!!values.locationCode && {
|
||||||
|
locationName: codeToText(values.locationCode),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
emits('success');
|
||||||
|
modelApi.close();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
modelApi.unlock();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modelApi.getData();
|
||||||
|
await formApi.resetForm();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
formData.value = data;
|
||||||
|
id.value = data.id;
|
||||||
|
} else {
|
||||||
|
id.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Vue to flush DOM updates (form fields mounted)
|
||||||
|
await nextTick();
|
||||||
|
if (data) {
|
||||||
|
await formApi.setValues(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDrawerTitle = computed(() => {
|
||||||
|
return formData.value?.id ? '编辑用户' : '新建用户';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Model :title="getDrawerTitle">
|
||||||
|
<Form />
|
||||||
|
</Model>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
113
apps/web-tdesign/src/views/things/point/index.vue
Normal file
113
apps/web-tdesign/src/views/things/point/index.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NSpace, NTag } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getPointListByPage } from '#/api';
|
||||||
|
import { toObject } from '#/enum';
|
||||||
|
import { router } from '#/router';
|
||||||
|
|
||||||
|
import { useGridSchema, useSearchFormSchema } from './data';
|
||||||
|
import Form from './form.vue';
|
||||||
|
|
||||||
|
const [FormModel, formModelApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Grid, GridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useSearchFormSchema(),
|
||||||
|
showCollapseButton: false,
|
||||||
|
submitButtonOptions: {
|
||||||
|
content: '查询',
|
||||||
|
},
|
||||||
|
submitOnChange: true,
|
||||||
|
submitOnEnter: true,
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
minHeight: 600,
|
||||||
|
columns: useGridSchema(),
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getPointListByPage({
|
||||||
|
current: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 完全移除分隔条
|
||||||
|
separator: false,
|
||||||
|
// 你也可以使用下面的代码来移除分隔条
|
||||||
|
// separator: { show: false },
|
||||||
|
// 或者使用下面的代码来改变分隔条的颜色
|
||||||
|
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addEvent = () => {
|
||||||
|
formModelApi.setData({}).open();
|
||||||
|
};
|
||||||
|
const editEvent = (row) => {
|
||||||
|
formModelApi.setData(row).open();
|
||||||
|
};
|
||||||
|
const onRefresh = () => {
|
||||||
|
GridApi.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deptOptionsMap = computed(() => {
|
||||||
|
return toObject(
|
||||||
|
GridApi.formApi.getFieldComponentRef('deptId')?.getOptions() ?? [],
|
||||||
|
'value',
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const jumpToDevice = (row: any) => {
|
||||||
|
router.push({
|
||||||
|
name: 'Device',
|
||||||
|
query: {
|
||||||
|
pointId: row.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid table-title="监测点列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<NButton class="mr-2" type="primary" @click="addEvent">
|
||||||
|
新增监测点
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #displayName="{ row }">
|
||||||
|
<NButton type="primary" text @click="jumpToDevice(row)">
|
||||||
|
{{ row.displayName }}
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
<template #deptIds="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NTag v-for="id in row.deptIds" :key="id">
|
||||||
|
{{ deptOptionsMap[id] }}
|
||||||
|
</NTag>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<NSpace>
|
||||||
|
<NButton text type="primary" @click="editEvent(row)">编辑</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<FormModel @success="onRefresh" />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@ -10,7 +10,7 @@ export default defineConfig(async () => {
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
// mock代理目标地址
|
// mock代理目标地址
|
||||||
target: 'http://localhost:5320/api',
|
target: 'http://localhost:7901/api',
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user