提交描述

This commit is contained in:
2024-01-17 07:46:08 +08:00
parent 9cd49595db
commit 53b678f2ec
32 changed files with 1410 additions and 414 deletions

View File

@ -1 +1,3 @@
VITE_API_BASE_URL= ''
VITE_API_URL= ''

View File

@ -1,7 +1,7 @@
import { mergeConfig } from 'vite';
import eslint from 'vite-plugin-eslint';
import * as path from 'path';
import baseConfig from './vite.config.base';
import * as path from "path";
export default mergeConfig(
{
@ -13,9 +13,11 @@ export default mergeConfig(
},
proxy: {
'/api': {
target: 'http://localhost:8080',
target: 'http://59.110.238.182:8081',
// target: 'http://4.246.149.244:8081',
// target: 'http://localhost:5173',
changeOrigin: true,
}
},
},
},
plugins: [

View File

@ -1,13 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" type="image/x-icon" href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico">
<link rel="shortcut icon" type="image/x-icon"
href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Arco Design Pro - 开箱即用的中台前端/设计解决方案</title>
<title>学院学习平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

195
npminstall-debug.log Normal file
View File

@ -0,0 +1,195 @@
{
root: 'C:\\Users\\29602\\Desktop\\study\\study-fontend',
registry: 'https://registry.npmmirror.com',
pkgs: [],
production: false,
cacheStrict: false,
cacheDir: 'C:\\Users\\29602\\.npminstall_tarball',
env: {
npm_config_registry: 'https://registry.npmmirror.com',
npm_config_argv: '{"remain":[],"cooked":["--fix-bug-versions","--china","--userconfig=C:\\\\Users\\\\29602\\\\.cnpmrc","--disturl=https://cdn.npmmirror.com/binaries/node","--registry=https://registry.npmmirror.com"],"original":["--fix-bug-versions","--china","--userconfig=C:\\\\Users\\\\29602\\\\.cnpmrc","--disturl=https://cdn.npmmirror.com/binaries/node","--registry=https://registry.npmmirror.com"]}',
npm_config_user_agent: 'npminstall/7.11.1 npm/? node/v18.19.0 win32 x64',
npm_config_cache: 'C:\\Users\\29602\\.npminstall_tarball',
NODE: 'C:\\Program Files\\nodejs\\node.exe',
npm_node_execpath: 'C:\\Program Files\\nodejs\\node.exe',
npm_execpath: 'C:\\Users\\29602\\AppData\\Roaming\\npm\\node_modules\\cnpm\\node_modules\\npminstall\\bin\\install.js',
npm_config_userconfig: 'C:\\Users\\29602\\.cnpmrc',
npm_config_disturl: 'https://cdn.npmmirror.com/binaries/node',
npm_config_r: 'https://registry.npmmirror.com',
COREPACK_NPM_REGISTRY: 'https://registry.npmmirror.com',
NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
NVM_NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
PHANTOMJS_CDNURL: 'https://cdn.npmmirror.com/binaries/phantomjs',
CHROMEDRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/chromedriver',
OPERADRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/operadriver',
CYPRESS_DOWNLOAD_PATH_TEMPLATE: 'https://cdn.npmmirror.com/binaries/cypress/${version}/${platform}-${arch}/cypress.zip',
ELECTRON_MIRROR: 'https://cdn.npmmirror.com/binaries/electron/',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://cdn.npmmirror.com/binaries/electron-builder-binaries/',
SASS_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-sass',
SWC_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-swc',
NWJS_URLBASE: 'https://cdn.npmmirror.com/binaries/nwjs/v',
PUPPETEER_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PUPPETEER_DOWNLOAD_BASE_URL: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PLAYWRIGHT_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/playwright',
SENTRYCLI_CDNURL: 'https://cdn.npmmirror.com/binaries/sentry-cli',
SAUCECTL_INSTALL_BINARY_MIRROR: 'https://cdn.npmmirror.com/binaries/saucectl',
RE2_DOWNLOAD_MIRROR: 'https://cdn.npmmirror.com/binaries/node-re2',
RE2_DOWNLOAD_SKIP_PATH: 'true',
PRISMA_ENGINES_MIRROR: 'https://cdn.npmmirror.com/binaries/prisma',
npm_config_better_sqlite3_binary_host: 'https://cdn.npmmirror.com/binaries/better-sqlite3',
npm_config_keytar_binary_host: 'https://cdn.npmmirror.com/binaries/keytar',
npm_config_sharp_binary_host: 'https://cdn.npmmirror.com/binaries/sharp',
npm_config_sharp_libvips_binary_host: 'https://cdn.npmmirror.com/binaries/sharp-libvips',
npm_config_robotjs_binary_host: 'https://cdn.npmmirror.com/binaries/robotjs',
npm_rootpath: 'C:\\Users\\29602\\Desktop\\study\\study-fontend',
INIT_CWD: 'C:\\Users\\29602\\Desktop\\study\\study-fontend'
},
binaryMirrors: {
ENVS: {
COREPACK_NPM_REGISTRY: 'https://registry.npmmirror.com',
NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
NVM_NODEJS_ORG_MIRROR: 'https://cdn.npmmirror.com/binaries/node',
PHANTOMJS_CDNURL: 'https://cdn.npmmirror.com/binaries/phantomjs',
CHROMEDRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/chromedriver',
OPERADRIVER_CDNURL: 'https://cdn.npmmirror.com/binaries/operadriver',
CYPRESS_DOWNLOAD_PATH_TEMPLATE: 'https://cdn.npmmirror.com/binaries/cypress/${version}/${platform}-${arch}/cypress.zip',
ELECTRON_MIRROR: 'https://cdn.npmmirror.com/binaries/electron/',
ELECTRON_BUILDER_BINARIES_MIRROR: 'https://cdn.npmmirror.com/binaries/electron-builder-binaries/',
SASS_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-sass',
SWC_BINARY_SITE: 'https://cdn.npmmirror.com/binaries/node-swc',
NWJS_URLBASE: 'https://cdn.npmmirror.com/binaries/nwjs/v',
PUPPETEER_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PUPPETEER_DOWNLOAD_BASE_URL: 'https://cdn.npmmirror.com/binaries/chrome-for-testing',
PLAYWRIGHT_DOWNLOAD_HOST: 'https://cdn.npmmirror.com/binaries/playwright',
SENTRYCLI_CDNURL: 'https://cdn.npmmirror.com/binaries/sentry-cli',
SAUCECTL_INSTALL_BINARY_MIRROR: 'https://cdn.npmmirror.com/binaries/saucectl',
RE2_DOWNLOAD_MIRROR: 'https://cdn.npmmirror.com/binaries/node-re2',
RE2_DOWNLOAD_SKIP_PATH: 'true',
PRISMA_ENGINES_MIRROR: 'https://cdn.npmmirror.com/binaries/prisma',
npm_config_better_sqlite3_binary_host: 'https://cdn.npmmirror.com/binaries/better-sqlite3',
npm_config_keytar_binary_host: 'https://cdn.npmmirror.com/binaries/keytar',
npm_config_sharp_binary_host: 'https://cdn.npmmirror.com/binaries/sharp',
npm_config_sharp_libvips_binary_host: 'https://cdn.npmmirror.com/binaries/sharp-libvips',
npm_config_robotjs_binary_host: 'https://cdn.npmmirror.com/binaries/robotjs'
},
'@ali/s2': { host: 'https://cdn.npmmirror.com/binaries/looksgood-s2' },
sharp: { replaceHostFiles: [Array], replaceHostMap: [Object] },
'@tensorflow/tfjs-node': {
replaceHostFiles: [Array],
replaceHostRegExpMap: [Object],
replaceHostMap: [Object]
},
cypress: {
host: 'https://cdn.npmmirror.com/binaries/cypress',
newPlatforms: [Object]
},
'utf-8-validate': {
host: 'https://cdn.npmmirror.com/binaries/utf-8-validate/v{version}'
},
xprofiler: {
remote_path: './xprofiler/v{version}/',
host: 'https://cdn.npmmirror.com/binaries'
},
leveldown: { host: 'https://cdn.npmmirror.com/binaries/leveldown/v{version}' },
couchbase: { host: 'https://cdn.npmmirror.com/binaries/couchbase/v{version}' },
gl: { host: 'https://cdn.npmmirror.com/binaries/gl/v{version}' },
sqlite3: {
host: 'https://cdn.npmmirror.com/binaries/sqlite3',
remote_path: 'v{version}'
},
'@journeyapps/sqlcipher': { host: 'https://cdn.npmmirror.com/binaries' },
grpc: {
host: 'https://cdn.npmmirror.com/binaries',
remote_path: '{name}/v{version}'
},
'grpc-tools': { host: 'https://cdn.npmmirror.com/binaries' },
wrtc: {
host: 'https://cdn.npmmirror.com/binaries',
remote_path: '{name}/v{version}'
},
fsevents: { host: 'https://cdn.npmmirror.com/binaries/fsevents' },
nodejieba: { host: 'https://cdn.npmmirror.com/binaries/nodejieba' },
canvas: { host: 'https://cdn.npmmirror.com/binaries/canvas' },
'skia-canvas': { host: 'https://cdn.npmmirror.com/binaries/skia-canvas' },
'flow-bin': {
replaceHost: 'https://github.com/facebook/flow/releases/download/v',
host: 'https://cdn.npmmirror.com/binaries/flow/v'
},
'jpegtran-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/jpegtran-bin'
},
'cwebp-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/cwebp-bin'
},
'zopflipng-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/zopflipng-bin'
},
'optipng-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/optipng-bin'
},
mozjpeg: {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/mozjpeg-bin'
},
gifsicle: {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/gifsicle-bin'
},
'pngquant-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/pngquant-bin',
replaceHostMap: [Object]
},
'pngcrush-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/pngcrush-bin'
},
'jpeg-recompress-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/jpeg-recompress-bin'
},
'advpng-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/advpng-bin'
},
'pngout-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/pngout-bin'
},
'jpegoptim-bin': {
replaceHost: [Array],
host: 'https://cdn.npmmirror.com/binaries/jpegoptim-bin'
},
argon2: { host: 'https://cdn.npmmirror.com/binaries/argon2' },
'ali-zeromq': { host: 'https://cdn.npmmirror.com/binaries/ali-zeromq' },
'ali-usb_ctl': { host: 'https://cdn.npmmirror.com/binaries/ali-usb_ctl' },
'gdal-async': { host: 'https://cdn.npmmirror.com/binaries/node-gdal-async' },
'libpg-query': { host: 'https://cdn.npmmirror.com/binaries' }
},
forbiddenLicenses: null,
flatten: false,
proxy: undefined,
prune: false,
disableFallbackStore: false,
workspacesMap: Map(0) {},
enableWorkspace: false,
workspaceRoot: 'C:\\Users\\29602\\Desktop\\study\\study-fontend',
isWorkspaceRoot: true,
isWorkspacePackage: false,
offline: false,
strictSSL: true,
ignoreScripts: false,
foregroundScripts: false,
ignoreOptionalDependencies: false,
detail: false,
forceLinkLatest: false,
trace: false,
engineStrict: false,
registryOnly: false,
client: false,
autoFixVersion: [Function: autoFixVersion]
}

