Merge remote-tracking branch 'origin/develop' into feature/device

# Conflicts:
#	.env.development
#	.eslintrc.js
#	components.d.ts
#	config/vite.config.dev.ts
#	index.html
#	package.json
#	pnpm-lock.yaml
#	src/api/authority.ts
#	src/api/dept.ts
#	src/api/interceptor.ts
#	src/api/message.ts
#	src/api/role.ts
#	src/api/user.ts
#	src/assets/world.json
#	src/components/breadcrumb/index.vue
#	src/components/footer/index.vue
#	src/components/menu/index.vue
#	src/components/message-box/index.vue
#	src/components/message-box/list.vue
#	src/components/navbar/index.vue
#	src/components/tab-bar/readme.md
#	src/components/tab-bar/tab-item.vue
#	src/config/settings.json
#	src/directive/permission/index.ts
#	src/hooks/permission.ts
#	src/locale/en-US.ts
#	src/locale/zh-CN.ts
#	src/mock/index.ts
#	src/router/guard/permission.ts
#	src/router/guard/userLoginInfo.ts
#	src/router/routes/modules/dashboard.ts
#	src/router/routes/modules/system.ts
#	src/router/routes/modules/user.ts
#	src/store/index.ts
#	src/store/modules/auth/index.ts
#	src/store/modules/auth/type.ts
#	src/store/modules/user/index.ts
#	src/store/modules/user/types.ts
#	src/utils/excel.ts
#	src/views/dashboard/workplace/components/banner.vue
#	src/views/dashboard/workplace/components/data-panel.vue
#	src/views/dashboard/workplace/index.vue
#	src/views/dashboard/workplace/locale/en-US.ts
#	src/views/dashboard/workplace/locale/zh-CN.ts
#	src/views/exception/403/index.vue
#	src/views/exception/404/index.vue
#	src/views/exception/500/index.vue
#	src/views/login/components/banner.vue
#	src/views/login/components/login-form.vue
#	src/views/login/index.vue
#	src/views/login/locale/en-US.ts
#	src/views/login/locale/zh-CN.ts
#	src/views/system/authority/components/auth-edit.vue
#	src/views/system/authority/components/auth-table.vue
#	src/views/system/authority/index.vue
#	src/views/system/authority/locale/en-US.ts
#	src/views/system/authority/locale/zh-CN.ts
#	src/views/system/dept/components/dept-edit.vue
#	src/views/system/dept/index.vue
#	src/views/system/dept/locale/en-US.ts
#	src/views/system/dept/locale/zh-CN.ts
#	src/views/system/permission/index.vue
#	src/views/system/role/components/role-edit.vue
#	src/views/system/role/index.vue
#	src/views/system/role/locale/en-US.ts
#	src/views/system/role/locale/zh-CN.ts
#	src/views/system/user/components/user-edit.vue
#	src/views/system/user/index.vue
#	src/views/system/user/locale/en-US.ts
#	src/views/system/user/locale/zh-CN.ts
#	src/views/user/setting/components/basic-information.vue
#	src/views/user/setting/components/password-reset.vue
#	src/views/user/setting/components/user-panel.vue
#	src/views/user/setting/index.vue
#	src/views/user/setting/locale/en-US.ts
#	src/views/user/setting/locale/zh-CN.ts
This commit is contained in:
vertoryao 2024-12-27 10:32:25 +08:00
commit 309dffa60b
37 changed files with 15546 additions and 913 deletions

View File

@ -1,3 +1,2 @@
VITE_API_URL= '' # VITE_API_BASE_URL= 'http://localhost:8080'
VITE_API_BASE_URL= '/'

View File