View File

@ -12,7 +12,11 @@
"preview": "npm run build && vite preview --host",
"type:check": "vue-tsc --noEmit --skipLibCheck",
"lint-staged": "npx lint-staged",
"prepare": "husky install"
"prepare": "husky install",
"start": "npm run dev"
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [

View File

@ -21,6 +21,14 @@ axios.interceptors.request.use(
// this example using the JWT token
// Authorization is a custom headers key
// please modify it according to the actual situation
const userStore = useUserStore();
// console.log('config', config);
// if (config.url === '/api/erst/user/me') {
// userStore.headerName = 'zsc';
// userStore.tokenDate = '123';
config.headers['X-CSRF-TOKEN'] = userStore.tokenDate;
// }
const token = getToken();
if (token) {
if (!config.headers) {
@ -28,6 +36,7 @@ axios.interceptors.request.use(
}
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
@ -70,7 +79,7 @@ axios.interceptors.response.use(
},
(error) => {
const { response } = error;
console.log(response);
console.log('error', error);
if (
[50008, 50012, 50014].includes(response.data.code) &&
response.config.url !== '/api/user/info'

View File

@ -8,6 +8,17 @@ export interface RoleCreateRecord {
permissionIds: (number | undefined)[];
remark: string;
}
export interface ListRecord {
username: string;
phone: string;
email: string;
enable: string;
nickname: string;
avater: string;
address: string;
}
export interface RoleRecord extends RoleCreateRecord {
permissions?: [];
}
@ -16,24 +27,35 @@ export interface RoleListRecord extends RoleRecord {
name: string;
}
export function create(data: RoleCreateRecord) {
return axios.post(`/api/role`, data);
}
// 更新用户
export function update(data: RoleRecord) {
return axios.patch(`/api/role/${data.id}`, data);
return axios.patch(`/api/rest/user/${data.id}`, data);
}
export function getDetail(id: number) {
return axios.get<RoleRecord>(`/api/role/${id}`);
}
export function remove(id: number) {
return axios.delete(`/api/role/${id}`);
// 删除用户
export function remove(id: string) {
return axios.delete(`/api/rest/role/${id}`);
}
// 模糊查询用户列表
export function queryRoles(params?: ListParams<Partial<RoleRecord>>) {
return queryList<RoleRecord>(`/api/role`, params);
return queryList<RoleRecord>(`/api/rest/user/query`, params);
}
// 添加用户
export function addUser(params: ListRecord) {
return axios.post('/api/rest/user/register', params);
}
// 是否启用
export function enabled(id: string) {
return axios.patch(`/api/rest/user/${id}/toggle`);
}

View File

@ -21,7 +21,8 @@ export interface LoginRes {
}
export interface PasswordReSetModel {
newPassword: string;
oldPassword: string;
password: string;
confirmPassword: string;
}
@ -73,16 +74,30 @@ export interface UserListRes {
pageable: Pageable;
}
export function login(data: LoginData) {
return axios.post<LoginRes>('/api/user/login', data);
// 获取令牌
export function me() {
return axios.get('/api/rest/user/me');
}
// 用户登录
export function login(data: LoginData, headerName: string, token: string) {
return axios({
url: '/api/rest/user/login',
data,
method: 'post',
headers: {
[headerName]: token,
},
});
}
export function logout() {
return axios.post<LoginRes>('/api/user/logout');
return axios.post<LoginRes>('/api/rest/user/logout');
}
// 更新密码
export function resetPassword(data: PasswordReSetModel) {
return axios.patch('/api/user/self/update-password', data);
return axios.patch('/api/rest/user/self/update-password', data);
}
export function create(data: CreateRecord) {
@ -94,15 +109,16 @@ export function update(data: UserRecord) {
}
export function selfUpdate(data: UserState) {
return axios.patch<Res>(`/api/user/self`, data);
return axios.patch<Res>(`/api/rest/user/self`, data);
}
export function switchRole(roleId: number) {
return axios.patch<UserState>(`/api/user/self/switch-role/${roleId}`);
}
// 获取用户信息
export function getUserInfo() {
return axios.get<UserState>('/api/user/info');
return axios.get<UserState>('/api/rest/user/self');
}
export function getUserDetail(id: number) {

View File

@ -10,7 +10,7 @@
:style="{ margin: 0, fontSize: '18px' }"
:heading="5"
>
Arco Pro
中山学院
</a-typography-title>
<icon-menu-fold
v-if="!topMenu && appStore.device === 'mobile'"
@ -144,16 +144,7 @@
</a-button>
</a-tooltip>
</li>
<li>
<a-tooltip content="切换用户角色">
<a-select
v-model="defaultRole"
:style="{ width: '140px' }"
:options="roleOptions as SelectOptionData[]"
@change="switchRoles"
/>
</a-tooltip>
</li>
<li>
<a-dropdown trigger="click">
<a-avatar

View File

@ -28,6 +28,7 @@ import localeUserSetting from '@/views/user/setting/locale/en-US';
import systemUser from '@/views/system/user/locale/en-US';
import localRole from '@/views/character/manage/locale/zh-CN';
import localeSettings from './en-US/settings';
export default {
@ -45,6 +46,7 @@ export default {
'menu.arcoWebsite': 'Arco Design',
'menu.faq': 'FAQ',
'menu.system': 'System',
'menu.role': 'Role',
'navbar.docs': 'Docs',
'navbar.action.locale': 'Switch to English',
...localeSettings,
@ -67,5 +69,6 @@ export default {
...locale500,
...localeUserInfo,
...localeUserSetting,
...localRole,
...systemUser,
};

View File

@ -28,6 +28,7 @@ import localeUserSetting from '@/views/user/setting/locale/zh-CN';
import systemUser from '@/views/system/user/locale/zh-CN';
import localRole from '@/views/character/manage/locale/zh-CN';
import localeSettings from './zh-CN/settings';
export default {
@ -45,6 +46,7 @@ export default {
'menu.arcoWebsite': 'Arco Design',
'menu.faq': '常见问题',
'menu.system': '系统管理',
'menu.role': '角色中心',
'navbar.docs': '文档中心',
'navbar.action.locale': '切换为中文',
...localeSettings,
@ -67,6 +69,7 @@ export default {
...locale500,
...localeUserInfo,
...localeUserSetting,
...localRole,
...systemUser,
};

View File

@ -12,7 +12,7 @@ setupMock({
// Mock.XHR.prototype.withCredentials = true;
// 用户信息
Mock.mock(new RegExp('/api/user/info'), () => {
Mock.mock(new RegExp('/api/rest/user/info'), () => {
if (isLogin()) {
const role = window.localStorage.getItem('userRole') || 'admin';
return successResponseWrap({
@ -39,7 +39,7 @@ setupMock({
});
// 登录
Mock.mock(new RegExp('/api/user/login'), (params: MockParams) => {
Mock.mock(new RegExp('/api/rest/user/login'), (params: MockParams) => {
const { username, password } = JSON.parse(params.body);
if (!username) {
return failResponseWrap(null, '用户名不能为空', 50000);

View File

@ -23,6 +23,7 @@ const router = createRouter({
requiresAuth: false,
},
},
...appRoutes,
REDIRECT_MAIN,
NOT_FOUND_ROUTE,

View File

@ -1,6 +1,7 @@
import type { RouteRecordNormalized } from 'vue-router';
const modules = import.meta.glob('./modules/*.ts', { eager: true });
const externalModules = import.meta.glob('./externalModules/*.ts', {
eager: true,
});

View File

@ -0,0 +1,28 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const CHARACTER: AppRouteRecordRaw = {
path: '/character',
name: 'character',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.role',
icon: 'icon-computer',
requiresAuth: true,
order: 2,
},
children: [
{
path: 'manage',
name: 'Manage',
component: () => import('@/views/character/manage/index.vue'),
meta: {
locale: 'menu.role.manage',
requiresAuth: true,
permissions: ['*'],
},
},
],
};
export default CHARACTER;

View File

@ -8,8 +8,8 @@ const DASHBOARD: AppRouteRecordRaw = {
meta: {
locale: 'menu.dashboard',
requiresAuth: true,
icon: 'icon-dashboard',
order: 0,
icon: 'icon-dashboard', // 设置图标
order: 0, // 排序路由菜单项。如果设置该值,值越高,越靠前
},
children: [
{
@ -24,13 +24,13 @@ const DASHBOARD: AppRouteRecordRaw = {
},
{
path: 'monitor',
name: 'Monitor',
component: () => import('@/views/dashboard/monitor/index.vue'),
path: 'monitor',// 一级路径
name: 'Monitor',// 路由名称
component: () => import('@/views/dashboard/monitor/index.vue'),// 要跳转的视图,这里要跳转到页面的基本布局
meta: {
locale: 'menu.dashboard.monitor',
requiresAuth: true,
permissions: ['admin'],
locale: 'menu.dashboard.monitor',// 菜单名字
requiresAuth: true,// 需要登录鉴权
permissions: ['admin'],// 只允许管理员用户访问
},
},
],

View File

@ -3,9 +3,10 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import useAppStore from './modules/app';
import useUserStore from './modules/user';
import useTabBarStore from './modules/tab-bar';
import useRoleStore from './modules/role';
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export { useAppStore, useUserStore, useTabBarStore };
export { useAppStore, useUserStore, useTabBarStore, useRoleStore };
export default pinia;

View File

@ -0,0 +1,54 @@
import { defineStore } from 'pinia';
import {
queryRoles,
ListRecord,
addUser,
remove,
update,
enabled,
} from '@/api/role';
import { RoleState } from './type';
const useRoleStore = defineStore('role', {
state: (): RoleState => ({
current: undefined,
pageSize: undefined,
username: undefined,
phone: undefined,
email: undefined,
enable: undefined,
nickname: undefined,
avater: undefined,
address: undefined,
}),
getters: {
roleInfo(state: RoleState): RoleState {
return { ...state };
},
},
actions: {
async getUserInfo(params: any) {
const res = await queryRoles(params);
return res;
},
async addUser(params: any) {
return addUser(params);
},
async deleteUser(id: string) {
return remove(id);
},
async updateUser(data: any) {
return update(data);
},
async enabledStatus(id: string) {
return enabled(id);
},
},
});
export default useRoleStore;

View File

@ -0,0 +1,14 @@
import { RoleRecord } from '@/api/role';
export type RoleType = '' | '*' | 'admin' | 'user' | string[];
export interface RoleState {
current?: string;
pageSize?: string;
username?: string;
phone?: string;
email?: string;
enable?: string;
nickname?: string;
avater?: string;
address?: string;
}

View File

@ -5,6 +5,7 @@ import {
switchRole,
getUserInfo,
LoginData,
me,
} from '@/api/user';
import { setToken, clearToken } from '@/utils/auth';
import { removeRouteListener } from '@/utils/route-listener';
@ -14,16 +15,19 @@ import useAppStore from '../app';
const useUserStore = defineStore('user', {
state: (): UserState => ({
username: undefined,
name: undefined,
nickName: undefined,
avatar: undefined,
email: undefined,
phone: undefined,
address: undefined,
createAt: undefined,
remark: undefined,
id: undefined,
role: undefined,
roles: undefined,
permissions: [],
headerName: '',
tokenDate: '',
}),
getters: {
@ -60,12 +64,20 @@ const useUserStore = defineStore('user', {
this.setInfo(res.data);
},
// Get user's crsf
async me() {
const res = await me();
this.headerName = res.data.csrf.headerName;
this.tokenDate = res.data.csrf.token;
return res;
},
// Login
async login(loginForm: LoginData) {
async login(loginForm: LoginData, headerName: string, token: string) {
try {
const res = await userLogin(loginForm);
// setToken(res.data.token);
setToken(res.data.token);
const res = await userLogin(loginForm, headerName, token);
setToken(token);
} catch (err) {
clearToken();
throw err;

View File

@ -3,14 +3,17 @@ import { RoleRecord } from '@/api/role';
export type RoleType = '' | '*' | 'admin' | 'user' | string[];
export interface UserState {
username?: string;
name?: string;
nickName?: string;
avatar?: string;
email?: string;
phone?: string;
address?: string;
createAt?: string;
remark?: string;
id?: number;
role?: RoleRecord;
roles?: RoleRecord[];
permissions?: string[] | '' | '*' | 'admin' | 'user';
headerName: string;
tokenDate: string;
}

View File

@ -24,9 +24,8 @@ export interface PostData {
}
export interface Pagination {
current: number;
pageSize: number;
total?: number;
page: number;
size: number;
}
export type TimeRanger = [string, string];

View File

@ -0,0 +1,489 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.role', 'menu.role.manage']" />
<a-card class="general-card" :title="$t('menu.list.searchTable')">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="left"
>
<a-row :gutter="16">
<a-col :span="7">
<a-form-item
field="username"
:label="$t('searchTable.form.username')"
>
<a-input
v-model="formModel.username"
:placeholder="$t('searchTable.form.username.placeholder')"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item
field="phone"
:label="$t('searchTable.form.phone')"
>
<a-input
v-model="formModel.phone"
:placeholder="$t('searchTable.form.phone.placeholder')"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item
field="email"
:label="$t('searchTable.form.email')"
>
<a-input
v-model="formModel.email"
:placeholder="$t('searchTable.form.email.placeholder')"
/>
</a-form-item>
</a-col>
<!-- <a-col :span="9">
<a-form-item field="role" :label="$t('searchTable.form.role')">
<a-select
v-model="formModel.role"
:options="roleOptions"
:placeholder="$t('searchTable.form.role.placeholder')"
/>
</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>
{{ $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-row :gutter="10">
<a-col :span="9">
<a-button
type="primary"
@click="handleClick"
style="margin-right: 16px"
>
<template #icon>
<icon-plus />
</template>
{{ $t('searchTable.operation.create') }}
</a-button>
<a-button type="primary" status="danger" @click="handleDelete">
<template #icon>
<icon-close />
</template>
{{ $t('searchTable.operation.delete') }}
</a-button>
</a-col>
</a-row>
<a-divider style="margin-top: 16px" />
<a-table
row-key="id"
:loading="loading"
:pagination="false"
:columns="(cloneColumns as TableColumnData[])"
:data="renderData"
:bordered="false"
:size="size"
:row-selection="true"
@select="selectRow"
>
<template #index="{ rowIndex }">
{{ rowIndex + 1 + (pagination.page - 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="text"
size="small"
@click="handleClick(record, 'modify')"
>
{{ $t('searchTable.columns.operations.detail') }}
</a-button>
</template>
</a-table>
<a-space direction="vertical" size="large" style="margin-top: 16px">
<a-pagination
show-total
:current="pagination.page"
@change="onPageChange"
:page-size="pagination.pageSize"
:total="pagination.total"
/>
</a-space>
<a-modal
v-model:visible="visible"
:title="dialogTitle"
:mask-closable="false"
@cancel="handleCancel"
:before-ok="handleBeforeOk"
>
<a-form :model="formDate">
<a-form-item field="username" :label="$t('add.user.info.username')">
<a-input
v-model="formDate.username"
:placeholder="$t('add.user.info.username.placeholder')"
:required="required"
:rules="[
{
required: true,
message: '用户名不能为空',
},
]"
/>
</a-form-item>
<a-form-item field="password" :label="$t('add.user.info.password')">
<a-input-password
v-model="formDate.password"
:placeholder="$t('add.user.info.password.placeholder')"
:rules="[
{
required: true,
message: '密码不能为空',
},
]"
/>
</a-form-item>
<a-form-item field="phone" :label="$t('add.user.info.phone')">
<a-input
v-model="formDate.phone"
:placeholder="$t('add.user.info.phone.placeholder')"
:rules="[
{
required: true,
message: '手机号码不能为空',
},
]"
/>
</a-form-item>
<a-form-item field="email" :label="$t('add.user.info.email')">
<a-input
v-model="formDate.email"
:placeholder="$t('add.user.info.email.placeholder')"
/>
</a-form-item>
<a-form-item field="nickName" :label="$t('add.user.info.nickName')">
<a-input
v-model="formDate.nickName"
:placeholder="$t('add.user.info.nickName.placeholder')"
/>
</a-form-item>
<a-form-item field="address" :label="$t('add.user.info.address')">
<a-input
v-model="formDate.address"
:placeholder="$t('add.user.info.address.placeholder')"
/>
</a-form-item>
<a-form-item field="dept" :label="$t('add.user.info.dept')">
<a-select
v-model="formDate.dept"
:placeholder="$t('add.user.info.dept.placeholder')"
>
<a-option value="user">user</a-option>
<a-option value="auditor">auditor</a-option>
<a-option value="admin">admin</a-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import { useRoleStore } from '@/store';
import { ListParams } from '@/api/list';
import { Pagination } from '@/types/global';
import { ListRecord } from '@/api/role';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { status } from 'nprogress';
import { stat } from 'fs';
type SizeProps = 'mini' | 'small' | 'medium' | 'large';
type Column = TableColumnData & { checked?: true };
//
const generateFormModel = () => {
return {
username: '',
phone: '',
email: '',
role: '',
};
};
const { t } = useI18n();
const { loading, setLoading } = useLoading();
const roleStore = useRoleStore();
const renderData = ref<ListRecord[]>([]);
const cloneColumns = ref<Column[]>([]);
const showColumns = ref<Column[]>([]);
const size = ref<SizeProps>('medium');
const seleteRowList = ref();
const visible = ref(false);
const messageType = ref();
let formDifer = {};
const dialogTitle = ref('添加用户');
const form = () => {
return {
id: '',
username: '',
password: '',
phone: '',
email: '',
nickName: '',
address: '',
dept: '',
};
};
const formDate = ref(form());
//
const handleClick = (record: any, type: string) => {
if (type === 'modify') {
messageType.value = 'modify';
formDifer = record;
formDate.value = { ...record };
dialogTitle.value = '修改用户信息';
}
visible.value = true;
};
//
const diffDataForm = (newData: any, oldData: any) => {
const result = {}; //
Object.keys(oldData).forEach((key) => {
if (oldData[key] !== newData[key] || key === 'id') {
result[key] = newData[key];
}
});
return result;
};
//
const handleBeforeOk = async (done: any) => {
try {
formDifer = diffDataForm(formDate.value, formDifer);
if (dialogTitle.value === '修改用户信息') {
const res = await roleStore.updateUser(formDifer);
if (res.status === 200) {
Message.success({
content: t('add.user.info.sucess'),
duration: 3 * 1000,
});
} else {
Message.error({
content: t('add.user.info.fail'),
duration: 3 * 1000,
});
}
} else {
const res = await roleStore.addUser(formDate.value);
if (res.status === 200) {
Message.success({
content: t('modify.user.info.sucess'),
duration: 3 * 1000,
});
} else {
Message.error({
content: t('modify.user.info.fail'),
duration: 3 * 1000,
});
}
}
} catch (err) {
// you can report use errorHandler or other
} finally {
formDate.value = form();
search();
}
};
//
const enabledStatus = async (record: string) => {
const res = await roleStore.enabledStatus(record.id);
search();
};
//
const selectRow = (rowKeys: string[]) => {
seleteRowList.value = rowKeys;
};
//
const handleDelete = async () => {
const res = await roleStore.deleteUser(seleteRowList.value);
};
//
const handleCancel = () => {
formDate.value = form();
visible.value = false;
dialogTitle.value = '添加用户';
};
// formModel
const formModel = ref(generateFormModel());
//
const basePagination: Pagination = {
page: 1,
size: 10,
};
const pagination = reactive({
...basePagination,
});
//
const onPageChange = (current: number) => {
const page = current;
fetchData({ ...basePagination, page });
};
//
const fetchData = async (params: { page: 1; size: 10 }) => {
setLoading(true);
try {
const { data } = await roleStore.getUserInfo(params);
renderData.value = data.list;
pagination.page = params.page;
pagination.total = data.total;
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
//
const search = () => {
fetchData({
...basePagination,
...formModel.value,
} as unknown as any);
};
search();
//
const reset = () => {
formModel.value = generateFormModel();
};
//
const columns = computed<TableColumnData[]>(() => [
{
title: t('searchTable.columns.index'),
dataIndex: 'index',
slotName: 'index',
},
{
title: t('searchTable.columns.username'),
dataIndex: 'username',
},
{
title: t('searchTable.columns.phone'),
dataIndex: 'phone',
},
{
title: t('searchTable.columns.email'),
dataIndex: 'email',
},
{
title: t('searchTable.columns.nickName'),
dataIndex: 'nickName',
},
{
title: t('searchTable.columns.address'),
dataIndex: 'address',
},
{
title: t('searchTable.columns.enabled'),
dataIndex: 'enabled',
slotName: 'enabled',
},
{
title: t('searchTable.columns.operations'),
dataIndex: 'operations',
slotName: 'operations',
},
]);
//
watch(
() => columns.value,
(val) => {
cloneColumns.value = val;
cloneColumns.value.forEach((item, index) => {
item.checked = true;
});
showColumns.value = cloneColumns.value;
},
{ deep: true, immediate: true }
);
//
const roleOptions = computed<SelectOptionData[]>(() => [
{
label: t('searchTable.form.role.user'),
vlaue: 'user',
},
{
label: t('searchTable.form.role.auditor'),
vlaue: 'auditor',
},
]);
</script>
<script lang="ts">
export default {
name: 'Manage',
};
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
}
</style>

View File

@ -0,0 +1,45 @@
export default {
'menu.list.searchTable': 'Search Table',
'menu.role.manage': 'Role Manage',
'searchTable.form.number': 'Set Number',
'searchTable.form.number.placeholder': 'Please enter Set Number',
'searchTable.form.name': 'Set Name',
'searchTable.form.name.placeholder': 'Please enter Set Name',
'searchTable.form.contentType': 'Content Type',
'searchTable.form.contentType.img': 'image-text',
'searchTable.form.contentType.horizontalVideo': 'Horizontal short video',
'searchTable.form.contentType.verticalVideo': 'Vertical short video',
'searchTable.form.filterType': 'Filter Type',
'searchTable.form.filterType.artificial': 'artificial',
'searchTable.form.filterType.rules': 'Rules',
'searchTable.form.createdTime': 'Create Date',
'searchTable.form.status': 'Status',
'searchTable.form.status.online': 'Online',
'searchTable.form.status.offline': 'Offline',
'searchTable.form.search': 'Search',
'searchTable.form.reset': 'Reset',
'searchTable.form.selectDefault': 'All',
'searchTable.operation.create': 'Create',
'searchTable.operation.import': 'Import',
'searchTable.operation.download': 'Download',
// columns
'searchTable.columns.index': '#',
'searchTable.columns.number': 'Set Number',
'searchTable.columns.name': 'Set Name',
'searchTable.columns.contentType': 'Content Type',
'searchTable.columns.filterType': 'Filter Type',
'searchTable.columns.count': 'Count',
'searchTable.columns.createdTime': 'CreatedTime',
'searchTable.columns.status': 'Status',
'searchTable.columns.operations': 'Operations',
'searchTable.columns.operations.view': 'View',
// size
'searchTable.size.mini': 'mini',
'searchTable.size.small': 'small',
'searchTable.size.medium': 'middle',
'searchTable.size.large': 'large',
// actions
'searchTable.actions.refresh': 'refresh',
'searchTable.actions.density': 'density',
'searchTable.actions.columnSetting': 'columnSetting',
};

View File

@ -0,0 +1,46 @@
export default {
'menu.list.searchTable': '查询表格',
'menu.role.manage': '角色管理',
'roleManage.username': '用户名',
'searchTable.form.username': '用户名',
'searchTable.form.username.placeholder': '请输入用户名',
'searchTable.form.phone': '电话号码',
'searchTable.form.phone.placeholder': '请输入电话号码',
'searchTable.form.role': '角色',
'searchTable.form.role.placeholder': '请选择角色',
'searchTable.form.role.user': '普通用户',
'searchTable.form.email': 'Email',
'searchTable.form.email.placeholder': '请输入Email',
'searchTable.form.role.auditor': '审核员',
'searchTable.form.search': '查询',
'searchTable.form.reset': '重置',
'searchTable.columns.nickName': '昵称',
'searchTable.columns.username': '用户名',
'searchTable.columns.phone': '电话',
'searchTable.columns.email': 'Email',
'searchTable.columns.enabled': '是否启用',
'searchTable.columns.avater': '头像',
'searchTable.columns.address': '地址',
'searchTable.columns.operations.detail': '详细',
'searchTable.operation.create': '添加',
'searchTable.operation.delete': '删除',
'add.user.info': '添加用户',
'add.user.info.username': '用户名',
'add.user.info.username.placeholder': '请输入用户名',
'add.user.info.password': '密码',
'add.user.info.password.placeholder': '请输入密码',
'add.user.info.phone': '电话号码',
'add.user.info.phone.placeholder': '请输入电话号码',
'add.user.info.email': 'Email',
'add.user.info.email.placeholder': '请输入Email',
'add.user.info.nickName': '昵称',
'add.user.info.nickName.placeholder': '请输入昵称',
'add.user.info.address': '地址',
'add.user.info.address.placeholder': '请输入地址',
'add.user.info.dept': '角色',
'add.user.info.dept.placeholder': '请选择角色',
'add.user.info.sucess': '添加成功',
'add.user.info.fail': '添加失败',
'modify.user.info.sucess': '修改成功',
'modify.user.info.fail': '修改失败',
};

View File

@ -56,6 +56,7 @@
<a-button type="primary" html-type="submit" long :loading="loading">
{{ $t('login.form.login') }}
</a-button>
<a-button type="text" long class="login-form-register-btn">
{{ $t('login.form.register') }}
</a-button>
@ -73,7 +74,9 @@
import { useStorage } from '@vueuse/core';
import { useUserStore } from '@/store';
import useLoading from '@/hooks/loading';
import type { LoginData } from '@/api/user';
import { LoginData } from '@/api/user';
const router = useRouter();
const { t } = useI18n();
@ -82,37 +85,66 @@
const userStore = useUserStore();
const loginConfig = useStorage('login-config', {
// 使 useStorage
rememberPassword: true,
username: '', //
password: '', // demo default value
});
const userInfo = reactive({
// loginConfig username password
username: loginConfig.value.username,
password: loginConfig.value.password,
});
const handleSubmit = async ({
//
errors,
values,
}: {
errors: Record<string, ValidatedError> | undefined;
values: Record<string, any>;
}) => {
//
if (loading.value) return;
//
if (!errors) {
setLoading(true);
try {
await userStore.login(values as LoginData);
const res = await userStore.me();
// console.log('e', res.data.csrf);
// await userCsrf.update(res)
// userCsrf.content = res.data.csrf;
// 使 userStore.login
await userStore.login(
values as LoginData,
res.data.csrf.headerName,
res.data.csrf.token
);
const csrf = await userStore.me();
// console.log('vue', csrf.data.csrf);
// userCsrf.content = csrf.data.csrf;
// let {crsf} = storeToRefs(userStore)
// useUserStore.user.csrf = csrf.data.csrf;
//
const { redirect, ...othersQuery } = router.currentRoute.value.query;
router.push({
name: (redirect as string) || 'Workplace',
name: 'Workplace',
query: {
...othersQuery,
},
});
//
Message.success(t('login.form.login.success'));
const { rememberPassword } = loginConfig.value;
const { username, password } = values;
//
// The actual production environment requires encrypted storage.
loginConfig.value.username = rememberPassword ? username : '';

View File

@ -1,5 +1,5 @@
export default {
'login.form.title': '中山学院学习平台',
'login.form.title': '学院学习平台',
'login.form.userName.errMsg': '用户名不能为空',
'login.form.password.errMsg': '密码不能为空',
'login.form.login.errMsg': '登录出错,请刷新重试',

View File

@ -8,20 +8,22 @@
<img :src="userInfo.avatar" />
</a-avatar>
<a-typography-title :heading="6" style="margin: 0">
{{ userInfo.username }}
{{ userInfo.nickName }}
</a-typography-title>
<div class="user-msg">
<a-space :size="18">
<div>
<icon-user />
<a-typography-text>{{ userInfo.username }}</a-typography-text>
</div>
<div>
<icon-home />
<icon-phone />
<a-typography-text>
{{ userInfo.phone }}
</a-typography-text>
</div>
<div>
<icon-email />
<a-typography-text>
{{ userInfo.email }}
</a-typography-text>
</div>
<!-- <div>-->
<!-- <icon-location />-->
<!-- <a-typography-text>{{ userInfo.locationName }}</a-typography-text>-->

View File

@ -38,7 +38,7 @@
/>
</a-form-item>
<a-form-item
field="name"
field="nickName"
label="昵称"
:rules="[
{
@ -48,13 +48,13 @@
]"
>
<a-input
v-model="formData.name"
v-model="formData.nickName"
:placeholder="$t('userSetting.basicInfo.placeholder.nickname')"
/>
</a-form-item>
<a-form-item
field="profile"
label="备注"
field="address"
label="地址"
:rules="[
{
maxLength: 200,
@ -63,7 +63,7 @@
]"
row-class="keep-margin"
>
<a-textarea v-model="formData.remark" />
<a-textarea v-model="formData.address" />
</a-form-item>
<a-form-item>
<a-space>

View File

@ -7,13 +7,26 @@
:wrapper-col-props="{ span: 16 }"
>
<a-form-item
field="newPassword"
field="oldPassword"
:label="$t('userSetting.passwordReset.form.label.oldPassword')"
:rules="[{ required: true, message: $t('login.form.password.errMsg') }]"
>
<a-input-password
v-model="formData.oldPassword"
placeholder="请输入原密码"
allow-clear
>
</a-input-password>
</a-form-item>
<a-form-item
field="password"
:label="$t('userSetting.passwordReset.form.label.newPassword')"
:rules="[{ required: true, message: $t('login.form.password.errMsg') }]"
:validate-trigger="['change', 'blur']"
>
<a-input-password
v-model="formData.newPassword"
v-model="formData.password"
placeholder="请输入新密码"
allow-clear
>
@ -51,13 +64,14 @@
import { FormInstance } from '@arco-design/web-vue/es/form';
import { PasswordReSetModel, resetPassword } from '@/api/user';
import { Message } from '@arco-design/web-vue';
import useUser from "@/hooks/user";
import useUser from '@/hooks/user';
const { t } = useI18n();
const formRef = ref<FormInstance>();
const formData = ref<PasswordReSetModel>({
newPassword: '',
oldPassword: '',
password: '',
confirmPassword: '',
});
const checkEquals = (
@ -66,7 +80,7 @@
) => {
if (!value) {
callback(t('userSetting.passwordReset.form.validate.blank'));
} else if (formData.value.newPassword !== formData.value.confirmPassword) {
} else if (formData.value.password !== formData.value.confirmPassword) {
callback(t('userSetting.passwordReset.form.validate.noEquals'));
}
};
@ -77,13 +91,15 @@
if (!vali) {
// do some thing
// you also can use html-type to submit
const res = await resetPassword(formData.value);
if (res.status === 200) {
Message.success({
content: res.data,
duration: 5 * 1000,
});
await user.logout();
// await user.logout();
}
}
};

View File

@ -45,6 +45,7 @@ export default {
'userSetting.basicInfo.placeholder.profile':
'Please enter your profile, no more than 200 words',
'userSetting.form.error.profile.maxLength': 'No more than 200 words',
'userSetting.passwordReset.form.label.oldPassword': 'Orinal Password',
'userSetting.SecuritySettings.form.label.password': 'Login Password',
'userSetting.SecuritySettings.placeholder.password':
'Has been set. The password must contain at least six letters, digits, and special characters except Spaces. The password must contain both uppercase and lowercase letters.',

View File

@ -33,6 +33,7 @@ export default {
'userSetting.basicInfo.placeholder.profile':
'请输入您的个人简介最多不超过200字。',
'userSetting.form.error.profile.maxLength': '最多不超过200字',
'userSetting.passwordReset.form.label.oldPassword': '原密码',
'userSetting.passwordReset.form.label.newPassword': '新密码',
'userSetting.passwordReset.form.label.confirmPassword': '确认新密码',
'userSetting.SecuritySettings.form.label.password': '登录密码',