@ -36,7 +36,7 @@ module.exports = {
}, },
}, },
rules: { rules: {
'prettier/prettier': 0, 'prettier/prettier': 1,
// Vue: Recommended rules to be closed or modify // Vue: Recommended rules to be closed or modify
'vue/require-default-prop': 0, 'vue/require-default-prop': 0,
'vue/singleline-html-element-content-newline': 0, 'vue/singleline-html-element-content-newline': 0,

10
.gitignore vendored
View File

@ -3,8 +3,10 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
node_modules
.DS_Store
dist
dist-ssr
*.local
.idea .idea
.husky yarn.lock
*.log
*-lock.json
yarn.lock

10
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
.idea/
yarn.lock

View File

@ -1,16 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8" /> <link
<link rel="icon" href="./src/assets/objectNetwork.png" type="image/x-icon"> rel="shortcut icon"
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> type="image/x-icon"
<title>物联网网关系统</title> href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico"
</head> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<body> <title>物联网网关系统</title>
<div id="app"></div> </head>
<script type="module" src="/src/main.ts"></script> <body>
</body> <div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html> </html>

View File

@ -2,11 +2,11 @@ import axios from 'axios';
export interface AuthCreateRecord { export interface AuthCreateRecord {
name: string; name: string;
// dataScope: string; // dataScope: string;
// permissionIds: (number | undefined)[]; // permissionIds: (number | undefined)[];
remark: string; remark: string;
enabled: boolean; enabled: boolean;
// authorities: (number | undefined)[]; // authorities: (number | undefined)[];
} }
// 基础信息 // 基础信息
@ -21,35 +21,35 @@ export interface AuthRecord extends AuthCreateRecord {
// 查询所有的权限列表 // 查询所有的权限列表
export function queryAuthList(data: any) { export function queryAuthList(data: any) {
return axios({ return axios({
url: '/api/rest/authority', // 路径 url: '/api/rest/auth', // 路径
method: 'get', method: 'get',
params: data, // 参数 params: data, // 参数
}) });
} }
// 切换启用状态 // 切换启用状态
export function enabled(id: string) { export function enabled(id: string) {
return axios.patch(`/api/rest/authority/toggle/${id}`); return axios.patch(`/api/rest/auth/toggle/${id}`);
} }
// 删除 // 删除
export function remove(id: string) { export function remove(id: string) {
return axios.delete(`/api/rest/authority/${id}`); return axios.delete(`/api/rest/auth/${id}`);
} }
// 添加 // 添加
export function create(data: AuthCreateRecord) { export function create(data: AuthCreateRecord) {
return axios.post(`/api/rest/authority`, data); return axios.post(`/api/rest/auth`, data);
} }
// 更新 // 更新
export function update(data: AuthRecord) { export function update(data: AuthRecord) {
return axios.patch(`/api/rest/authority/${data.id}`, data); return axios.patch(`/api/rest/auth/${data.id}`, data);
} }
// 获取详情 // 获取详情
export function getDetail(id: string) { export function getDetail(id: string) {
return axios.get<AuthRecord>(`/api/rest/authority/${id}`); return axios.get<AuthRecord>(`/api/rest/auth/${id}`);
} }
// export function queryRoles(params?: ListParams<Partial<RoleRecord>>) { // export function queryRoles(params?: ListParams<Partial<RoleRecord>>) {

View File

@ -29,9 +29,8 @@ export interface PasswordReSetModel {
// 添加用户数据 // 添加用户数据
export interface CreateRecord { export interface CreateRecord {
value: any; value: any;
code: any; code: any;
username: string; username: string;
nickName: string; nickName: string;
password: string; password: string;
@ -40,7 +39,7 @@ code: any;
enabled: string; enabled: string;
address: string; address: string;
deptId: DeptRecord | undefined; deptId: DeptRecord | undefined;
roleId: string| RoleRecord | undefined; roleId: string | RoleRecord | undefined;
permissionIds: (number | undefined)[]; permissionIds: (number | undefined)[];
authorities: string[]; authorities: string[];
} }
@ -58,13 +57,13 @@ export interface UserRecord extends CreateRecord {
value: any; value: any;
id: string; id: string;
avatar: string; avatar: string;
createAt: string createAt: string;
} }
export interface UserParams extends Partial<UserRecord> { export interface UserParams extends Partial<UserRecord> {
page: number; page: number;
size: number; size: number;
current: number current: number;
} }
export interface Pageable { export interface Pageable {
@ -128,11 +127,10 @@ export function queryUserList(params: any) {
} }
// 根据id查询用户信息 // 根据id查询用户信息
export function userDetail(id: string){ export function userDetail(id: string) {
return axios.get(`/api/rest/user/${id}`) return axios.get(`/api/rest/user/${id}`);
} }
// 是否启用 // 是否启用
export function enabled(id: string) { export function enabled(id: string) {
return axios.patch(`/api/rest/user/${id}/toggle`); return axios.patch(`/api/rest/user/${id}/toggle`);
@ -158,15 +156,15 @@ export function getUserInfo() {
} }
// 部门的审核员 // 部门的审核员
export function deptAudit(id: string,roleId:string){ export function deptAudit(id: string, roleId: string) {
return axios({ return axios({
url: `/api/rest/user/dept/${id}?roleId=${roleId}`, // 路径 url: `/api/rest/user/dept/${id}?roleId=${roleId}`, // 路径
method: 'get', method: 'get',
}); });
} }
// 获取验证码 // 获取验证码
export function code(data: string){ export function code(data: string) {
return axios.get(`/api/rest/user/send-email?email=${data}`); return axios.get(`/api/rest/user/send-email?email=${data}`);
} }
@ -179,6 +177,5 @@ export function getUserDetail(id: number) {
} }
export function getMenuList() { export function getMenuList() {
return axios.post<RouteRecordNormalized[]>('/api/user/menu'); return axios.get<RouteRecordNormalized[]>('/api/rest/user/menu');
} }

File diff suppressed because one or more lines are too long

View File

@ -77,7 +77,7 @@
itemData: { itemData: {
type: Object as PropType<TagProps>, type: Object as PropType<TagProps>,
default() { default() {
return []; return {};
}, },
}, },
index: { index: {

View File

@ -12,6 +12,6 @@
"globalSettings": false, "globalSettings": false,
"device": "desktop", "device": "desktop",
"tabBar": false, "tabBar": false,
"menuFromServer": false, "menuFromServer": true,
"serverMenu": [] "serverMenu": []
} }

View File

@ -8,10 +8,9 @@ function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
const { authorities } = userStore; const { authorities } = userStore;
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (value.length > 0) { if (value.length > 0) {
const hasPermission = intersection(value, authorities).length > 0; const hasPermission = intersection(value, authorities).length > 0;
if (!hasPermission && el.parentNode) { if (!hasPermission && el.parentNode) {
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
} }

View File

@ -6,32 +6,33 @@ export default function usePermission() {
const userStore = useUserStore(); const userStore = useUserStore();
return { return {
accessRouter(route: RouteLocationNormalized | RouteRecordRaw) { accessRouter(route: RouteLocationNormalized | RouteRecordRaw) {
return ( return (
!route.meta?.requiresAuth || !route.meta?.requiresAuth ||
!route.meta?.permissions || !route.meta?.permissions ||
route.meta?.permissions?.includes('*') || route.meta?.permissions?.includes('*') ||
intersection(route.meta?.permissions, userStore.permissions).length > 0 intersection(route.meta?.permissions, userStore.permissions).length >
|| route.meta?.permissions?.includes(userStore.permissions) 0 ||
route.meta?.permissions?.some((item) =>
userStore?.permissions?.includes(item)
)
); );
}, },
// TODO 不知道是干嘛的 findFirstPermissionRoute(_routers: any, permissions: string[]) {
// findFirstPermissionRoute(_routers: any, role: string | string[] = 'admin') { const cloneRouters = [..._routers];
// const cloneRouters = [..._routers]; while (cloneRouters.length) {
// while (cloneRouters.length) { const firstElement = cloneRouters.shift();
// const firstElement = cloneRouters.shift(); if (
// if ( firstElement?.meta?.roles?.find((el: string[]) => {
// firstElement?.meta?.roles?.find((el: string[]) => { return el.includes('*') || el.some((item) => permissions?.includes(item))
// return el.includes('*') || el.includes(role); })
// }) )
// ) return { name: firstElement.name };
// return { name: firstElement.name }; if (firstElement?.children) {
// if (firstElement?.children) { cloneRouters.push(...firstElement.children);
// cloneRouters.push(...firstElement.children); }
// } }
// } return null;
// return null; },
// },
// You can add any rules you want // You can add any rules you want
}; };
} }

View File

@ -5,10 +5,21 @@ import './message-box';
import '@/views/dashboard/workplace/mock'; import '@/views/dashboard/workplace/mock';
import '@/views/dashboard/monitor/mock';
import '@/views/list/card/mock';
import '@/views/list/search-table/mock';
import '@/views/form/step/mock';
import '@/views/profile/basic/mock';
import '@/views/visualization/data-analysis/mock';
import '@/views/visualization/multi-dimension-data-analysis/mock';
import '@/views/user/info/mock';
import '@/views/user/setting/mock'; import '@/views/user/setting/mock';
Mock.setup({ Mock.setup({
timeout: '600-1000', timeout: '600-1000',
}); });

View File

@ -2,15 +2,18 @@ import type { Router, RouteRecordNormalized } from 'vue-router';
import NProgress from 'nprogress'; // progress bar import NProgress from 'nprogress'; // progress bar
import usePermission from '@/hooks/permission'; import usePermission from '@/hooks/permission';
import { useAppStore } from '@/store'; import { useAppStore, useUserStore } from '@/store';
import { appRoutes } from '../routes';
import { WHITE_LIST, NOT_FOUND } from '../constants'; import { WHITE_LIST, NOT_FOUND } from '../constants';
export default function setupPermissionGuard(router: Router) { export default function setupPermissionGuard(router: Router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore();
const Permission = usePermission(); const Permission = usePermission();
const permissionsAllow = Permission.accessRouter(to); const permissionsAllow = Permission.accessRouter(to);
if (appStore.menuFromServer) { if (appStore.menuFromServer) {
// 针对来自服务端的菜单配置进行处理 // 针对来自服务端的菜单配置进行处理
// Handle routing configuration from the server // Handle routing configuration from the server
@ -44,8 +47,10 @@ export default function setupPermissionGuard(router: Router) {
if (permissionsAllow) next(); if (permissionsAllow) next();
else { else {
const destination = const destination =
// Permission.findFirstPermissionRoute(appRoutes, userStore.permissions) || Permission.findFirstPermissionRoute(
NOT_FOUND; appRoutes,
userStore.permissions
) || NOT_FOUND;
next(destination); next(destination);
} }
} }

View File

@ -14,7 +14,6 @@ export default function setupUserLoginInfoGuard(router: Router) {
} else { } else {
try { try {
await userStore.info(); await userStore.info();
next(); next();
} catch (error) { } catch (error) {
await userStore.logout(); await userStore.logout();

View File

@ -7,7 +7,6 @@ const DASHBOARD: AppRouteRecordRaw = {
component: DEFAULT_LAYOUT, component: DEFAULT_LAYOUT,
meta: { meta: {
locale: 'menu.dashboard', locale: 'menu.dashboard',
title: '首页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-dashboard', // 设置图标 icon: 'icon-dashboard', // 设置图标
order: 0, // 排序路由菜单项。如果设置该值,值越高,越靠前 order: 0, // 排序路由菜单项。如果设置该值,值越高,越靠前
@ -18,7 +17,7 @@ const DASHBOARD: AppRouteRecordRaw = {
name: 'Workplace', name: 'Workplace',
component: () => import('@/views/dashboard/workplace/index.vue'), component: () => import('@/views/dashboard/workplace/index.vue'),
meta: { meta: {
title: '工作台', locale: 'menu.dashboard.workplace',
requiresAuth: true, requiresAuth: true,
permissions: ['*'], permissions: ['*'],
}, },

View File

@ -0,0 +1,37 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const NOTIFICATION: AppRouteRecordRaw = {
path: '/notification',
name: 'notification',
component: DEFAULT_LAYOUT,
meta: {
locale: '通知管理',
requiresAuth: true,
icon: 'icon-message', // 设置图标
order: 0, // 排序路由菜单项。如果设置该值,值越高,越靠前
},
children: [
{
path: 'notice',
name: 'notice',
component: () => import('@/views/notification/notice/index.vue'),
meta: {
locale: '公告通知',
requiresAuth: true,
permissions: ['*'],
},
},
{
path: 'noticeSet',
name: 'noticeSet',
component: () => import('@/views/notification/noticeSet/index.vue'),
meta: {
locale: '公告管理',
requiresAuth: true,
permissions: ['*'],
},
},
],
};
export default NOTIFICATION;

View File

@ -27,8 +27,6 @@ const SYSTEM: AppRouteRecordRaw = {
name: 'Role', name: 'Role',
component: () => import('@/views/system/role/index.vue'), component: () => import('@/views/system/role/index.vue'),
meta: { meta: {
// locale: 'menu.system.role',
title: '角色管理',
requiresAuth: true, requiresAuth: true,
permissions: ['*'], permissions: ['*'],
}, },
@ -38,8 +36,6 @@ const SYSTEM: AppRouteRecordRaw = {
name: 'Dept', name: 'Dept',
component: () => import('@/views/system/dept/index.vue'), component: () => import('@/views/system/dept/index.vue'),
meta: { meta: {
// locale: 'menu.system.dept',
title: '部门管理',
requiresAuth: true, requiresAuth: true,
permissions: ['*'], permissions: ['*'],
}, },
@ -49,19 +45,14 @@ const SYSTEM: AppRouteRecordRaw = {
name: 'User', name: 'User',
component: () => import('@/views/system/user/index.vue'), component: () => import('@/views/system/user/index.vue'),
meta: { meta: {
// locale: 'menu.system.user',
title: '用户管理',
requiresAuth: true, requiresAuth: true,
permissions: ['*'], permissions: ['*'],
}, },
}, },
{ {
path: 'authority', path: 'authority',
name: 'Authority',
component: () => import('@/views/system/authority/index.vue'), component: () => import('@/views/system/authority/index.vue'),
meta: { meta: {
// locale: '权限管理',
title: '权限管理',
requiresAuth: true, requiresAuth: true,
permissions: ['*'], permissions: ['*'],
}, },

View File

@ -2,8 +2,8 @@ import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types'; import { AppRouteRecordRaw } from '../types';
const USER: AppRouteRecordRaw = { const USER: AppRouteRecordRaw = {
path: '/user', path: '/self',
name: 'user', name: 'Self',
component: DEFAULT_LAYOUT, component: DEFAULT_LAYOUT,
meta: { meta: {
locale: 'menu.user', locale: 'menu.user',

View File

@ -6,10 +6,6 @@ import useTabBarStore from './modules/tab-bar';
import useRoleStore from './modules/role'; import useRoleStore from './modules/role';
import useDeptStore from './modules/dept'; import useDeptStore from './modules/dept';
import useAuthStore from './modules/auth'; import useAuthStore from './modules/auth';
import useBulletinsStore from './modules/bulletins';
import useBulletinStore from './modules/bulle-mgmt';
import useMessagesStore from './modules/messages';
import useMessageStore from './modules/message-mgmt';
const pinia = createPinia(); const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); pinia.use(piniaPluginPersistedstate);
@ -18,12 +14,8 @@ export {
useAppStore, useAppStore,
useUserStore, useUserStore,
useTabBarStore, useTabBarStore,
useRoleStore,
useDeptStore, useDeptStore,
useRoleStore,
useAuthStore, useAuthStore,
useBulletinsStore,
useBulletinStore,
useMessagesStore,
useMessageStore,
}; };
export default pinia; export default pinia;

View File

@ -1,52 +1,55 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { import {
AuthCreateRecord, AuthCreateRecord,
AuthRecord, AuthRecord,
create, create,
queryAuthList, queryAuthList,
enabled, enabled,
remove, remove,
update, update,
getDetail getDetail,
} from '@/api/authority'; } from '@/api/authority';
import { authStore } from './type'; import { authStore } from './type';
const useAuthStore = defineStore('auth', { const useAuthStore = defineStore('auth', {
state: (): authStore => ({ state: (): authStore => ({
id: undefined, id: undefined,
remark: undefined, remark: undefined,
// createTime: undefined, // createTime: undefined,
name: undefined, name: undefined,
enabled: undefined, enabled: undefined,
}), }),
getters: {
AuthInfo(state: authStore): authStore {
return { ...state };
},
},
actions: {
// 获取所有的角色列表
async getAuthList(data: AuthRecord) {
return queryAuthList(data);
},
async enabledAuth(id: string) {
return enabled(id);
},
async removeAuth(id: string) {
return remove(id);
},
async createAuth(params: AuthCreateRecord) {
return create(params);
},
async updateAuth(params: AuthRecord) {
return update(params);
},
},
});
export default useAuthStore; getters: {
AuthInfo(state: authStore): authStore {
return { ...state };
},
},
actions: {
// 获取所有的角色列表
async getAuthList(data: AuthRecord) {
return queryAuthList(data);
},
async enabledAuth(id: string) {
return enabled(id);
},
async removeAuth(id: string) {
return remove(id);
},
async createAuth(params: AuthCreateRecord) {
return create(params);
},
async updateAuth(params: AuthRecord) {
return update(params);
},
async getAuthDetail(id: string) {
return getDetail(id);
},
},
});
export default useAuthStore;

View File

@ -14,7 +14,7 @@ import {
update, update,
userDetail, userDetail,
deptAudit, deptAudit,
code code,
} from '@/api/user'; } from '@/api/user';
import { setToken, clearToken } from '@/utils/auth'; import { setToken, clearToken } from '@/utils/auth';
import { removeRouteListener } from '@/utils/route-listener'; import { removeRouteListener } from '@/utils/route-listener';
@ -23,6 +23,7 @@ import useAppStore from '../app';
const useUserStore = defineStore('user', { const useUserStore = defineStore('user', {
state: (): UserState => ({ state: (): UserState => ({
id: undefined,
username: undefined, username: undefined,
nickName: undefined, nickName: undefined,
avatar: undefined, avatar: undefined,
@ -31,7 +32,6 @@ const useUserStore = defineStore('user', {
address: undefined, address: undefined,
createAt: undefined, createAt: undefined,
remark: undefined, remark: undefined,
id: undefined,
role: undefined, role: undefined,
roles: undefined, roles: undefined,
permissions: [], permissions: [],
@ -46,7 +46,7 @@ const useUserStore = defineStore('user', {
actions: { actions: {
// Set user's information // Set user's information
setInfo(partial: Partial<UserState>) { setInfo(partial: Partial<UserState>) {
this.$patch(partial); this.$patch(partial);
}, },
@ -56,10 +56,10 @@ const useUserStore = defineStore('user', {
}, },
async info() { async info() {
const res = await getUserInfo(); const res = await me();
res.data.user.permissions = res.data.permissions; // res.data.user.permissions = res.data.permissions;
res.data.user.authorities = res.data.authorities; // res.data.user.authorities = res.data.authorities;
this.setInfo(res.data.user); this.setInfo(res.data.user);
}, },
// Get user's crsf // Get user's crsf
@ -71,7 +71,9 @@ const useUserStore = defineStore('user', {
async login(loginForm: LoginData, token: string) { async login(loginForm: LoginData, token: string) {
try { try {
setToken(token); setToken(token);
await userLogin(loginForm); // await userLogin(loginForm);
const res = await userLogin(loginForm);
this.setInfo(res.data.user);
} catch (err) { } catch (err) {
clearToken(); clearToken();
throw err; throw err;
@ -108,16 +110,16 @@ const useUserStore = defineStore('user', {
return update(data); return update(data);
}, },
async getUserDetail(id: string){ async getUserDetail(id: string) {
return userDetail(id); return userDetail(id);
}, },
async getDeptAudit(deptId: string,roleId: string){ async getDeptAudit(deptId: string, roleId: string) {
return deptAudit(deptId,roleId) return deptAudit(deptId, roleId);
}, },
async getCode(params: string){ async getCode(params: string) {
return code(params) return code(params);
}, },
logoutCallBack() { logoutCallBack() {

View File

@ -13,6 +13,7 @@ export interface UserState {
id?: number; id?: number;
role?: RoleRecord; role?: RoleRecord;
roles?: RoleRecord[]; roles?: RoleRecord[];
permissions?: string[] | '' | '*' | 'admin' | 'user'|'auditor'; // permissions?: string[] | '' | '*' | 'admin' | 'user' | 'auditor';
authorities?: string[] permissions?: string[];
authorities?: string[];
} }

View File

@ -1,67 +1,68 @@
// 新建 @/utils/excel.ts // // 新建 @/utils/excel.ts
//
import saveAs from 'file-saver'; // https://www.npmjs.com/package/file-saver // import saveAs from 'file-saver'; // https://www.npmjs.com/package/file-saver
import ExcelJS from 'exceljs'; // https://github.com/exceljs/exceljs/blob/master/README_zh.md // import ExcelJS from 'exceljs'; // https://github.com/exceljs/exceljs/blob/master/README_zh.md
import * as XLSX from 'xlsx'; // https://www.npmjs.com/package/xlsx // import * as XLSX from 'xlsx'; // https://www.npmjs.com/package/xlsx
import { Message } from '@arco-design/web-vue'; // https://arco.design/vue/component/message // import { Message } from '@arco-design/web-vue'; // https://arco.design/vue/component/message
import { FileItem } from '@arco-design/web-vue/es/upload/interfaces'; // arco类型 // import { FileItem } from '@arco-design/web-vue/es/upload/interfaces'; // arco类型
//
export interface DownloadExcelPrams { // export interface DownloadExcelPrams {
columns: { title: string, key: string }[]; // columns: { title: string; key: string }[];
rows: object[]; // rows: object[];
name: string // name: string;
} // }
//
// 导出下载文件 // // 导出下载文件
export function downloadExcel({ columns, rows, name = '未命名文件' }: DownloadExcelPrams) { // export function downloadExcel({
// columns,
const workbook = new ExcelJS.Workbook(); // rows,
workbook.creator = 'Start-front'; // name = '未命名文件',
workbook.lastModifiedBy = 'Start-front'; // }: DownloadExcelPrams) {
workbook.created = new Date(1985, 8, 30); // const workbook = new ExcelJS.Workbook();
workbook.modified = new Date(); // workbook.creator = 'Start-front';
workbook.lastPrinted = new Date(2016, 9, 27); // workbook.lastModifiedBy = 'Start-front';
// workbook.created = new Date(1985, 8, 30);
// 将工作簿添加一个sheet页sheet1 // workbook.modified = new Date();
const sheet1 = workbook.addWorksheet(name); // workbook.lastPrinted = new Date(2016, 9, 27);
// 表头数据添加 //
sheet1.columns = columns.map(item => ({ // // 将工作簿添加一个sheet页sheet1
header: item.title, // const sheet1 = workbook.addWorksheet(name);
key: item.key, // // 表头数据添加
width: 20 // sheet1.columns = columns.map((item) => ({
})); // header: item.title,
// 表格内容添加 // key: item.key,
rows.map(item => sheet1.addRow(item)); // width: 20,
workbook.xlsx.writeBuffer().then(buffer => { // }));
saveAs( // // 表格内容添加
new Blob([buffer], { type: 'application/octet-stream' }), // rows.map((item) => sheet1.addRow(item));
`${name}.xlsx` // workbook.xlsx.writeBuffer().then((buffer) => {
); // saveAs(
}); // new Blob([buffer], { type: 'application/octet-stream' }),
}; // `${name}.xlsx`
// );
// });
// 读取文件为json格式 // }
export function readExcle(fileItem:FileItem) { //
return new Promise((resove,reject)=>{ // // 读取文件为json格式
try { // export function readExcle(fileItem: FileItem) {
let workbook:XLSX.Sheet; // return new Promise((resove, reject) => {
const reader = new FileReader(); // try {
reader.readAsBinaryString(fileItem.file as File); // 发起异步请求 // let workbook: XLSX.Sheet;
reader.onload = function(ev){ // const reader = new FileReader();
const data = ev.target?.result; // reader.readAsBinaryString(fileItem.file as File); // 发起异步请求
workbook = XLSX.read(data, {type: 'binary'}); // reader.onload = function (ev) {
const sheetNames = workbook.SheetNames; // 工作表名称集合 // const data = ev.target?.result;
sheetNames.forEach((name:string) => { // workbook = XLSX.read(data, { type: 'binary' });
const worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表 // const sheetNames = workbook.SheetNames; // 工作表名称集合
const jsonres = XLSX.utils.sheet_to_json(worksheet); // sheetNames.forEach((name: string) => {
resove(jsonres) // const worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表
}); // const jsonres = XLSX.utils.sheet_to_json(worksheet);
} // onload // resove(jsonres);
} catch (error) { // });
Message.error('读取失败,请选择正确文件'); // }; // onload
reject(error); // } catch (error) {
} // Message.error('读取失败,请选择正确文件');
}) // reject(error);
} // }
// });
// }

View File

@ -1,108 +1,150 @@
<template> <template>
<a-grid :cols="24" :row-gap="16" class="panel"> <div style="width: 100%">
<a-grid-item <a-grid :cols="24" :row-gap="16" class="panel">
class="panel-col" <a-grid-item
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }" class="panel-col"
> :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
<a-space> >
<a-avatar :size="54" class="col-avatar"> <a-space @click="to('PASS')">
<img <a-avatar :size="54" class="col-avatar">
alt="avatar" <img
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/288b89194e657603ff40db39e8072640.svg~tplv-49unhts6dw-image.image" alt="avatar"
/> src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/288b89194e657603ff40db39e8072640.svg~tplv-49unhts6dw-image.image"
</a-avatar> />
<a-statistic </a-avatar>
:title="$t('workplace.onlineContent')" <a-statistic
:value="373.5" :title="$t('workplace.pass')"
:precision="1" :value="formData.pass || 0"
:value-from="0" :value-from="0"
animation show-group-separator
show-group-separator >
> <template #suffix>
<template #suffix> <span class="unit">{{ $t('workplace.pecs') }}</span>
W+ <span class="unit">{{ $t('workplace.pecs') }}</span> </template>
</template> </a-statistic>
</a-statistic> </a-space>
</a-space> </a-grid-item>
</a-grid-item> <a-grid-item
<a-grid-item class="panel-col"
class="panel-col" :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }" >
> <a-space @click="to('FAILED')">
<a-space> <a-avatar :size="54" class="col-avatar">
<a-avatar :size="54" class="col-avatar"> <img
<img alt="avatar"
alt="avatar" src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/fdc66b07224cdf18843c6076c2587eb5.svg~tplv-49unhts6dw-image.image"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/fdc66b07224cdf18843c6076c2587eb5.svg~tplv-49unhts6dw-image.image" />
/> </a-avatar>
</a-avatar> <a-statistic
<a-statistic :title="$t('workplace.notPass')"
:title="$t('workplace.putIn')" :value="formData.notPass || 0"
:value="368" :value-from="0"
:value-from="0" show-group-separator
animation >
show-group-separator <template #suffix>
> <span class="unit">{{ $t('workplace.pecs') }}</span>
<template #suffix> </template>
<span class="unit">{{ $t('workplace.pecs') }}</span> </a-statistic>
</template> </a-space>
</a-statistic> </a-grid-item>
</a-space> <a-grid-item
</a-grid-item> class="panel-col"
<a-grid-item :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
class="panel-col" >
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }" <a-space @click="to('EXAMINE')">
> <a-avatar :size="54" class="col-avatar">
<a-space> <img
<a-avatar :size="54" class="col-avatar"> alt="avatar"
<img src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/77d74c9a245adeae1ec7fb5d4539738d.svg~tplv-49unhts6dw-image.image"
alt="avatar" />
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/77d74c9a245adeae1ec7fb5d4539738d.svg~tplv-49unhts6dw-image.image" </a-avatar>
/> <a-statistic
</a-avatar> :title="$t('workplace.notAudit')"
<a-statistic :value="formData.notAudit || 0"
:title="$t('workplace.newDay')" :value-from="0"
:value="8874" show-group-separator
:value-from="0" >
animation <template #suffix>
show-group-separator <span class="unit">{{ $t('workplace.pecs') }}</span>
> </template>
<template #suffix> </a-statistic>
<span class="unit">{{ $t('workplace.pecs') }}</span> </a-space>
</template> </a-grid-item>
</a-statistic> <a-grid-item
</a-space> v-if="userStore.permissions !== 'auditor'"
</a-grid-item> class="panel-col"
<a-grid-item :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
class="panel-col" style="border-right: none"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }" >
style="border-right: none" <a-space @click="to('SUBMIT')">
> <a-avatar :size="54" class="col-avatar">
<a-space> <img
<a-avatar :size="54" class="col-avatar"> alt="avatar"
<img src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/c8b36e26d2b9bb5dbf9b74dd6d7345af.svg~tplv-49unhts6dw-image.image"
alt="avatar" />
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/c8b36e26d2b9bb5dbf9b74dd6d7345af.svg~tplv-49unhts6dw-image.image" </a-avatar>
/> <a-statistic
</a-avatar> v-model="formData.notFiled"
<a-statistic :title="$t('workplace.notFiled')"
:title="$t('workplace.newFromYesterday')" :value="formData.notFiled || 0"
:value="2.8" :value-from="0"
:precision="1" show-group-separator
:value-from="0" >
animation <template #suffix>
> <span class="unit">{{ $t('workplace.pecs') }}</span>
<template #suffix> % <icon-caret-up class="up-icon" /> </template> </template>
</a-statistic> </a-statistic>
</a-space> </a-space>
</a-grid-item> </a-grid-item>
<a-grid-item :span="24"> <a-grid-item :span="24">
<a-divider class="panel-border" /> <a-divider class="panel-border" />
</a-grid-item> </a-grid-item>
</a-grid> </a-grid>
<a-card :bordered="false">
<a-space id="TicketEcharts" style="width: 98%; height: 400px"></a-space>
</a-card>
</div>
</template> </template>
<script lang="ts" setup></script> <script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import { useUserStore } from '@/store';
import { onMounted, ref } from 'vue';
import * as echarts from 'echarts';
import router from '@/router';
const { t } = useI18n();
const userStore = useUserStore();
const formData = ref({
notAudit: undefined,
notFiled: undefined,
notPass: undefined,
pass: undefined,
});
const getHomeData = async (params: {
auditorId: number | undefined;
userId: number | string | undefined;
}) => {
if (userStore.permissions === 'admin') {
params.userId = '';
} else if (userStore.permissions === 'auditor') {
params.auditorId = userStore.id;
} else if (userStore.permissions === 'user') {
params.userId = userStore.id;
}
};
const to = (key: string) => {
router.push({
name: 'TicketManage',
query: {
status: key,
},
});
};
getHomeData({});
</script>
<style lang="less" scoped> <style lang="less" scoped>
.arco-grid.panel { .arco-grid.panel {

View File

@ -15,70 +15,70 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import bannerImage from '@/assets/images/login-banner.png'; import bannerImage from '@/assets/images/login-banner.png';
const { t } = useI18n(); const { t } = useI18n();
const carouselItem = computed(() => [ const carouselItem = computed(() => [
{ {
slogan: t('login.banner.slogan1'), slogan: t('login.banner.slogan1'),
subSlogan: t('login.banner.subSlogan1'), subSlogan: t('login.banner.subSlogan1'),
image: bannerImage, image: bannerImage,
}, },
{ {
slogan: t('login.banner.slogan2'), slogan: t('login.banner.slogan2'),
subSlogan: t('login.banner.subSlogan2'), subSlogan: t('login.banner.subSlogan2'),
image: bannerImage, image: bannerImage,
}, },
{ {
slogan: t('login.banner.slogan3'), slogan: t('login.banner.slogan3'),
subSlogan: t('login.banner.subSlogan3'), subSlogan: t('login.banner.subSlogan3'),
image: bannerImage, image: bannerImage,
}, },
]); ]);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.banner { .banner {
display: flex;
align-items: center;
justify-content: center;
&-inner {
flex: 1;
height: 100%;
}
}
.carousel {
height: 100%;
&-item {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&-inner {
flex: 1;
height: 100%;
}
}
.carousel {
height: 100%; height: 100%;
}
&-title { &-item {
color: var(--color-fill-1); display: flex;
font-weight: 500; flex-direction: column;
font-size: 20px; align-items: center;
line-height: 28px; justify-content: center;
} height: 100%;
}
&-sub-title { &-title {
margin-top: 8px; color: var(--color-fill-1);
color: var(--color-text-3); font-weight: 500;
font-size: 14px; font-size: 20px;
line-height: 22px; line-height: 28px;
} }
&-image { &-sub-title {
width: 320px; margin-top: 8px;
margin-top: 30px; color: var(--color-text-3);
font-size: 14px;
line-height: 22px;
}
&-image {
width: 320px;
margin-top: 30px;
}
} }
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="login-form-wrapper"> <div class="login-form-wrapper">
<div class="login-form-title">物联网网关系统</div> <div class="login-form-title">{{ $t('login.form.title') }}</div>
<!-- <div class="login-form-sub-title">{{ $t('login.form.title') }}</div>--> <!-- <div class="login-form-sub-title">{{ $t('login.form.title') }}</div>-->
<!-- <div class="login-form-sub-title">请先登录</div>--> <!-- <div class="login-form-sub-title">请先登录</div>-->
<div class="login-form-error-msg">{{ errorMessage }}</div> <div class="login-form-error-msg">{{ errorMessage }}</div>
@ -170,9 +170,9 @@
<a-button <a-button
type="primary" type="primary"
style="margin-left: 50px; margin-right: 50px" style="margin-left: 50px; margin-right: 50px"
@click="getCodeData"
:disabled="flag" :disabled="flag"
> @click="getCodeData"
>
<!-- 发送验证码 --> <!-- 发送验证码 -->
<div v-if="flag">({{ totalCount }}s)</div> <div v-if="flag">({{ totalCount }}s)</div>
</a-button> </a-button>
@ -182,195 +182,195 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { FormInstance } from '@arco-design/web-vue/es/form'; import { FormInstance } from '@arco-design/web-vue/es/form';
import { ref, reactive } from 'vue'; import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { ValidatedError } from '@arco-design/web-vue/es/form/interface'; import { ValidatedError } from '@arco-design/web-vue/es/form/interface';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { useUserStore } from '@/store'; import { useUserStore } from '@/store';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { LoginData, CreateRecord } from '@/api/user'; import { LoginData, CreateRecord } from '@/api/user';
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const errorMessage = ref(''); const errorMessage = ref('');
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const userStore = useUserStore(); const userStore = useUserStore();
const { visible, setVisible } = useVisible(false); const { visible, setVisible } = useVisible(false);
const userCreateRef = ref<FormInstance>(); const userCreateRef = ref<FormInstance>();
const modalTitle = t('create.user'); const modalTitle = t('create.user');
const formData = ref<CreateRecord>({ const formData = ref<CreateRecord>({
username: '', username: '',
nickName: '', nickName: '',
password: '', password: '',
phone: '', phone: '',
email: '@qq.com', email: '@qq.com',
enabled: 'true', enabled: 'true',
address: '', address: '',
deptId: 235, deptId: 235,
roleId: undefined, roleId: undefined,
permissionIds: [], permissionIds: [],
authorities: [], authorities: [],
code: '', code: '',
}); });
const flag = ref(false); const flag = ref(false);
const totalCount = ref(60); const totalCount = ref(60);
const loginConfig = useStorage('login-config', { const loginConfig = useStorage('login-config', {
// 使 useStorage // 使 useStorage
rememberPassword: true, rememberPassword: true,
username: '', // username: '', //
password: '', // demo default value password: '', // demo default value
}); });
const userInfo = reactive({ const userInfo = reactive({
// loginConfig username password // loginConfig username password
username: loginConfig.value.username, username: loginConfig.value.username,
password: loginConfig.value.password, password: loginConfig.value.password,
}); });
// //
const handleSubmit = async ({ const handleSubmit = async ({
// //
errors, errors,
values, values,
}: { }: {
errors: Record<string, ValidatedError> | undefined; errors: Record<string, ValidatedError> | undefined;
values: Record<string, any>; values: Record<string, any>;
}) => { }) => {
// //
if (loading.value) return; if (loading.value) return;
// //
if (!errors) { if (!errors) {
setLoading(true); setLoading(true);
try { try {
const res = await userStore.me(); const res = await userStore.me();
await userStore.login(values as LoginData, res.data.csrf.token); await userStore.login(values as LoginData, res.data.csrf.token);
await userStore.me(); // await userStore.me();
await userStore.info(); // await userStore.info();
// //
const { ...othersQuery } = router.currentRoute.value.query; const { ...othersQuery } = router.currentRoute.value.query;
router.push({ router.push({
name: 'Workplace', name: 'Workplace',
query: { query: {
...othersQuery, ...othersQuery,
}, },
}); });
// //
Message.success(t('login.form.login.success')); Message.success(t('login.form.login.success'));
const { rememberPassword } = loginConfig.value; const { rememberPassword } = loginConfig.value;
const { username, password } = values; const { username, password } = values;
// //
// The actual production environment requires encrypted storage. // The actual production environment requires encrypted storage.
loginConfig.value.username = rememberPassword ? username : ''; loginConfig.value.username = rememberPassword ? username : '';
loginConfig.value.password = rememberPassword ? password : ''; loginConfig.value.password = rememberPassword ? password : '';
} catch (err) { } catch (err) {
// errorMessage.value = (err as Error).message; // errorMessage.value = (err as Error).message;
errorMessage.value = t('login.form.login.errMsg'); errorMessage.value = t('login.form.login.errMsg');
} finally { } finally {
setLoading(false); setLoading(false);
}
} }
} };
};
// //
const setRememberPassword = (value: boolean) => { const setRememberPassword = (value: boolean) => {
loginConfig.value.rememberPassword = value; loginConfig.value.rememberPassword = value;
}; };
// //
const handleClick = () => { const handleClick = () => {
setVisible(true); setVisible(true);
}; };
// //
const handleCancel = async () => { const handleCancel = async () => {
userCreateRef.value?.resetFields();
setVisible(false);
};
//
const getCodeData = async () => {
if (formData.value.email === '') {
Message.error({
content: t('user.info.email.required'),
duration: 5 * 1000,
});
} else {
const res = await userStore.getCode(formData.value.email);
if (res.status === 200) {
flag.value = true;
setInterval(() => {
if (totalCount.value !== 0) {
totalCount.value = totalCount.value * 1 - 1;
}
}, 1000);
setTimeout(() => {
flag.value = false;
totalCount.value = 60;
}, 60000);
}
}
};
//
const handleOk = async () => {
const valid = await userCreateRef.value?.validate();
if (!valid) {
// formData.value.username = formData.value.email;
const res = await userStore.registerUser(formData.value);
if (res.status === 200) {
Message.success({
content: t('create.sucess'),
duration: 5 * 1000,
});
}
userCreateRef.value?.resetFields(); userCreateRef.value?.resetFields();
setVisible(false); setVisible(false);
} };
};
//
const getCodeData = async () => {
if (formData.value.email === '') {
Message.error({
content: t('user.info.email.required'),
duration: 5 * 1000,
});
} else {
const res = await userStore.getCode(formData.value.email);
if (res.status === 200) {
flag.value = true;
setInterval(() => {
if (totalCount.value !== 0) {
totalCount.value = totalCount.value * 1 - 1;
}
}, 1000);
setTimeout(() => {
flag.value = false;
totalCount.value = 60;
}, 60000);
}
}
};
//
const handleOk = async () => {
const valid = await userCreateRef.value?.validate();
if (!valid) {
// formData.value.username = formData.value.email;
const res = await userStore.registerUser(formData.value);
if (res.status === 200) {
Message.success({
content: t('create.sucess'),
duration: 5 * 1000,
});
}
userCreateRef.value?.resetFields();
setVisible(false);
}
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.login-form { .login-form {
&-wrapper { &-wrapper {
width: 320px; width: 320px;
} }
&-title { &-title {
color: var(--color-text-1); color: var(--color-text-1);
font-weight: 500; font-weight: 500;
font-size: 24px; font-size: 24px;
line-height: 32px; line-height: 32px;
} }
&-sub-title { &-sub-title {
color: var(--color-text-3); color: var(--color-text-3);
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
} }
&-error-msg { &-error-msg {
height: 32px; height: 32px;
color: rgb(var(--red-6)); color: rgb(var(--red-6));
line-height: 32px; line-height: 32px;
} }
&-password-actions { &-password-actions {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
&-register-btn { &-register-btn {
color: var(--color-text-3) !important; color: var(--color-text-3) !important;
}
} }
}
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="container"> <div class="container">
<div class="logo"> <div class="logo">
<div class="logo-text">Hello!</div> <div class="logo-text">Hello Ticket!</div>
</div> </div>
<LoginBanner /> <LoginBanner />
<div class="content"> <div class="content">
@ -13,61 +13,61 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import LoginBanner from './components/banner.vue'; import LoginBanner from './components/banner.vue';
import LoginForm from './components/login-form.vue'; import LoginForm from './components/login-form.vue';
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.container { .container {
display: flex;
height: 100vh;
.banner {
width: 550px;
background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
}
.content {
position: relative;
display: flex; display: flex;
flex: 1; height: 100vh;
.banner {
width: 550px;
background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
}
.content {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
padding-bottom: 40px;
}
.footer {
position: absolute;
right: 0;
bottom: 0;
width: 100%;
}
}
.logo {
position: fixed;
top: 24px;
left: 22px;
z-index: 1;
display: inline-flex;
align-items: center; align-items: center;
justify-content: center;
padding-bottom: 40px;
}
.footer { &-text {
position: absolute; margin-right: 4px;
right: 0; margin-left: 4px;
bottom: 0; color: var(--color-fill-1);
width: 100%; font-size: 20px;
}
} }
}
.logo {
position: fixed;
top: 24px;
left: 22px;
z-index: 1;
display: inline-flex;
align-items: center;
&-text {
margin-right: 4px;
margin-left: 4px;
color: var(--color-fill-1);
font-size: 20px;
}
}
</style> </style>
<style lang="less" scoped> <style lang="less" scoped>
// responsive // responsive
@media (max-width: @screen-lg) { @media (max-width: @screen-lg) {
.container { .container {
.banner { .banner {
width: 25%; width: 25%;
}
} }
} }
}
</style> </style>

View File

@ -0,0 +1,355 @@
<template>
<div class="container">
<Breadcrumb :items="['通知管理', '公告通知']" />
<a-card class="general-card" title=" ">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="right"
>
<a-row :gutter="18">
<a-col :span="9">
<a-form-item field="title" :label="$t('标题')">
<a-input
v-model="formModel.title"
style="width: 360px"
:placeholder="$t('请输入标题')"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item field="enable" :label="$t('状态')">
<a-select
v-model="formModel.states"
style="width: 360px"
:placeholder="$t('请选择状态')"
:options="statusOptions"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item field="publishTimeBegin" :label="$t('开始时间')">
<a-date-picker
v-model="formModel.publishTimeBegin"
style="width: 360px"
show-time
:time-picker-props="{ defaultValue: '09:09:06' }"
format="YYYY-MM-DD HH:mm"
:allow-clear="false"
@select="onSelect"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item field="publishTimeEnd" :label="$t('结束时间')">
<a-date-picker
v-model="formModel.publishTimeEnd"
style="width: 360px"
show-time
:time-picker-props="{ defaultValue: '09:09:06' }"
format="YYYY-MM-DD HH:mm"
:allow-clear="false"
@select="onSelect"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-col>
<a-divider style="height: 84px" direction="vertical" />
<a-col :flex="'46px'" style="text-align: right">
<a-space direction="vertical" :size="18">
<a-button type="primary" @click="search">
<template #icon>
<icon-search />
</template>
{{ $t('searchTable.form.search') }}
</a-button>
<a-button @click="reset">
<template #icon>
<icon-refresh />
</template>
{{ $t('searchTable.form.reset') }}
</a-button>
</a-space>
</a-col>
</a-row>
<a-divider style="margin-top: 0" />
<a-table
row-key="id"
:loading="loading"
:pagination="false"
:columns="(cloneColumns as TableColumnData[])"
:data="renderData"
:bordered="false"
:size="size"
style="margin-bottom: 40px"
:filter-icon-align-left="alignLeft"
@change="handleSortChange"
@page-change="onPageChange"
>
<template #index="{ rowIndex }">
{{ rowIndex + 1 + (pagination.current - 1) * pagination.size }}
</template>
<template #enabled="{ record }">
<a-switch
:model-value="record.enabled"
:checked-value="true"
:unchecked-value="false"
@change="enabledStatus(record)"
/>
</template>
<template #operations="{ record }">
<a-button
type="outline"
size="small"
status="success"
:prem="record"
style="padding: 7px; margin-right: 10px"
>
<template #icon><icon-list /></template>
详情
</a-button>
</template>
</a-table>
<a-pagination
style="float: right; position: relative; right: 1px; bottom: 25px"
:total="pagination.total"
:size="size"
show-total
show-jumper
show-page-size
@page-size-change="onSizeChange"
@change="onPageChange"
/>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import useLoading from '@/hooks/loading';
import { UserRecord, UserParams } from '@/api/user';
import { Pagination } from '@/types/global';
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import { useUserStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import useTableOption from '@/hooks/table-option';
import NoticeEdit from '@/views/notification/noticeSet/components/notice-edit.vue';
const generateFormModel = () => {
return {
title: '',
states: '',
publishTimeBegin: '',
publishTimeEnd: '',
};
};
const { loading, setLoading } = useLoading(true);
const { t } = useI18n();
const renderData = ref<UserRecord[]>([]);
const formModel = ref(generateFormModel());
const {
cloneColumns,
showColumns,
densityList,
handleSelectDensity,
handleChange,
popupVisibleChange,
deepClone,
} = useTableOption();
const sizeof = useTableOption().size;
const userStore = useUserStore();
const pagination: Pagination = {
page: 1,
size: 10,
current: 1,
total: null,
};
const columns = computed<TableColumnData[]>(() => [
{
title: '序号',
dataIndex: 'index',
slotName: 'index',
width: 60,
},
{
title: '标题',
dataIndex: 'title',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('时间'),
dataIndex: 'publishTimeBegin',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('状态'),
dataIndex: 'states',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '操作',
dataIndex: 'operations',
slotName: 'operations',
},
]);
const statusOptions = computed<SelectOptionData[]>(() => [
{
label: t('已发布'),
value: 'true',
},
{
label: t('未发布'),
value: 'false',
},
]);
//
const fetchData = async (
params: UserParams = { page: 1, size: 10, current: 1 }
) => {
setLoading(true);
try {
const res = await userStore.getUserList(params);
renderData.value = res.data.records;
pagination.page = res.data.page;
pagination.current = res.data.current;
pagination.total = res.data.total;
pagination.size = res.data.size;
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
//
const search = () => {
fetchData({
...pagination,
...formModel.value,
} as unknown as UserParams);
};
//
const onPageChange = (current: number) => {
pagination.page = current;
pagination.current = current;
search();
};
//
const onSizeChange = (size: number) => {
pagination.size = size;
search();
};
search();
//
const reset = () => {
formModel.value = generateFormModel();
};
//
const alignLeft = ref(false);
const handleSortChange = (
data: any,
extra: any,
currentDataSource: any
) => {};
//
const enabledStatus = async (record: string) => {
record.enabled = !record.enabled;
const res = await userStore.enabledUser(record.id);
if (res.status === 200) {
Message.success({
content: t('modify.status.sucess'),
duration: 3 * 1000,
});
} else {
Message.error({
content: t('modify.status.fail'),
duration: 3 * 1000,
});
}
};
//
// const handleDelete = async (record: UserRecord) => {
// const res = await userStore.removeUser(record.id);
// if (res.status === 200) {
// Message.success({
// content: '',
// duration: 5 * 1000,
// });
// search();
// }
// };
watch(() => columns.value, deepClone, { deep: true, immediate: true });
</script>
<style scoped>
.container {
padding: 0 20px 20px 20px;
}
.content-col {
flex: 1;
padding-left: 24px; /* 左侧内边距 */
}
.message-item {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
margin-bottom: 16px; /* 卡片之间的间距 */
display: flex;
align-items: center;
}
.message-content {
flex: 1;
padding: 16px;
}
.scroll-wrapper {
height: calc(62vh); /* 调整高度以适应页面布局 */
overflow-y: auto; /* 当内容超出时显示滚动条 */
border: 1px solid #d9d9d9; /* 边框颜色调淡 */
border-radius: 8px;
padding: 16px;
}
.load-trigger {
height: 1px; /* 设置高度为1px使其不占据太多空间 */
opacity: 0; /* 使其不可见 */
}
.loading,
.no-more {
text-align: center;
padding: 10px;
color: #999;
}
</style>

View File

@ -0,0 +1,208 @@
<template>
<a-button v-if="props.isCreate" type="primary">
<template #icon><icon-plus /></template>
新建
</a-button>
<a-button
v-if="!props.isCreate"
type="outline"
size="small"
:style="{ marginRight: '10px', padding: '7px' }"
>
<template #icon><icon-edit /></template>
修改
</a-button>
<a-modal
width="700px"
:visible="visible"
@ok="handleSubmit"
@cancel="handleCancel"
>
<template #title>{{ modalTitle }}</template>
<a-form ref="CreateRef" :model="formData" :style="{ width: '650px' }">
<a-form-item
field="title"
:label="$t('标题')"
:validate-trigger="['change', 'input']"
:rules="[
{ required: true, message: '' },
{
match: /^[a-zA-Z0-9\u4e00-\u9fa5]{1,20}$/,
message: '',
},
]"
>
<a-input v-model="formData.title" :placeholder="$t('请输入公告标题')" />
<!-- v-if="props.isCreate"-->
<!-- <div v-else>{{ formData.title }}</div>-->
</a-form-item>
<a-form-item
field="email"
:label="$t('作者')"
:rules="[
{
required: true,
type: 'email',
message: t('user.info.email.required'),
},
]"
:validate-trigger="['change', 'input']"
>
<a-input v-model="formData.email" :placeholder="$t('请输入作者')" />
</a-form-item>
<a-form-item
field="phone"
:label="$t('发布范围')"
:rules="[
{ required: true, message: t('user.info.phone.required') },
{ match: /^1[3-9]\d{9}$/, message: t('user.info.phone.format') },
]"
:validate-trigger="['change', 'input']"
>
<a-input v-model="formData.phone" :placeholder="$t('请选择范围')" />
</a-form-item>
<a-form-item
field="password"
:label="$t('发布时间')"
:validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('user.info.password.required') }]"
>
<!-- v-if="isCreate"-->
<a-input
v-model="formData.password"
:placeholder="$t('请选择发布时间')"
/>
</a-form-item>
<a-form-item field="nickName" :label="$t('公告内容')">
<a-textarea
default-value="请输入内容"
:auto-size="{
minRows: 2,
maxRows: 5,
}"
style="margin-top: 20px"
v-model="formData.content"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import useVisible from '@/hooks/visible';
import { computed, PropType, ref } from 'vue';
import { CreateRecord } from '@/api/user';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { deptList } from '@/api/dept';
import { Message } from '@arco-design/web-vue';
import { useUserStore } from '@/store';
const props = defineProps({
prem: {
type: Object as PropType<CreateRecord>,
},
isCreate: Boolean,
});
const { t } = useI18n();
const modalTitle = computed(() => {
return props.isCreate ? t('新增公告') : t('编辑公告');
});
const { visible, setVisible } = useVisible(false);
const checkKeys = ref<number[]>([]);
const CreateRef = ref<FormInstance>();
const formData = ref<any>({});
let formDifer = {};
const userStore = useUserStore();
//
const deptOptions = ref();
const getDeptData = async () => {
const res = await deptList();
deptOptions.value = res.data.records;
};
//
// const roleOptions = ref();
// const getRoleData = async () => {
// const res = await queryRoleList('');
// roleOptions.value = res.data.records.filter((item: any) => {
// return item.enabled !== false;
// });
// };
//
// const handleClick = () => {
// getDeptData();
// // getRoleData();
// const userId = props.prem?.id;
// //
// if (!props.isCreate && userId) {
// formData.value = props.prem;
// formDifer = { ...props.prem };
// }
// setVisible(true);
// };
//
const diffDataForm = (newData: any, oldData: any) => {
const result = {}; //
Object.keys(oldData).forEach((key) => {
if (oldData[key] !== newData[key]) {
result[key] = newData[key];
}
});
return result;
};
//
const handleSubmit = async () => {
const valid = await CreateRef.value?.validate();
if (!valid) {
formData.value.permissionIds = checkKeys.value;
//
if (props.isCreate) {
// formData.value.username = formData.value.email;
const res = await userStore.createUser(formData.value);
if (res.status === 200) {
Message.success({
content: t('create.sucess'),
duration: 5 * 1000,
});
}
CreateRef.value?.resetFields();
} else {
//
formDifer = diffDataForm(formData.value, formDifer);
if (Object.keys(formDifer).length === 0) {
Message.success({
content: t('unmodified'),
duration: 3 * 1000,
});
} else {
formDifer.id = formData.value.id;
const res = await userStore.updateUser(formDifer);
if (res.status === 200) {
Message.success({
content: t('modify.sucess'),
duration: 5 * 1000,
});
}
}
}
checkKeys.value = [];
setVisible(false);
}
};
//
const handleCancel = async () => {
checkKeys.value = [];
setVisible(false);
};
</script>
<style scoped></style>

View File

@ -0,0 +1,445 @@
<template>
<div class="container">
<Breadcrumb :items="['系统管理', '公告设置']" />
<a-card class="general-card" title=" ">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="right"
>
<a-row :gutter="18">
<a-col :span="9">
<a-form-item field="title" :label="$t('标题')">
<a-input
v-model="formModel.title"
style="width: 360px"
:placeholder="$t('请输入标题')"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item field="enable" :label="$t('状态')">
<a-select
v-model="formModel.states"
style="width: 360px"
:placeholder="$t('请选择状态')"
:options="statusOptions"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item field="publishTimeBegin" :label="$t('开始时间')">
<a-date-picker
v-model="formModel.publishTimeBegin"
style="width: 360px"
show-time
:time-picker-props="{ defaultValue: '09:09:06' }"
format="YYYY-MM-DD HH:mm"
:allow-clear="false"
@select="onSelect"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item field="publishTimeEnd" :label="$t('结束时间')">
<a-date-picker
v-model="formModel.publishTimeEnd"
style="width: 360px"
show-time
:time-picker-props="{ defaultValue: '09:09:06' }"
format="YYYY-MM-DD HH:mm"
:allow-clear="false"
@select="onSelect"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-col>
<a-divider style="height: 84px" direction="vertical" />
<a-col :flex="'86px'" style="text-align: right">
<a-space direction="vertical" :size="18">
<a-button type="primary" @click="search">
<template #icon>
<icon-search />
</template>
查询
</a-button>
<a-button @click="reset">
<template #icon>
<icon-refresh />
</template>
重置
</a-button>
</a-space>
</a-col>
</a-row>
<a-divider style="margin-top: 0" />
<a-row>
<a-col :span="12">
<a-space>
<NoticeEdit
ref="createUserRef"
:is-create="true"
@refresh="search"
/>
</a-space>
<a-button style="margin-left: 20px" @click="generateExcel">
<template #icon>
<icon-download size="18" />
</template>
导出
</a-button>
</a-col>
<a-col
:span="12"
style="
display: flex;
align-items: center;
justify-content: end;
padding-bottom: 20px;
"
>
<a-tooltip :content="$t('刷新')">
<div class="action-icon" @click="search">
<icon-refresh size="18" />
</div>
</a-tooltip>
<a-dropdown @select="handleSelectDensity">
<a-tooltip :content="$t('密度')">
<div class="action-icon"><icon-line-height size="18" /></div>
</a-tooltip>
<template #content>
<a-doption
v-for="item in densityList"
:key="item.value"
:value="item.value"
:class="{ active: item.value === sizeof }"
>
<span>{{ item.name }}</span>
</a-doption>
</template>
</a-dropdown>
<a-tooltip :content="$t('列设置')">
<a-popover
trigger="click"
position="bl"
@popup-visible-change="popupVisibleChange"
>
<div class="action-icon"><icon-settings size="18" /></div>
<template #content>
<div id="tableSetting">
<div
v-for="(item, index) in showColumns"
:key="item.dataIndex"
class="setting"
>
<div style="margin-right: 4px; cursor: move">
<icon-drag-arrow />
</div>
<div>
<a-checkbox
v-model="item.checked"
@change="
handleChange($event, item as TableColumnData, index)
"
>
</a-checkbox>
</div>
<div class="title">
{{ item.title === '#' ? '序列号' : item.title }}
</div>
</div>
</div>
</template>
</a-popover>
</a-tooltip>
</a-col>
</a-row>
<a-table
row-key="id"
:loading="loading"
:pagination="false"
:columns="(cloneColumns as TableColumnData[])"
:data="renderData"
:bordered="false"
:size="size"
style="margin-bottom: 40px"
:filter-icon-align-left="alignLeft"
@change="handleSortChange"
@page-change="onPageChange"
>
<template #index="{ rowIndex }">
{{ rowIndex + 1 + (pagination.current - 1) * pagination.size }}
</template>
<template #enabled="{ record }">
<a-switch
:model-value="record.enabled"
:checked-value="true"
:unchecked-value="false"
@change="enabledStatus(record)"
/>
</template>
<template #operations="{ record }">
<a-button
type="outline"
size="small"
status="success"
style="padding: 7px; margin-right: 10px"
>
<template #icon><icon-list /></template>
详情
</a-button>
<NoticeEdit
ref="editUserRef"
:prem="record"
:is-create="false"
@refresh="fetchData"
/>
<a-button
type="outline"
size="small"
status="danger"
style="padding: 7px"
>
<template #icon><icon-delete /></template>
删除
</a-button>
</template>
</a-table>
<a-pagination
style="float: right; position: relative; right: 1px; bottom: 25px"
:total="pagination.total"
:size="size"
show-total
show-jumper
show-page-size
@page-size-change="onSizeChange"
@change="onPageChange"
/>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import useLoading from '@/hooks/loading';
import { UserRecord, UserParams } from '@/api/user';
import { Pagination } from '@/types/global';
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import { useUserStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import useTableOption from '@/hooks/table-option';
import NoticeEdit from './components/notice-edit.vue';
const generateFormModel = () => {
return {
title: '',
states: '',
publishTimeBegin: '',
publishTimeEnd: '',
};
};
const { loading, setLoading } = useLoading(true);
const { t } = useI18n();
const renderData = ref<UserRecord[]>([]);
const formModel = ref(generateFormModel());
const {
cloneColumns,
showColumns,
densityList,
handleSelectDensity,
handleChange,
popupVisibleChange,
deepClone,
} = useTableOption();
const sizeof = useTableOption().size;
const userStore = useUserStore();
const pagination: Pagination = {
page: 1,
size: 10,
current: 1,
total: null,
};
const columns = computed<TableColumnData[]>(() => [
{
title: '序号',
dataIndex: 'index',
slotName: 'index',
width: 60,
},
{
title: '标题',
dataIndex: 'title',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('时间'),
dataIndex: 'publishTimeBegin',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('状态'),
dataIndex: 'states',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '操作',
dataIndex: 'operations',
slotName: 'operations',
},
]);
const statusOptions = computed<SelectOptionData[]>(() => [
{
label: t('已发布'),
value: 'true',
},
{
label: t('未发布'),
value: 'false',
},
]);
//
const fetchData = async (
params: UserParams = { page: 1, size: 10, current: 1 }
) => {
setLoading(true);
try {
const res = await userStore.getUserList(params);
renderData.value = res.data.records;
pagination.page = res.data.page;
pagination.current = res.data.current;
pagination.total = res.data.total;
pagination.size = res.data.size;
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
//
const search = () => {
fetchData({
...pagination,
...formModel.value,
} as unknown as UserParams);
};
//
const onPageChange = (current: number) => {
pagination.page = current;
pagination.current = current;
search();
};
//
const onSizeChange = (size: number) => {
pagination.size = size;
search();
};
search();
//
const reset = () => {
formModel.value = generateFormModel();
};
//
const alignLeft = ref(false);
const handleSortChange = (
data: any,
extra: any,
currentDataSource: any
) => {};
//
const enabledStatus = async (record: string) => {
record.enabled = !record.enabled;
const res = await userStore.enabledUser(record.id);
if (res.status === 200) {
Message.success({
content: t('modify.status.sucess'),
duration: 3 * 1000,
});
} else {
Message.error({
content: t('modify.status.fail'),
duration: 3 * 1000,
});
}
};
//
// const handleDelete = async (record: UserRecord) => {
// const res = await userStore.removeUser(record.id);
// if (res.status === 200) {
// Message.success({
// content: '',
// duration: 5 * 1000,
// });
// search();
// }
// };
watch(() => columns.value, deepClone, { deep: true, immediate: true });
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
}
:deep(.arco-table-th) {
&:last-child {
.arco-table-th-item-title {
margin-left: 16px;
}
}
}
.action-icon {
margin-left: 12px;
cursor: pointer;
}
.active {
color: #0960bd;
background-color: #e3f4fc;
}
.setting {
display: flex;
align-items: center;
width: 200px;
.title {
margin-left: 12px;
cursor: pointer;
}
}
</style>

View File

@ -1,51 +1,48 @@
<template> <template>
<a-button v-if="props.isCreate" type="primary" @click="handleClick"> <a-button v-if="props.isCreate" type="primary" @click="handleClick">
<template #icon><icon-plus /></template> <template #icon><icon-plus /></template>
新建 新建
</a-button> </a-button>
<a-button <a-button
v-if="!props.isCreate" v-if="!props.isCreate"
type="outline" type="outline"
size="small" size="small"
:style="{ marginRight: '10px', padding: '7px' }" :style="{ marginRight: '10px', padding: '7px' }"
@click="handleClick" @click="handleClick"
> >
<template #icon><icon-edit /></template> <template #icon><icon-edit /></template>
修改 修改
</a-button> </a-button>
<a-modal <a-modal
width="700px" width="700px"
:visible="visible" :visible="visible"
@ok="handleSubmit" @ok="handleSubmit"
@cancel="handleCancel" @cancel="handleCancel"
> >
<template #title>{{ modalTitle }}</template> <template #title>{{ modalTitle }}</template>
<a-form ref="createEditRef" :model="formData" :style="{ width: '650px' }"> <a-form ref="createEditRef" :model="formData" :style="{ width: '650px' }">
<a-form-item <a-form-item
field="name" field="name"
label="权限名称" label="权限名称"
:validate-trigger="['change', 'input']" :validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('auth.info.name.required') }]" :rules="[{ required: true, message: t('auth.info.name.required') }]"
> >
<a-input <a-input v-model="formData.name" placeholder="请输入权限名称" />
v-model="formData.name" </a-form-item>
placeholder="请输入权限名称"
/> <a-form-item field="remark" label="备注">
</a-form-item> <a-textarea
v-model="formData.remark"
<a-form-item field="remark" label="备注"> :show-word-limit="true"
<a-textarea placeholder="请输入备注"
v-model="formData.remark" style="height: 100px"
:show-word-limit="true" />
placeholder="请输入备注" </a-form-item>
style="height: 100px" </a-form>
/> </a-modal>
</a-form-item> </template>
</a-form>
</a-modal> <script lang="ts" setup>
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { computed, PropType, ref } from 'vue'; import { computed, PropType, ref } from 'vue';
@ -53,7 +50,7 @@
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { AuthRecord } from '@/api/authority'; import { AuthRecord } from '@/api/authority';
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store';
const props = defineProps({ const props = defineProps({
prem: { prem: {
type: Object as PropType<AuthRecord>, type: Object as PropType<AuthRecord>,
@ -62,7 +59,7 @@
}); });
const { t } = useI18n(); const { t } = useI18n();
const modalTitle = computed(() => { const modalTitle = computed(() => {
return props.isCreate ? '创建权限' : '编辑权限'; return props.isCreate ? t('Create Auth Info') : t('Edit Auth Info');
}); });
const { visible, setVisible } = useVisible(false); const { visible, setVisible } = useVisible(false);
const createEditRef = ref<FormInstance>(); const createEditRef = ref<FormInstance>();
@ -76,9 +73,9 @@
remark: '', remark: '',
// authorities: [], // authorities: [],
}); });
const emit = defineEmits(['refresh']); const emit = defineEmits(['refresh']);
// //
const handleClick = () => { const handleClick = () => {
const authId = props.prem?.id; const authId = props.prem?.id;
@ -86,7 +83,7 @@
if (!props.isCreate && authId) { if (!props.isCreate && authId) {
authStore authStore
.getAuthDetail(authId) .getAuthDetail(authId)
.then((res:any) => { .then((res: any) => {
formData.value = res.data; formData.value = res.data;
}) })
.then(() => { .then(() => {
@ -96,7 +93,7 @@
setVisible(true); setVisible(true);
} }
}; };
// //
const handleSubmit = async () => { const handleSubmit = async () => {
const valid = await createEditRef.value?.validate(); const valid = await createEditRef.value?.validate();
@ -125,13 +122,12 @@
setVisible(false); setVisible(false);
} }
}; };
// //
const handleCancel = async () => { const handleCancel = async () => {
createEditRef.value?.resetFields(); createEditRef.value?.resetFields();
setVisible(false); setVisible(false);
}; };
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,133 +1,158 @@
<template> <template>
<a-table row-key="id" :loading="loading" :pagination="false" :columns="columns" :data="data" :bordered="false" <a-table
:size="size" style="margin-bottom: 40px" @pageChange="onPageChange"> row-key="id"
<template #index="{ rowIndex }"> :loading="loading"
{{ rowIndex + 1 }} :pagination="false"
</template> :columns="columns"
<template #createTime="{ record }"> :data="data"
{{ dayjs(record.createTime).format('YYYY-MM-DD') }} :bordered="false"
</template> :size="size"
<template #enabled="{ record }"> style="margin-bottom: 40px"
<a-switch :model-value="record.enabled" :checked-value="true" :unchecked-value="false" @pageChange="onPageChange"
@change="enabledStatus(record)" /> >
</template> <template #index="{ rowIndex }">
<template #operations="{ record }"> {{ rowIndex + 1 }}
<!-- 编辑 --> </template>
<AuthEdit ref="editRef" :prem="record" :is-create="false" @refresh="fetchData" /> <template #createTime="{ record }">
<a-popconfirm :content="t('Confirm the deletion of this role')" type="error" @ok="handleDelete(record)"> {{ dayjs(record.createTime).format('YYYY-MM-DD') }}
<a-button type="outline" size="small" status="danger" style="padding: 7px"> </template>
<template #icon><icon-delete /></template> <template #enabled="{ record }">
删除 <a-switch
</a-button> :model-value="record.enabled"
</a-popconfirm> :checked-value="true"
</template> :unchecked-value="false"
</a-table> @change="enabledStatus(record)"
/>
</template>
<template #operations="{ record }">
<!-- 编辑 -->
<AuthEdit
ref="editRef"
:prem="record"
:is-create="false"
@refresh="fetchData"
/>
<a-popconfirm
:content="t('Confirm the deletion of this role')"
type="error"
@ok="handleDelete(record)"
>
<a-button
type="outline"
size="small"
status="danger"
style="padding: 7px"
>
<template #icon><icon-delete /></template>
删除
</a-button>
</a-popconfirm>
</template>
</a-table>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineProps, defineEmits} from 'vue'; import { defineProps, defineEmits } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store';
import { AuthRecord } from '@/api/authority'; import { AuthRecord } from '@/api/authority';
// import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'; // import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import AuthEdit from './auth-edit.vue'; import AuthEdit from './auth-edit.vue';
const authStore = useAuthStore();
const authStore = useAuthStore(); const props = defineProps({
const props = defineProps({
loading: Boolean, loading: Boolean,
data: Array, data: Array,
size: String, size: String,
}); });
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits(['pageChange', 'refresh','search']); const emit = defineEmits(['pageChange', 'refresh', 'search']);
// //
// const columns = computed<TableColumnData[]>(() => [ // const columns = computed<TableColumnData[]>(() => [
// { // {
// title: t('roleTable.columns.index'), // title: t('roleTable.columns.index'),
// dataIndex: 'index', // dataIndex: 'index',
// slotName: 'index', // slotName: 'index',
// }, // },
// { // {
// title: t('roleTable.columns.name'), // title: t('roleTable.columns.name'),
// dataIndex: 'name', // dataIndex: 'name',
// sortable: { // sortable: {
// sortDirections: ['ascend', 'descend'], // sortDirections: ['ascend', 'descend'],
// }, // },
// }, // },
// { // {
// title: t('roleTable.columns.remark'), // title: t('roleTable.columns.remark'),
// dataIndex: 'remark', // dataIndex: 'remark',
// }, // },
// { // {
// title: t('roleTable.columns.createTime'), // title: t('roleTable.columns.createTime'),
// dataIndex: 'createTime', // dataIndex: 'createTime',
// slotName: 'createTime', // slotName: 'createTime',
// sortable: { // sortable: {
// sortDirections: ['ascend', 'descend'], // sortDirections: ['ascend', 'descend'],
// }, // },
// }, // },
// { // {
// title: t('roleTable.columns.enabled'), // title: t('roleTable.columns.enabled'),
// dataIndex: 'enabled', // dataIndex: 'enabled',
// slotName: 'enabled', // slotName: 'enabled',
// }, // },
// { // {
// title: t('searchTable.columns.operations'), // title: t('searchTable.columns.operations'),
// dataIndex: 'operations', // dataIndex: 'operations',
// slotName: 'operations', // slotName: 'operations',
// }, // },
// ]); // ]);
const fetchData = () => {
const fetchData = () => {
emit('refresh'); emit('refresh');
}; };
// //
const onPageChange = (page: number) => { const onPageChange = (page: number) => {
emit('pageChange', page); emit('pageChange', page);
}; };
// //
const enabledStatus = async (record: string) => { const enabledStatus = async (record: string) => {
record.enabled = !record.enabled; record.enabled = !record.enabled;
const res = await authStore.enabledAuth(record.id); const res = await authStore.enabledAuth(record.id);
if (res.status === 200) { if (res.status === 200) {
Message.success({ Message.success({
content: t('modify.status.sucess'), content: t('modify.status.sucess'),
duration: 3 * 1000, duration: 3 * 1000,
}); });
} else { } else {
Message.error({ Message.error({
content: t('modify.status.fail'), content: t('modify.status.fail'),
duration: 3 * 1000, duration: 3 * 1000,
}); });
} }
}; };
// //
const handleDelete = async (record: AuthRecord) => { const handleDelete = async (record: AuthRecord) => {
const res = await authStore.removeAuth(record.id); const res = await authStore.removeAuth(record.id);
if (res.status === 200) { if (res.status === 200) {
Message.success({ Message.success({
content: t('delete.auth.sucess'), content: t('delete.auth.sucess'),
duration: 5 * 1000, duration: 5 * 1000,
}); });
emit('search'); emit('search');
} else { } else {
Message.error({ Message.error({
content: t('delete.auth.fail'), content: t('delete.auth.fail'),
duration: 3 * 1000, duration: 3 * 1000,
}); });
} }
}; };
// watch(() => columns.value, deepClone, { deep: true, immediate: true }); // watch(() => columns.value, deepClone, { deep: true, immediate: true });
</script> </script>
<style scoped> <style scoped>
/* 添加一些样式 */ /* 添加一些样式 */
</style> </style>

View File

@ -0,0 +1,158 @@
<template>
<a-table
row-key="id"
:loading="loading"
:pagination="false"
:columns="columns"
:data="data"
:bordered="false"
:size="size"
style="margin-bottom: 40px"
@pageChange="onPageChange"
>
<template #index="{ rowIndex }">
{{ rowIndex + 1 }}
</template>
<template #createTime="{ record }">
{{ dayjs(record.createTime).format('YYYY-MM-DD') }}
</template>
<template #enabled="{ record }">
<a-switch
:model-value="record.enabled"
:checked-value="true"
:unchecked-value="false"
@change="enabledStatus(record)"
/>
</template>
<template #operations="{ record }">
<!-- 编辑 -->
<RoleEdit
ref="editRef"
:prem="record"
:is-create="false"
@refresh="fetchData"
/>
<a-popconfirm
:content="t('Confirm the deletion of this role')"
type="error"
@ok="handleDelete(record)"
>
<a-button
type="outline"
size="small"
status="danger"
style="padding: 7px"
>
<template #icon><icon-delete /></template>
删除
</a-button>
</a-popconfirm>
</template>
</a-table>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue';
import dayjs from 'dayjs';
import { useI18n } from 'vue-i18n';
import { Message } from '@arco-design/web-vue';
import { useRoleStore } from '@/store';
import { RoleRecord } from '@/api/role';
// import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import RoleEdit from './role-edit.vue';
const roleStore = useRoleStore();
const props = defineProps({
loading: Boolean,
data: Array,
size: String,
});
const { t } = useI18n();
const emit = defineEmits(['pageChange', 'refresh', 'search']);
//
// const columns = computed<TableColumnData[]>(() => [
// {
// title: t('roleTable.columns.index'),
// dataIndex: 'index',
// slotName: 'index',
// },
// {
// title: t('roleTable.columns.name'),
// dataIndex: 'name',
// sortable: {
// sortDirections: ['ascend', 'descend'],
// },
// },
// {
// title: t('roleTable.columns.remark'),
// dataIndex: 'remark',
// },
// {
// title: t('roleTable.columns.createTime'),
// dataIndex: 'createTime',
// slotName: 'createTime',
// sortable: {
// sortDirections: ['ascend', 'descend'],
// },
// },
// {
// title: t('roleTable.columns.enabled'),
// dataIndex: 'enabled',
// slotName: 'enabled',
// },
// {
// title: t('searchTable.columns.operations'),
// dataIndex: 'operations',
// slotName: 'operations',
// },
// ]);
const fetchData = () => {
emit('refresh');
};
//
const onPageChange = (page: number) => {
emit('pageChange', page);
};
//
const enabledStatus = async (record: string) => {
record.enabled = !record.enabled;
const res = await roleStore.enabledRole(record.id);
if (res.status === 200) {
Message.success({
content: t('modify.status.sucess'),
duration: 3 * 1000,
});
} else {
Message.error({
content: t('modify.status.fail'),
duration: 3 * 1000,
});
}
};
//
const handleDelete = async (record: RoleRecord) => {
const res = await roleStore.removeRole(record.id);
if (res.status === 200) {
Message.success({
content: t('delete.role.sucess'),
duration: 5 * 1000,
});
emit('search');
} else {
Message.error({
content: t('delete.role.fail'),
duration: 3 * 1000,
});
}
};
// watch(() => columns.value, deepClone, { deep: true, immediate: true });
</script>
<style scoped>
/* 添加一些样式 */
</style>

View File

@ -43,13 +43,13 @@
:rules="[ :rules="[
{ {
required: true, required: true,
message: '请输入昵称', message: $t('userSetting.form.error.nickname.required'),
}, },
]" ]"
> >
<a-input <a-input
v-model="formData.nickName" v-model="formData.nickName"
placeholder='请输入昵称' :placeholder="$t('userSetting.basicInfo.placeholder.nickname')"
/> />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
@ -79,42 +79,42 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useUserStore } from '@/store'; import { useUserStore } from '@/store';
import { FormInstance } from '@arco-design/web-vue/es/form'; import { FormInstance } from '@arco-design/web-vue/es/form';
import { UserState } from '@/store/modules/user/types'; import { UserState } from '@/store/modules/user/types';
import { selfUpdate, getUserInfo } from '@/api/user'; import { selfUpdate, getUserInfo } from '@/api/user';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
const userStore = useUserStore(); const userStore = useUserStore();
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const formData = ref<UserState>({ const formData = ref<UserState>({
...userStore.userInfo, ...userStore.userInfo,
}); });
const validate = async () => { const validate = async () => {
const valid = await formRef.value?.validate(); const valid = await formRef.value?.validate();
if (!valid) { if (!valid) {
// do some thing // do some thing
// you also can use html-type to submit // you also can use html-type to submit
const res = await selfUpdate(formData.value); const res = await selfUpdate(formData.value);
if (res.status === 200) { if (res.status === 200) {
await userStore.info(); await userStore.info();
Message.success({ Message.success({
content: '编辑成功', content: '编辑成功',
duration: 5 * 1000, duration: 5 * 1000,
}); });
}
} }
} };
}; const reset = async () => {
const reset = async () => { await formRef.value?.resetFields();
await formRef.value?.resetFields(); };
};
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.form { .form {
width: 540px; width: 540px;
margin: 20px auto; margin: 20px auto;
} }
</style> </style>

View File

@ -50,60 +50,57 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { FormInstance } from '@arco-design/web-vue/es/form'; import { FormInstance } from '@arco-design/web-vue/es/form';
import { PasswordReSetModel, resetPassword } from '@/api/user'; import { PasswordReSetModel, resetPassword } from '@/api/user';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import useUser from '@/hooks/user'; import router from '@/router';
import router from '@/router'; import { clearToken } from '@/utils/auth';
import { clearToken } from '@/utils/auth';
const { t } = useI18n(); const { t } = useI18n();
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const formData = ref<PasswordReSetModel>({ const formData = ref<PasswordReSetModel>({
oldPassword: '', oldPassword: '',
password: '', password: '',
confirmPassword: '', confirmPassword: '',
}); });
const checkEquals = ( const checkEquals = (
value: string | undefined, value: string | undefined,
callback: (error?: string) => void callback: (error?: string) => void
) => { ) => {
if (!value) { if (!value) {
callback(t('userSetting.passwordReset.form.validate.blank')); callback(t('userSetting.passwordReset.form.validate.blank'));
} else if (formData.value.password !== formData.value.confirmPassword) { } else if (formData.value.password !== formData.value.confirmPassword) {
callback(t('userSetting.passwordReset.form.validate.noEquals')); callback(t('userSetting.passwordReset.form.validate.noEquals'));
}
};
const user = useUser();
const validate = async () => {
const vali = await formRef.value?.validate();
if (!vali) {
const res = await resetPassword(formData.value);
if (res.status === 200) {
Message.success({
content: t('passwordReset.sucess'),
duration: 5 * 1000,
});
clearToken();
router.push({
name: 'login',
});
} }
} };
};
const reset = async () => { const validate = async () => {
await formRef.value?.resetFields(); const vali = await formRef.value?.validate();
}; if (!vali) {
const res = await resetPassword(formData.value);
if (res.status === 200) {
Message.success({
content: t('passwordReset.sucess'),
duration: 5 * 1000,
});
clearToken();
router.push({
name: 'login',
});
}
}
};
const reset = async () => {
await formRef.value?.resetFields();
};
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.form { .form {
width: 540px; width: 540px;
margin: 20px auto; margin: 20px auto;
} }
</style> </style>

View File

@ -2,12 +2,12 @@
<a-card :bordered="false"> <a-card :bordered="false">
<a-space :size="54"> <a-space :size="54">
<a-upload <a-upload
:custom-request="Onchange" :custom-request="() => {}"
list-type="picture-card" list-type="picture-card"
:file-list="fileList" :file-list="fileList"
:show-upload-button="true" :show-upload-button="true"
:show-file-list="false" :show-file-list="false"
@change="Onchange" @change="() => {}"
> >
<template #upload-button> <template #upload-button>
<a-avatar :size="100" class="info-avatar"> <a-avatar :size="100" class="info-avatar">
@ -46,66 +46,81 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ref } from 'vue'; import { ref } from 'vue';
import type { FileItem } from '@arco-design/web-vue/es/upload/interfaces'; import type { FileItem } from '@arco-design/web-vue/es/upload/interfaces';
import { useUserStore} from '@/store'; import { useUserStore } from '@/store';
import userIcon from '@/assets/images/user-circle.png'; import userIcon from '@/assets/images/user-circle.png';
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface'; import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { selfUpdate } from '@/api/user'; import { selfUpdate } from '@/api/user';
const userStore = useUserStore(); const userStore = useUserStore();
const { t } = useI18n(); const { t } = useI18n();
// const ticketStore = useTicketStore();
const file = { const file = {
uid: '-2', uid: '-2',
name: 'avatar.png', name: 'avatar.png',
url: userStore.avatar ? userStore.avatar : userIcon, url: userStore.avatar ? userStore.avatar : userIcon,
}; };
const renderData = [ const renderData = [
{ {
label: 'userSetting.label.name', label: 'userSetting.label.name',
value: userStore.username, value: userStore.username,
}, },
{ {
label: 'userSetting.label.accountId', label: 'userSetting.label.accountId',
value: userStore.id, value: userStore.id,
}, },
{ {
label: 'userSetting.label.phone', label: 'userSetting.label.phone',
value: userStore.phone, value: userStore.phone,
}, },
{ {
label: 'userSetting.label.registrationDate', label: 'userSetting.label.registrationDate',
value: dayjs(userStore.createAt).format('YYYY-MM-DD hh:mm:ss'), value: dayjs(userStore.createAt).format('YYYY-MM-DD hh:mm:ss'),
}, },
] as DescData[]; ] as DescData[];
const fileList = ref<FileItem[]>([file]); const fileList = ref<FileItem[]>([file]);
const Onchange = async (option: any) => { // const Onchange = async (option: any) => {
const FormDatas = new FormData(); // const FormDatas = new FormData();
FormDatas.append('file', option.fileItem.file); // FormDatas.append('file', option.fileItem.file);
}; // const res = await ticketStore.uploadFile(FormDatas);
// if (res.status === 200) {
// Message.success({
// content: t('upload.sucess'),
// duration: 3 * 1000,
// });
// res.data.name = res.data.fileName;
// fileList.value.push(res.data);
// await selfUpdate({ avatar: res.data.id });
// await userStore.info();
// } else {
// Message.error({
// content: t('upload.fail'),
// duration: 3 * 1000,
// });
// }
// };
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.arco-card { .arco-card {
padding: 14px 0 4px 4px; padding: 14px 0 4px 4px;
border-radius: 4px; border-radius: 4px;
} }
:deep(.arco-avatar-trigger-icon-button) { :deep(.arco-avatar-trigger-icon-button) {
width: 32px; width: 32px;
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
background-color: #e8f3ff; background-color: #e8f3ff;
.arco-icon-camera { .arco-icon-camera {
margin-top: 8px; margin-top: 8px;
color: rgb(var(--arcoblue-6)); color: rgb(var(--arcoblue-6));
font-size: 14px; font-size: 14px;
}
} }
}
</style> </style>