提交 486f65e1 编写于 作者: J Jachin

chore: prettier & lint

上级 f24f00f1
......@@ -5,4 +5,10 @@ NODE_ENV='development'
VITE_APP_TITLE = 'vue3-element-admin'
VITE_APP_PORT = 3000
# API请求前缀
VITE_APP_BASE_API = '/dev-api'
# proxy代理配置
VITE_APP_TARGET_URL = 'http://localhost:3000'
VITE_APP_TARGET_BASE_API = ''
......@@ -2,4 +2,10 @@
VITE_APP_TITLE = 'vue3-element-admin'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/prod-api'
# API请求前缀
VITE_APP_BASE_API = 'http://vapi.youlai.tech'
# proxy代理配置
VITE_APP_TARGET_URL = "http://vapi.youlai.tech"
VITE_APP_TARGET_BASE_API = ''
......@@ -3,4 +3,6 @@ NODE_ENV='staging'
VITE_APP_TITLE = 'vue3-element-admin'
VITE_APP_PORT = 3000
VITE_APP_TARGET_URL = 'http://localhost:3000';
VITE_APP_BASE_API = '/prod--api'
......@@ -266,4 +266,4 @@
"watchWithFilter": true,
"whenever": true
}
}
\ No newline at end of file
}
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
......@@ -10,11 +11,16 @@ module.exports = {
"plugin:vue/vue3-recommended",
"./.eslintrc-auto-import.json",
"prettier",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
parser: "@typescript-eslint/parser",
project: "./tsconfig.*?.json",
createDefaultProgram: false,
extraFileExtensions: [".vue"],
},
plugins: ["vue", "@typescript-eslint"],
rules: {
......@@ -23,6 +29,41 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "off", // 关闭any类型的警告
"vue/no-v-model-argument": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"vue/script-setup-uses-vars": "error",
"vue/no-reserved-component-names": "off",
"vue/custom-event-name-casing": "off",
"vue/attributes-order": "off",
"vue/one-component-per-file": "off",
"vue/html-closing-bracket-newline": "off",
"vue/max-attributes-per-line": "off",
"vue/multiline-html-element-content-newline": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/attribute-hyphenation": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "never",
component: "always",
},
svg: "always",
math: "always",
},
],
"vue/multi-word-component-names": "off",
},
// eslint不能对html文件生效
overrides: [
......
......@@ -6,7 +6,6 @@ dist-ssr
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
......@@ -15,3 +14,4 @@ dist-ssr
package-lock.json
pnpm-lock.yaml
stats.html
......@@ -8,3 +8,4 @@ public
*.md
src/assets
stats.html
......@@ -8,3 +8,4 @@ public
*.md
src/assets
stats.html
{
"recommendations": ["Vue.volar"]
}
{
"typescript.tsdk": "./node_modules/typescript/lib",
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"material-icon-theme.folders.color": "#42a5f5",
"material-icon-theme.folders.theme": "specific",
"material-icon-theme.activeIconPack": "vue_vuex",
"path-intellisense.mappings": {
"@/": "${workspaceRoot}/src/"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"antd",
"antv",
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echarts",
"ERUDA",
"esnext",
"esno",
"iconify",
"INTLIFY",
"lint-staged",
"lintstagedrc",
"logicflow",
"mkcert",
"mockjs",
"nprogress",
"persistedstate",
"pinia",
"pnpm",
"qrcode",
"sider",
"sortablejs",
"stylelint",
"tailwindcss",
"tdesign",
"tinymce",
"unocss",
"unplugin",
"vben",
"vditor",
"Vite",
"vitejs",
"vueuse",
"windi",
"windicss",
"zxcvbn"
],
}
import { MockMethod } from "vite-plugin-mock";
const url = "/api/v1/menus/routes";
const method = "get";
const data = {
code: "00000",
data: [
{
path: "/system",
component: "Layout",
redirect: "/system/user",
meta: {
title: "系统管理",
icon: "system",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "user",
component: "system/user/index",
name: "User",
meta: {
title: "用户管理",
icon: "user",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "role",
component: "system/role/index",
name: "Role",
meta: {
title: "角色管理",
icon: "role",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "menu",
component: "system/menu/index",
name: "Menu",
meta: {
title: "菜单管理",
icon: "menu",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "dept",
component: "system/dept/index",
name: "Dept",
meta: {
title: "部门管理",
icon: "tree",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "dict",
component: "system/dict/index",
name: "Dict",
meta: {
title: "字典管理",
icon: "dict",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
],
},
{
path: "/api",
component: "Layout",
meta: {
title: "接口",
icon: "api",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "apidoc",
component: "demo/api-doc",
name: "Apidoc",
meta: {
title: "接口文档",
icon: "api",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
],
},
{
path: "/external-link",
component: "Layout",
redirect: "noredirect",
meta: {
title: "外部链接",
icon: "link",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "https://juejin.cn/post/7228990409909108793",
meta: {
title: "document",
icon: "document",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
],
},
{
path: "/multi-level",
component: "Layout",
redirect: "/multi-level/multi-level1",
meta: {
title: "多级菜单",
icon: "multi_level",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "multi-level1",
component: "demo/multi-level/level1",
redirect: "/multi-level/multi-level2",
meta: {
title: "菜单一级",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "multi-level2",
component: "demo/multi-level/children/level2",
redirect: "/multi-level/multi-level2/multi-level3-1",
meta: {
title: "菜单二级",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "multi-level3-1",
component: "demo/multi-level/children/children/level3-1",
name: "MultiLevel31",
meta: {
title: "菜单三级-1",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "multi-level3-2",
component: "demo/multi-level/children/children/level3-2",
name: "MultiLevel32",
meta: {
title: "菜单三级-2",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
],
},
],
},
],
},
{
path: "/component",
component: "Layout",
meta: {
title: "组件封装",
icon: "menu",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "wang-editor",
component: "demo/wang-editor",
name: "WangEditor",
meta: {
title: "富文本编辑器",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "upload",
component: "demo/upload",
name: "Upload",
meta: {
title: "图片上传",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "icon-selector",
component: "demo/icon-selector",
name: "IconSelector",
meta: {
title: "图标选择器",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "dict-demo",
component: "demo/dict",
name: "DictDemo",
meta: {
title: "字典组件",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "taginput",
component: "demo/taginput",
name: "Taginput",
meta: {
title: "标签输入框",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "signature",
component: "demo/signature",
name: "Signature",
meta: {
title: "签名",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "table",
component: "demo/table",
name: "Table",
meta: {
title: "表格",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
],
},
{
path: "/function",
component: "Layout",
meta: {
title: "功能演示",
icon: "menu",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
children: [
{
path: "websocket",
component: "demo/websocket",
name: "Websocket",
meta: {
title: "Websocket",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
{
path: "other",
component: "demo/other",
meta: {
title: "敬请期待...",
icon: "",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
],
},
],
msg: "一切ok",
};
export default [
{
url: url,
method: method,
response: () => {
return data;
},
},
] as MockMethod[];
import { MockMethod } from "vite-plugin-mock";
export default [
{
url: "/api/hello_world",
method: "get",
response: (request) => {
return {
msg: "hello world",
headers: request.headers,
};
},
},
{
url: "/api/v1/auth/captcha",
method: "get",
response: () => {
return {
code: "00000",
data: {
verifyCodeKey: "534b8ef2b0a24121bec76391ddd159f9",
verifyCodeBase64:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAkCAIAAADNSmkJAAAFKUlEQVR4Xu2ZXUwcVRiGV70wMWo08V5NvPXCrDbFaGpMaZW2hqQxaoiJTRsaMBCNSYtpa2JTKiFSelFa+Q/QZcMWqEhBlh+htbEpZhMrBQrlJ0hBywLLyrJ0WZbje3bqOvPNLHPWrDvdOE9ONmfe78zkzMs335wzWJhJQrBQweS/wTQ6QWgYHdoIOcecOe05O+t2WkutO+p2ZF3Ksg/YV9ZW6FATYajR3nveg60H9327r3O8c35lHgp+r05dPdJzBL73TPSQ8SaCKIxGLsPlop+K0JHrEkPuoT31e5qGmmjARACF0agYyGVNlyVm/pzZXrN9fHGcBkz0UBid+31u93i3XFFT80vN8cvHqWqih8Lo1NpUqS5vwh3vnd223VQ10UNh9NbyrcFQUK6oCawHUipSqGqiB83oBf+CXFGDMp1mS6OqiR4Ko7FexkpOrqhpHGw82nOUqiZ6KIzGrkRuorW0dJMmOy+hOCfYGzb2RBFv6HRO0gEJw/U7y+pgL1bwmTxexN6sZ31TdEwEhdG+gA+7EqyXpUO1uZH20cWL8hMTRt1N9tBXzCJrOIRoCPJpSO2RAp4HmtCdIfZ+2JWgEBN9LbR28seTGU0Zue1tMLp+YIAMSADzfvbkKX4/eb28j4YODiGin3heqmIlLja5hAUCu+nmGY3JWKvpMAlqNGgebsauBOvlqSX+JEx7p7EbTLen53XlzfmWUioqXikrc68Y8N2juJ/fyVsNChGHEE//rBANYWaZz+TRQqpLaBgNsPfDrgSpbS21YtV87IdjrlkX9JZbt5DOma2t9ITo5F+5glN22WwL/n+yDv00mw06orKxOqQ5+J04hhViwzAXETIcJDVm8uxZqktoGx2Nj9t43Wgaul/ERQiGQvtbWnDWgZYW9CXlQFjZ/7ciyHNn+Z2MexTimIeLz59TiIln0M1e+IbPpOAaDUnEYPTi6iqKxpbycs/qKo1tCslfKcffPn9enuMiPPY1vxO/ckeFQ4h46cdGqUWoidE/y54q5tPY5WDrGzQqIXot4BgchEE57e00IMCw2/1qZSVO/7SjA78o9INzcxsbrL+fnTnDDh9mmZn8F30oG1Hm+nABv5mQMopDS/h1HxtqTzWbABMe9sxpPoe9zezeOo1GELqWhPS8t46M0IAYHbdvR1aHbaOjbjfLz2eFhez6dba4yAfgF30o0BFVE8+Mjh/wFxPI+I5mAEHU6Ls+38vhTFwOBGhMDF8gkFpbC5ffsdv/uBs6dIj19dExEtARVXv9YNbop8NFY3aZ6gRRo+tu3IBHnzmdNCBMXldXJKPfL74WzWUJRE+coDUknqsOdZXQbAJYwluVTbOZI3Qt8GFzMwxyjo3RgBiN4fr+elXVpZGRLWXl6PdOTtJBSlBDUK/lnIrjOlrtqWYTQDJaF6FrTXu9sOa1ysrVoM5HVE1GFxZQcyJ/p+xzv6K/rbr6N6+XDpUBl0tKFIrbz78qWB6YnWFMCBld4XLBms+7df75ook/GNzb0GCV7U1Qfz9p64TyQWNjYD3qe9rj4SMJtQP3MyjSDPzWIRHPjH7X4YAvfXoPuyZf9Pbi3PcuXIh4mp3NllYC6XY79C+jl2o8PBipxjnBttn4MgMNnWgfcRJGPI2OL8hTj3LloIlmRicvBhiNykvecpqoa3RSY4DRcLAwyicuOepVR1JjgNFYHWONHL04czTX0UmNAUYD7Pr+xc4wqTHGaBb2OtZvHUmNYUazcA2J6etdUmOk0f8rTKMTxF91RG0D1SwYGwAAAABJRU5ErkJggg==",
},
msg: "一切ok",
};
},
},
{
url: "/api/v1/auth/login",
method: "post",
response: () => {
return {
code: "00000",
data: {
accessToken:
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImp0aSI6IjE2YWJkNTlkOTAxNzQwZDliYmI3ZjczODBhZDkyNzNhIiwidXNlcklkIjoyLCJ1c2VybmFtZSI6ImFkbWluIiwiZGVwdElkIjoxLCJkYXRhU2NvcGUiOjEsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNjkxMTAzMzgyfQ.P4cuIfmPepl3HuguhMS7NXn5a7IUPpsLbmtA_rHOhHk",
tokenType: "Bearer",
refreshToken: null,
expires: null,
},
msg: "一切ok",
};
},
},
{
url: "/api/v1/users/me",
method: "get",
response: () => {
return {
code: "00000",
data: {
userId: 2,
nickname: "系统管理员",
avatar:
"https://oss.youlai.tech/youlai-boot/2023/05/16/811270ef31f548af9cffc026dfc3777b.gif",
roles: ["ADMIN"],
perms: [
"sys:menu:delete",
"sys:dept:edit",
"sys:dict_type:add",
"sys:dict:edit",
"sys:dict:delete",
"sys:dict_type:edit",
"sys:menu:add",
"sys:user:add",
"sys:role:edit",
"sys:dept:delete",
"sys:user:edit",
"sys:user:delete",
"sys:user:reset_pwd",
"sys:dept:add",
"sys:role:delete",
"sys:dict_type:delete",
"sys:menu:edit",
"sys:dict:add",
"sys:role:add",
],
},
msg: "一切ok",
};
},
},
{
url: "/api/v1/auth/logout",
method: "delete",
response: () => {
return {
code: "00000",
data: {},
msg: "string",
};
},
},
] as MockMethod[];
......@@ -2,11 +2,10 @@
"name": "vue3-element-admin",
"private": true,
"version": "2.4.1",
"type": "module",
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "vite serve --mode development",
"build:prod": "vite build --mode production &&vue-tsc --noEmit",
"build:prod": "vite build --mode production && vue-tsc --noEmit",
"prepare": "husky install",
"lint:eslint": "eslint --fix --ext .ts,.js,.vue ./src ",
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"",
......@@ -68,7 +67,6 @@
"@types/lodash": "^4.14.195",
"@types/nprogress": "^0.2.0",
"@types/path-browserify": "^1.0.0",
"@types/xlsx": "^0.0.36",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"autoprefixer": "^10.4.14",
......@@ -76,6 +74,7 @@
"cz-git": "^1.6.1",
"eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.13.0",
"fast-glob": "^3.2.11",
......@@ -85,14 +84,15 @@
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.58.3",
"stylelint": "^15.7.0",
"stylelint": "^15.10.2",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^4.0.0",
"stylelint-config-recommended-scss": "11.0.0 ",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^33.0.0",
"stylelint-config-standard-scss": "^9.0.0",
"stylelint-config-recommended-scss": "^12.0.0",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^10.0.0",
"typescript": "^5.0.4",
"unocss": "^0.51.13",
"unplugin-auto-import": "^0.15.3",
......@@ -100,8 +100,9 @@
"unplugin-vue-components": "^0.24.1",
"vite": "^4.4.2",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-mock": "^3.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.6.5 "
"vue-tsc": "^1.7.0 "
},
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
"author": "有来开源组织",
......
<script setup lang="ts">
import { ElConfigProvider } from 'element-plus';
import { useAppStore } from '@/store/modules/app';
import { ElConfigProvider } from "element-plus";
import { useAppStore } from "@/store/modules/app";
const appStore = useAppStore();
</script>
......
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DeptForm, DeptQuery, DeptVO } from './types';
import request from "@/utils/request";
import { AxiosPromise } from "axios";
import { DeptForm, DeptQuery, DeptVO } from "./types";
/**
* 部门树形表格
......@@ -9,9 +9,9 @@ import { DeptForm, DeptQuery, DeptVO } from './types';
*/
export function listDepts(queryParams?: DeptQuery): AxiosPromise<DeptVO[]> {
return request({
url: '/api/v1/dept',
method: 'get',
params: queryParams
url: "/api/v1/dept",
method: "get",
params: queryParams,
});
}
......@@ -20,8 +20,8 @@ export function listDepts(queryParams?: DeptQuery): AxiosPromise<DeptVO[]> {
*/
export function listDeptOptions(): AxiosPromise<[]> {
return request({
url: '/api/v1/dept/options',
method: 'get'
url: "/api/v1/dept/options",
method: "get",
});
}
......@@ -32,8 +32,8 @@ export function listDeptOptions(): AxiosPromise<[]> {
*/
export function getDeptForm(id: number): AxiosPromise<DeptForm> {
return request({
url: '/api/v1/dept/' + id + '/form',
method: 'get'
url: "/api/v1/dept/" + id + "/form",
method: "get",
});
}
......@@ -44,9 +44,9 @@ export function getDeptForm(id: number): AxiosPromise<DeptForm> {
*/
export function addDept(data: DeptForm) {
return request({
url: '/api/v1/dept',
method: 'post',
data: data
url: "/api/v1/dept",
method: "post",
data: data,
});
}
......@@ -58,9 +58,9 @@ export function addDept(data: DeptForm) {
*/
export function updateDept(id: number, data: DeptForm) {
return request({
url: '/api/v1/dept/' + id,
method: 'put',
data: data
url: "/api/v1/dept/" + id,
method: "put",
data: data,
});
}
......@@ -71,7 +71,7 @@ export function updateDept(id: number, data: DeptForm) {
*/
export function deleteDept(ids: string) {
return request({
url: '/api/v1/dept/' + ids,
method: 'delete'
url: "/api/v1/dept/" + ids,
method: "delete",
});
}
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { FileInfo } from './types';
import request from "@/utils/request";
import { AxiosPromise } from "axios";
import { FileInfo } from "./types";
/**
* 上传文件
......@@ -9,14 +9,14 @@ import { FileInfo } from './types';
*/
export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
const formData = new FormData();
formData.append('file', file);
formData.append("file", file);
return request({
url: '/api/v1/files',
method: 'post',
url: "/api/v1/files",
method: "post",
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
"Content-Type": "multipart/form-data",
},
});
}
......@@ -27,8 +27,8 @@ export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
*/
export function deleteFileApi(filePath?: string) {
return request({
url: '/api/v1/files',
method: 'delete',
params: { filePath: filePath }
url: "/api/v1/files",
method: "delete",
params: { filePath: filePath },
});
}
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { MenuQuery, MenuVO, MenuForm } from './types';
import request from "@/utils/request";
import { AxiosPromise } from "axios";
import { MenuQuery, MenuVO, MenuForm } from "./types";
/**
* 获取路由列表
*/
export function listRoutes() {
return request({
url: '/api/v1/menus/routes',
method: 'get'
url: "/api/v1/menus/routes",
method: "get",
});
}
......@@ -19,9 +19,9 @@ export function listRoutes() {
*/
export function listMenus(queryParams: MenuQuery): AxiosPromise<MenuVO[]> {
return request({
url: '/api/v1/menus',
method: 'get',
params: queryParams
url: "/api/v1/menus",
method: "get",
params: queryParams,
});
}
......@@ -30,8 +30,8 @@ export function listMenus(queryParams: MenuQuery): AxiosPromise<MenuVO[]> {
*/
export function listMenuOptions(): AxiosPromise<OptionType[]> {
return request({
url: '/api/v1/menus/options',
method: 'get'
url: "/api/v1/menus/options",
method: "get",
});
}
......@@ -42,8 +42,8 @@ export function listMenuOptions(): AxiosPromise<OptionType[]> {
*/
export function getMenuForm(id: number): AxiosPromise<MenuForm> {
return request({
url: '/api/v1/menus/' + id + '/form',
method: 'get'
url: "/api/v1/menus/" + id + "/form",
method: "get",
});
}
......@@ -54,9 +54,9 @@ export function getMenuForm(id: number): AxiosPromise<MenuForm> {
*/
export function addMenu(data: MenuForm) {
return request({
url: '/api/v1/menus',
method: 'post',
data: data
url: "/api/v1/menus",
method: "post",
data: data,
});
}
......@@ -68,9 +68,9 @@ export function addMenu(data: MenuForm) {
*/
export function updateMenu(id: string, data: MenuForm) {
return request({
url: '/api/v1/menus/' + id,
method: 'put',
data: data
url: "/api/v1/menus/" + id,
method: "put",
data: data,
});
}
......@@ -81,7 +81,7 @@ export function updateMenu(id: string, data: MenuForm) {
*/
export function deleteMenu(id: number) {
return request({
url: '/api/v1/menus/' + id,
method: 'delete'
url: "/api/v1/menus/" + id,
method: "delete",
});
}
import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
/**
* 菜单查询参数类型
......
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { RoleQuery, RolePageResult, RoleForm } from './types';
import request from "@/utils/request";
import { AxiosPromise } from "axios";
import { RoleQuery, RolePageResult, RoleForm } from "./types";
/**
* 获取角色分页数据
......@@ -11,9 +11,9 @@ export function getRolePage(
queryParams?: RoleQuery
): AxiosPromise<RolePageResult> {
return request({
url: '/api/v1/roles/page',
method: 'get',
params: queryParams
url: "/api/v1/roles/page",
method: "get",
params: queryParams,
});
}
......@@ -26,9 +26,9 @@ export function listRoleOptions(
queryParams?: RoleQuery
): AxiosPromise<OptionType[]> {
return request({
url: '/api/v1/roles/options',
method: 'get',
params: queryParams
url: "/api/v1/roles/options",
method: "get",
params: queryParams,
});
}
......@@ -39,8 +39,8 @@ export function listRoleOptions(
*/
export function getRoleMenuIds(roleId: number): AxiosPromise<number[]> {
return request({
url: '/api/v1/roles/' + roleId + '/menuIds',
method: 'get'
url: "/api/v1/roles/" + roleId + "/menuIds",
method: "get",
});
}
......@@ -54,9 +54,9 @@ export function updateRoleMenus(
data: number[]
): AxiosPromise<any> {
return request({
url: '/api/v1/roles/' + roleId + '/menus',
method: 'put',
data: data
url: "/api/v1/roles/" + roleId + "/menus",
method: "put",
data: data,
});
}
......@@ -67,8 +67,8 @@ export function updateRoleMenus(
*/
export function getRoleForm(id: number): AxiosPromise<RoleForm> {
return request({
url: '/api/v1/roles/' + id + '/form',
method: 'get'
url: "/api/v1/roles/" + id + "/form",
method: "get",
});
}
......@@ -79,9 +79,9 @@ export function getRoleForm(id: number): AxiosPromise<RoleForm> {
*/
export function addRole(data: RoleForm) {
return request({
url: '/api/v1/roles',
method: 'post',
data: data
url: "/api/v1/roles",
method: "post",
data: data,
});
}
......@@ -93,9 +93,9 @@ export function addRole(data: RoleForm) {
*/
export function updateRole(id: number, data: RoleForm) {
return request({
url: '/api/v1/roles/' + id,
method: 'put',
data: data
url: "/api/v1/roles/" + id,
method: "put",
data: data,
});
}
......@@ -106,7 +106,7 @@ export function updateRole(id: number, data: RoleForm) {
*/
export function deleteRoles(ids: string) {
return request({
url: '/api/v1/roles/' + ids,
method: 'delete'
url: "/api/v1/roles/" + ids,
method: "delete",
});
}
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { UserForm, UserInfo, UserPageVO, UserQuery } from './types';
import request from "@/utils/request";
import { AxiosPromise } from "axios";
import { UserForm, UserInfo, UserPageVO, UserQuery } from "./types";
/**
* 登录成功后获取用户信息(昵称、头像、权限集合和角色集合)
*/
export function getUserInfo(): AxiosPromise<UserInfo> {
return request({
url: '/api/v1/users/me',
method: 'get'
url: "/api/v1/users/me",
method: "get",
});
}
......@@ -21,9 +21,9 @@ export function getUserPage(
queryParams: UserQuery
): AxiosPromise<PageResult<UserPageVO[]>> {
return request({
url: '/api/v1/users/page',
method: 'get',
params: queryParams
url: "/api/v1/users/page",
method: "get",
params: queryParams,
});
}
......@@ -34,8 +34,8 @@ export function getUserPage(
*/
export function getUserForm(userId: number): AxiosPromise<UserForm> {
return request({
url: '/api/v1/users/' + userId + '/form',
method: 'get'
url: "/api/v1/users/" + userId + "/form",
method: "get",
});
}
......@@ -46,9 +46,9 @@ export function getUserForm(userId: number): AxiosPromise<UserForm> {
*/
export function addUser(data: any) {
return request({
url: '/api/v1/users',
method: 'post',
data: data
url: "/api/v1/users",
method: "post",
data: data,
});
}
......@@ -60,9 +60,9 @@ export function addUser(data: any) {
*/
export function updateUser(id: number, data: UserForm) {
return request({
url: '/api/v1/users/' + id,
method: 'put',
data: data
url: "/api/v1/users/" + id,
method: "put",
data: data,
});
}
......@@ -74,9 +74,9 @@ export function updateUser(id: number, data: UserForm) {
*/
export function updateUserStatus(id: number, status: number) {
return request({
url: '/api/v1/users/' + id + '/status',
method: 'patch',
params: { status: status }
url: "/api/v1/users/" + id + "/status",
method: "patch",
params: { status: status },
});
}
......@@ -88,9 +88,9 @@ export function updateUserStatus(id: number, status: number) {
*/
export function updateUserPassword(id: number, password: string) {
return request({
url: '/api/v1/users/' + id + '/password',
method: 'patch',
params: { password: password }
url: "/api/v1/users/" + id + "/password",
method: "patch",
params: { password: password },
});
}
......@@ -101,8 +101,8 @@ export function updateUserPassword(id: number, password: string) {
*/
export function deleteUsers(ids: string) {
return request({
url: '/api/v1/users/' + ids,
method: 'delete'
url: "/api/v1/users/" + ids,
method: "delete",
});
}
......@@ -113,9 +113,9 @@ export function deleteUsers(ids: string) {
*/
export function downloadTemplateApi() {
return request({
url: '/api/v1/users/template',
method: 'get',
responseType: 'arraybuffer'
url: "/api/v1/users/template",
method: "get",
responseType: "arraybuffer",
});
}
......@@ -127,10 +127,10 @@ export function downloadTemplateApi() {
*/
export function exportUser(queryParams: UserQuery) {
return request({
url: '/api/v1/users/_export',
method: 'get',
url: "/api/v1/users/_export",
method: "get",
params: queryParams,
responseType: 'arraybuffer'
responseType: "arraybuffer",
});
}
......@@ -141,14 +141,14 @@ export function exportUser(queryParams: UserQuery) {
*/
export function importUser(deptId: number, file: File) {
const formData = new FormData();
formData.append('file', file);
formData.append("file", file);
return request({
url: '/api/v1/users/_import',
method: 'post',
url: "/api/v1/users/_import",
method: "post",
params: { deptId: deptId },
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
"Content-Type": "multipart/form-data",
},
});
}
......@@ -11,7 +11,7 @@
:key="option.value"
:label="option.label"
:value="option.value"
></el-option>
/>
</el-select>
</div>
</template>
......
......@@ -89,8 +89,8 @@ onMounted(() => {
class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]"
@click="visible = !visible"
>
<i-ep-caret-top v-show="visible"></i-ep-caret-top>
<i-ep-caret-bottom v-show="!visible"></i-ep-caret-bottom>
<i-ep-caret-top v-show="visible" />
<i-ep-caret-bottom v-show="!visible" />
</div>
</template>
......
......@@ -54,7 +54,7 @@ onBeforeUnmount(() => {
<template>
<div ref="rightPanel" :class="{ show: show }">
<div class="right-panel-overlay" />
<div class="right-panel-overlay"></div>
<div class="right-panel-container">
<div
class="right-panel-btn"
......@@ -67,7 +67,7 @@ onBeforeUnmount(() => {
<i-ep-setting v-show="!show" />
</div>
<div>
<slot />
<slot></slot>
</div>
</div>
</div>
......
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app';
import { useAppStore } from "@/store/modules/app";
const appStore = useAppStore();
const sizeOptions = ref([
{ label: '默认', value: 'default' },
{ label: '大型', value: 'large' },
{ label: '小型', value: 'small' }
{ label: "默认", value: "default" },
{ label: "大型", value: "large" },
{ label: "小型", value: "small" },
]);
function handleSizeChange(size: string) {
appStore.changeSize(size);
ElMessage.success('切换布局大小成功');
ElMessage.success("切换布局大小成功");
}
</script>
......
/**
* getRePosFromStr 正则匹配字段返回位置信息
* */
export function getRePosFromStr(text: any = '', re: any = /\$.+?\$/g) {
const lines = text.split('\n')
const positions: any = []
let m
for (let i = 0; i < lines.length; i++) {
const l = lines[i]
while ((m = re.exec(l)) !== null) {
var tag = m[0].substring(1, m[0].length - 1)
positions.push({
line: i,
start: m.index,
stop: m.index + m[0].length,
tag,
})
}
export function getRePosFromStr(text: any = "", re: any = /\$.+?\$/g) {
const lines = text.split("\n");
const positions: any = [];
let m;
for (let i = 0; i < lines.length; i++) {
const l = lines[i];
while ((m = re.exec(l)) !== null) {
const tag = m[0].substring(1, m[0].length - 1);
positions.push({
line: i,
start: m.index,
stop: m.index + m[0].length,
tag,
});
}
return positions
}
return positions;
}
/**
* 输入框模式
*/
export enum MODE {
// 文本
TEXT = 1,
// 公式
FORMULA,
// 只允许选择tag
ONLYTAG,
// 日期
DATE
}
\ No newline at end of file
// 文本
TEXT = 1,
// 公式
FORMULA,
// 只允许选择tag
ONLYTAG,
// 日期
DATE,
}
......@@ -71,4 +71,4 @@ onBeforeUnmount(() => {
});
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>
\ No newline at end of file
<style src="@wangeditor/editor/dist/css/style.css"></style>
import type { App } from 'vue';
import type { App } from "vue";
import { hasPerm } from './permission';
import { hasPerm } from "./permission";
// 全局注册 directive
export function setupDirective(app: App<Element>) {
// 使 v-hasPerm 在所有组件中都可用
app.directive('hasPerm', hasPerm);
app.directive("hasPerm", hasPerm);
}
import { useUserStoreHook } from '@/store/modules/user';
import { Directive, DirectiveBinding } from 'vue';
import { useUserStoreHook } from "@/store/modules/user";
import { Directive, DirectiveBinding } from "vue";
/**
* 按钮权限
......@@ -8,7 +8,7 @@ export const hasPerm: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
// 「超级管理员」拥有所有的按钮权限
const { roles, perms } = useUserStoreHook();
if (roles.includes('ROOT')) {
if (roles.includes("ROOT")) {
return true;
}
// 「其他角色」按钮权限校验
......@@ -16,7 +16,7 @@ export const hasPerm: Directive = {
if (value) {
const requiredPerms = value; // DOM绑定需要的按钮权限标识
const hasPerm = perms?.some(perm => {
const hasPerm = perms?.some((perm) => {
return requiredPerms.includes(perm);
});
......@@ -28,7 +28,7 @@ export const hasPerm: Directive = {
"need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
);
}
}
},
};
/**
......@@ -41,7 +41,7 @@ export const hasRole: Directive = {
if (value) {
const requiredRoles = value; // DOM绑定需要的角色编码
const { roles } = useUserStoreHook();
const hasRole = roles.some(perm => {
const hasRole = roles.some((perm) => {
return requiredRoles.includes(perm);
});
......@@ -51,5 +51,5 @@ export const hasRole: Directive = {
} else {
throw new Error("need roles! Like v-has-role=\"['admin','test']\"");
}
}
},
};
......@@ -2,18 +2,18 @@ export enum MenuTypeEnum {
/**
* 目录
*/
CATALOG = 'CATALOG',
CATALOG = "CATALOG",
/**
* 菜单
*/
MENU = 'MENU',
MENU = "MENU",
/**
* 按钮
*/
BUTTON = 'BUTTON',
BUTTON = "BUTTON",
/**
* 外链
*/
EXTLINK = 'EXTLINK'
EXTLINK = "EXTLINK",
}
import { createI18n } from 'vue-i18n';
import { useAppStore } from '@/store/modules/app';
import { createI18n } from "vue-i18n";
import { useAppStore } from "@/store/modules/app";
// 本地语言包
import enLocale from "./package/en";
import zhCnLocale from "./package/zh-cn";
const appStore = useAppStore();
// 本地语言包
import enLocale from './package/en';
import zhCnLocale from './package/zh-cn';
const messages = {
'zh-cn': {
...zhCnLocale
"zh-cn": {
...zhCnLocale,
},
en: {
...enLocale
}
...enLocale,
},
};
const i18n = createI18n({
legacy: false,
locale: appStore.language,
messages: messages,
globalInjection: true
globalInjection: true,
});
export default i18n;
export default {
// 路由国际化
route: {
dashboard: 'Dashboard',
document: 'Document'
dashboard: "Dashboard",
document: "Document",
},
// 登录页面国际化
login: {
title: 'vue3-element-admin',
username: 'Username',
password: 'Password',
login: 'Login',
verifyCode: 'Verify Code',
title: "vue3-element-admin",
username: "Username",
password: "Password",
login: "Login",
verifyCode: "Verify Code",
},
// 导航栏国际化
navbar: {
dashboard: 'Dashboard',
logout: 'Logout',
document: 'Document',
gitee: 'Gitee'
}
dashboard: "Dashboard",
logout: "Logout",
document: "Document",
gitee: "Gitee",
},
};
export default {
// 路由国际化
route: {
dashboard: '首页',
document: '项目文档'
dashboard: "首页",
document: "项目文档",
},
// 登录页面国际化
login: {
title: 'vue3-element-admin',
username: '用户名',
password: '密码',
login: '登 录',
verifyCode: '验证码'
title: "vue3-element-admin",
username: "用户名",
password: "密码",
login: "登 录",
verifyCode: "验证码",
},
// 导航栏国际化
navbar: {
dashboard: '首页',
logout: '注销',
document: '项目文档',
gitee: '码云'
}
dashboard: "首页",
logout: "注销",
document: "项目文档",
gitee: "码云",
},
};
......@@ -84,7 +84,7 @@ onMounted(() => {
class="inline-block w-[30px] h-[30px] cursor-pointer"
:style="{ background: color }"
@click="changeThemeColor(color)"
/>
></li>
</ul>
<el-divider>导航设置</el-divider>
......@@ -98,8 +98,8 @@ onMounted(() => {
"
@click="changeLayout('left')"
>
<div />
<div />
<div></div>
<div></div>
</li>
</el-tooltip>
<el-tooltip content="顶部模式" placement="bottom">
......@@ -110,8 +110,8 @@ onMounted(() => {
"
@click="changeLayout('top')"
>
<div />
<div />
<div></div>
<div></div>
</li>
</el-tooltip>
<el-tooltip content="混合模式" placement="bottom">
......@@ -122,8 +122,8 @@ onMounted(() => {
"
@click="changeLayout('mix')"
>
<div />
<div />
<div></div>
<div></div>
</li>
</el-tooltip>
</ul>
......
<script lang="ts" setup>
import { computed } from 'vue';
import { isExternal } from '@/utils/index';
import { useRouter } from 'vue-router';
import { computed } from "vue";
import { isExternal } from "@/utils/index";
import { useRouter } from "vue-router";
import { useAppStore } from "@/store/modules/app";
import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();
const sidebar = computed(() => appStore.sidebar);
......@@ -12,16 +13,16 @@ const device = computed(() => appStore.device);
const props = defineProps({
to: {
type: String,
required: true
}
required: true,
},
});
const router = useRouter();
function push() {
if (device.value === 'mobile' && sidebar.value.opened == true) {
if (device.value === "mobile" && sidebar.value.opened == true) {
appStore.closeSideBar(false);
}
router.push(props.to).catch(err => {
router.push(props.to).catch((err) => {
console.error(err);
});
}
......@@ -29,9 +30,9 @@ function push() {
<template>
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
<slot />
<slot></slot>
</a>
<div v-else @click="push">
<slot />
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { useSettingsStore } from '@/store/modules/settings';
import { useSettingsStore } from "@/store/modules/settings";
const settingsStore = useSettingsStore();
defineProps({
collapse: {
type: Boolean,
required: true
}
required: true,
},
});
const logo = ref(new URL(`../../../assets/logo.png`, import.meta.url).href);
......
<script setup lang="ts">
import path from 'path-browserify';
import { isExternal } from '@/utils/index';
import AppLink from './Link.vue';
import path from "path-browserify";
import { isExternal } from "@/utils/index";
import AppLink from "./Link.vue";
import { translateRouteTitleI18n } from '@/utils/i18n';
import SvgIcon from '@/components/SvgIcon/index.vue';
import { translateRouteTitleI18n } from "@/utils/i18n";
import SvgIcon from "@/components/SvgIcon/index.vue";
const props = defineProps({
/**
......@@ -12,7 +12,7 @@ const props = defineProps({
*/
item: {
type: Object,
required: true
required: true,
},
/**
......@@ -20,8 +20,8 @@ const props = defineProps({
*/
basePath: {
type: String,
required: true
}
required: true,
},
});
const onlyOneChild = ref(); // 临时变量,唯一子路由
......@@ -53,7 +53,7 @@ function hasOneShowingChild(children = [], parent: any) {
// 2:如果无子路由, 复制当前路由信息作为其子路由,满足只拥有一个子路由的条件,所以返回 true
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
return true;
}
return false;
......
......@@ -99,7 +99,7 @@ defineExpose({
:vertical="false"
@wheel.prevent="handleScroll"
>
<slot />
<slot></slot>
</el-scrollbar>
</template>
......
<script setup lang="ts">
import {
getCurrentInstance,
nextTick,
ref,
watch,
onMounted,
ComponentInternalInstance,
} from "vue";
import { getCurrentInstance, ComponentInternalInstance } from "vue";
import { storeToRefs } from "pinia";
import path from "path-browserify";
......
export { default as Navbar } from './Navbar.vue';
export { default as AppMain } from './AppMain.vue';
export { default as Settings } from './Settings/index.vue';
export { default as TagsView } from './TagsView/index.vue';
export { default as Navbar } from "./Navbar.vue";
export { default as AppMain } from "./AppMain.vue";
export { default as Settings } from "./Settings/index.vue";
export { default as TagsView } from "./TagsView/index.vue";
import { createApp } from 'vue';
import App from './App.vue';
import router from '@/router';
import { setupStore } from '@/store';
import { setupDirective } from '@/directive';
import { createApp } from "vue";
import App from "./App.vue";
import router from "@/router";
import { setupStore } from "@/store";
import { setupDirective } from "@/directive";
import '@/permission';
import "@/permission";
// 本地SVG图标
import 'virtual:svg-icons-register';
import "virtual:svg-icons-register";
// 国际化
import i18n from '@/lang/index';
import i18n from "@/lang/index";
// 样式
import 'element-plus/theme-chalk/dark/css-vars.css';
import '@/styles/index.scss';
import 'uno.css';
import "element-plus/theme-chalk/dark/css-vars.css";
import "@/styles/index.scss";
import "uno.css";
const app = createApp(App);
// 全局注册 自定义指令(directive)
......@@ -23,4 +23,4 @@ setupDirective(app);
// 全局注册 状态管理(store)
setupStore(app);
app.use(router).use(i18n).mount('#app');
app.use(router).use(i18n).mount("#app");
......@@ -4,6 +4,7 @@ import { usePermissionStoreHook } from "@/store/modules/permission";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
NProgress.configure({ showSpinner: false }); // 进度条
const permissionStore = usePermissionStoreHook();
......
......@@ -46,17 +46,35 @@ export const constantRoutes: RouteRecordRaw[] = [
],
},
{
path: "/ext",
component: Layout,
children: [
{
path: "google",
component: import("@/views/demo/google.vue"),
name: "google",
meta: {
title: "Google",
icon: "client",
hidden: false,
roles: ["ADMIN"],
keepAlive: true,
},
},
],
},
// 外部链接
/*{
path: '/external-link',
component: Layout,
children: [
{
path: 'https://www.cnblogs.com/haoxianrui/',
meta: { title: '外部链接', icon: 'link' }
}
]
}*/
// {
// path: "/external-link",
// component: Layout,
// children: [ {
// component: () => import("@/views/external-link/index.vue"),
// path: "https://www.cnblogs.com/haoxianrui/",
// meta: { title: "外部链接", icon: "link" },
// },
// ],
// },
// 多级嵌套路由
/* {
path: '/nested',
......
declare module 'xlsx/xlsx.mjs'
declare module "xlsx/xlsx.mjs";
import type { App } from 'vue';
import { createPinia } from 'pinia';
import type { App } from "vue";
import { createPinia } from "pinia";
const store = createPinia();
......
......@@ -19,7 +19,7 @@ html.dark {
--w-e-textarea-bg-color: var(--el-bg-color-overlay);
--w-e-textarea-color: var(--el-text-color-regulary);
--w-e-textarea-slight-border-color: var(--el-color-primary);
--w-e-textarea-slight-bg-color: rgba(var(--el-color-primary-rgb), .1);
--w-e-textarea-slight-bg-color: rgb(var(--el-color-primary-rgb) 0.1);
--w-e-textarea-selected-border-color: var(--el-color-primary);
--w-e-textarea-border-color: var(--el-color-info-light-5);
......
// 导出 variables.module.scss 变量提供给TypeScript使用
:export {
menuBg: $menuBg;
menuText: $menuText;
menuActiveText: $menuActiveText
}
\ No newline at end of file
menuBg: $menuBg;
menuText: $menuText;
menuActiveText: $menuActiveText;
}
......@@ -11,7 +11,7 @@
// wang-editor textarea
--w-e-textarea-slight-border-color: var(--el-color-primary);
--w-e-textarea-slight-bg-color: rgba(var(--el-color-primary-rgb), .1);
--w-e-textarea-slight-bg-color: rgb(var(--el-color-primary-rgb) 0.1);
--w-e-textarea-selected-border-color: var(--el-color-primary);
}
......
// translate router.meta.title, be used in breadcrumb sidebar tagsview
import i18n from '@/lang/index';
import i18n from "@/lang/index";
export function translateRouteTitleI18n(title: any) {
// 判断是否存在国际化配置,如果没有原生返回
const hasKey = i18n.global.te('route.' + title);
const hasKey = i18n.global.te("route." + title);
if (hasKey) {
const translatedTitle = i18n.global.t('route.' + title);
const translatedTitle = i18n.global.t("route." + title);
return translatedTitle;
}
return title;
......
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { useUserStoreHook } from '@/store/modules/user';
import axios, { InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { useUserStoreHook } from "@/store/modules/user";
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' }
headers: { "Content-Type": "application/json;charset=utf-8" },
});
// 请求拦截器
......@@ -26,7 +26,7 @@ service.interceptors.request.use(
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, msg } = response.data;
if (code === '00000') {
if (code === "00000") {
return response.data;
}
// 响应数据为二进制流处理(Excel导出)
......@@ -34,23 +34,23 @@ service.interceptors.response.use(
return response;
}
ElMessage.error(msg || '系统出错');
return Promise.reject(new Error(msg || 'Error'));
ElMessage.error(msg || "系统出错");
return Promise.reject(new Error(msg || "Error"));
},
(error: any) => {
if (error.response.data) {
const { code, msg } = error.response.data;
// token 过期,重新登录
if (code === 'A0230') {
ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', {
confirmButtonText: '确定',
type: 'warning'
if (code === "A0230") {
ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", {
confirmButtonText: "确定",
type: "warning",
}).then(() => {
localStorage.clear();
window.location.href = '/';
window.location.href = "/";
});
} else {
ElMessage.error(msg || '系统出错');
ElMessage.error(msg || "系统出错");
}
}
return Promise.reject(error.message);
......
......@@ -47,7 +47,7 @@ export const scrollTo = (to: number, duration: number, callback?: any) => {
const change = to - start;
const increment = 20;
let currentTime = 0;
duration = typeof duration === 'undefined' ? 500 : duration;
duration = typeof duration === "undefined" ? 500 : duration;
const animateScroll = function () {
// increment the time
currentTime += increment;
......@@ -59,7 +59,7 @@ export const scrollTo = (to: number, duration: number, callback?: any) => {
if (currentTime < duration) {
requestAnimFrame(animateScroll);
} else {
if (callback && typeof callback === 'function') {
if (callback && typeof callback === "function") {
// the animation is done so lets callback
callback();
}
......
......@@ -5,15 +5,12 @@
<div class="title">
业绩柱状图
<el-tooltip effect="dark" content="点击试试下载" placement="bottom">
<i-ep-download
class="download"
@click="downloadEchart"
></i-ep-download>
<i-ep-download class="download" @click="downloadEchart" />
</el-tooltip>
</div>
</template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }"></div>
</el-card>
</template>
......
<!-- 漏斗图 -->
<template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }"></div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
import * as echarts from "echarts";
const props = defineProps({
id: {
type: String,
default: 'funnelChart'
default: "funnelChart",
},
className: {
type: String,
default: ''
default: "",
},
width: {
type: String,
default: '200px',
required: true
default: "200px",
required: true,
},
height: {
type: String,
default: '200px',
required: true
}
default: "200px",
required: true,
},
});
const options = {
title: {
show: true,
text: '订单线索转化漏斗图',
x: 'center',
text: "订单线索转化漏斗图",
x: "center",
padding: 15,
textStyle: {
fontSize: 18,
fontStyle: 'normal',
fontWeight: 'bold',
color: '#337ecc'
}
fontStyle: "normal",
fontWeight: "bold",
color: "#337ecc",
},
},
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
left: "2%",
right: "2%",
bottom: "10%",
containLabel: true,
},
legend: {
x: 'center',
y: 'bottom',
data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
x: "center",
y: "bottom",
data: ["Show", "Click", "Visit", "Inquiry", "Order"],
},
series: [
{
name: 'Funnel',
type: 'funnel',
left: '20%',
name: "Funnel",
type: "funnel",
left: "20%",
top: 60,
bottom: 60,
width: '60%',
sort: 'descending',
width: "60%",
sort: "descending",
gap: 2,
label: {
show: true,
position: 'inside'
position: "inside",
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid'
}
type: "solid",
},
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1
borderColor: "#fff",
borderWidth: 1,
},
emphasis: {
label: {
fontSize: 20
}
fontSize: 20,
},
},
data: [
{ value: 60, name: 'Visit' },
{ value: 40, name: 'Inquiry' },
{ value: 20, name: 'Order' },
{ value: 80, name: 'Click' },
{ value: 100, name: 'Show' }
]
}
]
{ value: 60, name: "Visit" },
{ value: 40, name: "Inquiry" },
{ value: 20, name: "Order" },
{ value: 80, name: "Click" },
{ value: 100, name: "Show" },
],
},
],
};
onMounted(() => {
......@@ -99,7 +99,7 @@ onMounted(() => {
);
chart.setOption(options);
window.addEventListener('resize', () => {
window.addEventListener("resize", () => {
chart.resize();
});
});
......
......@@ -2,69 +2,69 @@
<template>
<el-card>
<template #header> 产品分类饼图 </template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }"></div>
</el-card>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
import * as echarts from "echarts";
const props = defineProps({
id: {
type: String,
default: 'pieChart'
default: "pieChart",
},
className: {
type: String,
default: ''
default: "",
},
width: {
type: String,
default: '200px',
required: true
default: "200px",
required: true,
},
height: {
type: String,
default: '200px',
required: true
}
default: "200px",
required: true,
},
});
const options = {
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
left: "2%",
right: "2%",
bottom: "10%",
containLabel: true,
},
legend: {
top: 'bottom',
top: "bottom",
textStyle: {
color: '#999'
}
color: "#999",
},
},
series: [
{
name: 'Nightingale Chart',
type: 'pie',
name: "Nightingale Chart",
type: "pie",
radius: [50, 130],
center: ['50%', '50%'],
roseType: 'area',
center: ["50%", "50%"],
roseType: "area",
itemStyle: {
borderRadius: 1,
color: function (params: any) {
//自定义颜色
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
const colorList = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C"];
return colorList[params.dataIndex];
}
},
},
data: [
{ value: 26, name: '家用电器' },
{ value: 27, name: '户外运动' },
{ value: 24, name: '汽车用品' },
{ value: 23, name: '手机数码' }
]
}
]
{ value: 26, name: "家用电器" },
{ value: 27, name: "户外运动" },
{ value: 24, name: "汽车用品" },
{ value: 23, name: "手机数码" },
],
},
],
};
onMounted(() => {
......@@ -72,7 +72,7 @@ onMounted(() => {
document.getElementById(props.id) as HTMLDivElement
);
chart.setOption(options);
window.addEventListener('resize', () => {
window.addEventListener("resize", () => {
chart.resize();
});
});
......
......@@ -2,89 +2,89 @@
<template>
<el-card>
<template #header> 订单状态雷达图 </template>
<div :id="id" :class="className" :style="{ height, width }" />
<div :id="id" :class="className" :style="{ height, width }"></div>
</el-card>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
import * as echarts from "echarts";
const props = defineProps({
id: {
type: String,
default: 'radarChart'
default: "radarChart",
},
className: {
type: String,
default: ''
default: "",
},
width: {
type: String,
default: '200px',
required: true
default: "200px",
required: true,
},
height: {
type: String,
default: '200px',
required: true
}
default: "200px",
required: true,
},
});
const options = {
grid: {
left: '2%',
right: '2%',
bottom: '10%',
containLabel: true
left: "2%",
right: "2%",
bottom: "10%",
containLabel: true,
},
legend: {
x: 'center',
y: 'bottom',
data: ['预定数量', '下单数量', '发货数量'],
x: "center",
y: "bottom",
data: ["预定数量", "下单数量", "发货数量"],
textStyle: {
color: '#999'
}
color: "#999",
},
},
radar: {
// shape: 'circle',
radius: '60%',
radius: "60%",
indicator: [
{ name: '家用电器' },
{ name: '服装箱包' },
{ name: '运动户外' },
{ name: '手机数码' },
{ name: '汽车用品' },
{ name: '家具厨具' }
]
{ name: "家用电器" },
{ name: "服装箱包" },
{ name: "运动户外" },
{ name: "手机数码" },
{ name: "汽车用品" },
{ name: "家具厨具" },
],
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
name: "Budget vs spending",
type: "radar",
itemStyle: {
borderRadius: 6,
color: function (params: any) {
//自定义颜色
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
const colorList = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C"];
return colorList[params.dataIndex];
}
},
},
data: [
{
value: [400, 400, 400, 400, 400, 400],
name: '预定数量'
name: "预定数量",
},
{
value: [300, 300, 300, 300, 300, 300],
name: '下单数量'
name: "下单数量",
},
{
value: [200, 200, 200, 200, 200, 200],
name: '发货数量'
}
]
}
]
name: "发货数量",
},
],
},
],
};
onMounted(() => {
......@@ -93,7 +93,7 @@ onMounted(() => {
);
chart.setOption(options);
window.addEventListener('resize', () => {
window.addEventListener("resize", () => {
chart.resize();
});
});
......
<script setup lang="ts">
import { useUserStore } from "@/store/modules/user";
import { useTransition, TransitionPresets } from "@vueuse/core";
defineOptions({
// eslint-disable-next-line vue/no-reserved-component-names
name: "Dashboard",
inheritAttrs: false,
});
import { useUserStore } from "@/store/modules/user";
import { useTransition, TransitionPresets } from "@vueuse/core";
const userStore = useUserStore();
const date: Date = new Date();
......
<!-- 接口文档 -->
<template>
<div class="app-container">
<iframe
src="https://creator.xiaohongshu.com/"
width="100%"
height="100%"
frameborder="0"
></iframe>
</div>
</template>
<style lang="scss" scoped>
.app-container {
display: flex;
flex-direction: column;
/* 84 = navbar + tags-view = 50 + 34 */
height: calc(100vh - 50px);
}
.hasTagsView {
.app-container {
height: calc(100vh - 84px);
}
}
</style>
......@@ -15,8 +15,8 @@
<div class="label-wrap" @click="handleClickHead(oneCol)">
<div>{{ oneCol.label }}</div>
<template v-if="oneCol.order">
<i-ep-sort-up v-if="oneCol.order === 'asc'"></i-ep-sort-up>
<i-ep-sort-down v-else></i-ep-sort-down>
<i-ep-sort-up v-if="oneCol.order === 'asc'" />
<i-ep-sort-down v-else />
</template>
</div>
......@@ -24,7 +24,7 @@
trigger="click"
@command="(command: string) => handleCommand(command, oneCol)"
>
<i-ep-arrow-down class="action-more"></i-ep-arrow-down>
<i-ep-arrow-down class="action-more" />
<template #dropdown>
<el-dropdown-menu>
<template v-if="oneCol.prop === 'id'">
......@@ -56,7 +56,7 @@
action=""
:http-request="handleUploadFile"
>
<i-ep-upload-filled class="icon"></i-ep-upload-filled>
<i-ep-upload-filled class="icon" />
<div class="el-upload__text">拖拽excel到这里</div>
</el-upload>
<div v-loading="loading" class="excel-table-wrap">
......
<!-- wangEditor富文本编辑器示例 -->
<script setup lang="ts">
import Editor from "@/components/WangEditor/index.vue";
const value = ref("初始内容");
</script>
......
<!-- websocket 示例 -->
<script setup lang="ts">
import { sendToAll, sendToUser } from "@/api/websocket";
import { sendToAll, sendToUser } from "@/api/websocket"; // 点对点消息列表
import { useUserStore } from "@/store/modules/user";
import { useWebSocket } from "@vueuse/core";
const inputVal = ref("初始内容");
const topicMsgs = ref<string[]>(["接收到一条主题消息"]); // 主题消息列表
const p2pMsgs = ref<string[]>(["接收到一条点对线消息"]); // 点对点消息列表
import { useUserStore } from "@/store/modules/user";
const p2pMsgs = ref<string[]>(["接收到一条点对线消息"]);
const userId = useUserStore().userId;
import { useWebSocket } from "@vueuse/core";
const { data, status, close, send, open } = useWebSocket(
"ws://localhost:8989/ws",
{
......
<!-- setup 无法设置组件名称,组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "Page401",
};
</script>
<script setup lang="ts">
import { reactive, toRefs } from "vue";
import { useRouter } from "vue-router";
import { defineComponent } from "vue";
defineComponent({
name: "Page401",
});
const state = reactive({
errGif: new URL(`../../assets/401_images/401.gif`, import.meta.url).href,
......
<template>
<div>
<div style="margin-bottom: 15px">Your roles: {{ roles }}</div>
Switch roles:
<el-radio-group v-model="switchRoles">
<el-radio-button label="EDITOR" />
<el-radio-button label="ADMIN" />
</el-radio-group>
</div>
</template>
<script setup lang="ts">
import { useUserStoreHook } from "@/store/modules/user";
import { storeToRefs } from "pinia";
const emit = defineEmits(["change"]);
const store = storeToRefs(useUserStoreHook());
const { roles } = store;
const switchRoles = computed({
get: () => roles.value[0],
set: (val) => {
roles.value = [val];
emit("change");
},
});
</script>
<template>
<div class="app-container">
<switch-roles @change="handleRolesChange" />
</div>
</template>
<script setup lang="ts">
import router from "@/router";
import SwitchRoles from "./components/SwitchRoles.vue";
defineOptions({
// eslint-disable-next-line
name: "PagePermission",
inheritAttrs: false,
});
function handleRolesChange() {
console.log("roles changed");
router.push({ path: "/permission/page?" + new Date() });
}
</script>
<template>
<div />
<div></div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
......@@ -11,5 +11,5 @@ const router = useRouter();
const { params, query } = route;
const { path } = params;
router.replace({ path: '/' + path, query });
router.replace({ path: "/" + path, query });
</script>
<script setup lang="ts">
defineOptions({
name: "Dept",
inheritAttrs: false,
});
import {
getDeptForm,
deleteDept,
......@@ -15,6 +10,11 @@ import {
import { DeptVO, DeptForm, DeptQuery } from "@/api/dept/types";
defineOptions({
name: "Dept",
inheritAttrs: false,
});
const queryFormRef = ref(ElForm);
const deptFormRef = ref(ElForm);
......
<!-- 字典数据 -->
<script setup lang="ts">
defineOptions({
name: "DictData",
inheritAttrs: false,
});
import {
getDictPage,
getDictFormData,
......@@ -14,6 +9,11 @@ import {
} from "@/api/dict";
import { DictPageVO, DictForm, DictQuery } from "@/api/dict/types";
defineOptions({
name: "DictData",
inheritAttrs: false,
});
const props = defineProps({
typeCode: {
type: String,
......@@ -311,7 +311,7 @@ onMounted(() => {
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea"></el-input>
<el-input v-model="formData.remark" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
......
<!--字典类型-->
<script setup lang="ts">
defineOptions({
name: "DictType",
inheritAttrs: false,
});
import {
getDictTypePage,
getDictTypeForm,
......@@ -17,6 +12,11 @@ import DictData from "@/views/system/dict/DictData.vue";
import { DictTypePageVO, DictTypeQuery, DictTypeForm } from "@/api/dict/types";
defineOptions({
name: "DictType",
inheritAttrs: false,
});
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
......
<script setup lang="ts">
defineOptions({
// eslint-disable-next-line vue/no-reserved-component-names
name: "Menu",
inheritAttrs: false,
});
import { MenuQuery, MenuForm, MenuVO } from "@/api/menu/types";
import {
listMenus,
......@@ -20,6 +14,12 @@ import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
import SvgIcon from "@/components/SvgIcon/index.vue";
import IconSelect from "@/components/IconSelect/index.vue";
defineOptions({
// eslint-disable-next-line vue/no-reserved-component-names
name: "Menu",
inheritAttrs: false,
});
const queryFormRef = ref(ElForm);
const menuFormRef = ref(ElForm);
......
<script setup lang="ts">
defineOptions({
name: "Role",
inheritAttrs: false,
});
import {
getRolePage,
updateRole,
......@@ -17,6 +12,11 @@ import { listMenuOptions } from "@/api/menu";
import { RolePageVO, RoleForm, RoleQuery } from "@/api/role/types";
defineOptions({
name: "Role",
inheritAttrs: false,
});
const queryFormRef = ref(ElForm);
const roleFormRef = ref(ElForm);
const menuRef = ref(ElTree);
......
......@@ -2,10 +2,6 @@
/**
* @see {@link https://vuejs.org/api/sfc-script-setup.html#defineoptions}
*/
defineOptions({
name: "User",
inheritAttrs: false,
});
import { UploadFile } from "element-plus";
import {
getUserPage,
......@@ -24,6 +20,11 @@ import { listRoleOptions } from "@/api/role";
import { UserForm, UserQuery, UserPageVO } from "@/api/user/types";
defineOptions({
name: "User",
inheritAttrs: false,
});
const deptTreeRef = ref(ElTree); // 部门树
const queryFormRef = ref(ElForm); // 查询表单
const userFormRef = ref(ElForm); // 用户表单
......@@ -383,7 +384,7 @@ onMounted(() => {
:filter-node-method="handleDeptFilter"
default-expand-all
@node-click="handleDeptNodeClick"
></el-tree>
/>
</el-card>
</el-col>
......@@ -524,7 +525,7 @@ onMounted(() => {
align="center"
prop="createTime"
width="180"
></el-table-column>
/>
<el-table-column label="操作" fixed="right" width="220">
<template #default="scope">
<el-button
......
......@@ -5,6 +5,7 @@
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"noLib": false,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
......@@ -20,7 +21,13 @@
"allowSyntheticDefaultImports": true /* 允许默认导入 */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */
},
"include": ["src/**/*.ts", "src/**/*.vue", "src/types/**/*.d.ts"],
"exclude": ["node_modules", "dist", "**/*.js"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": [
"src/**/*.ts",
"src/**/*.vue",
"src/types/**/*.d.ts",
"mock/**/*.ts",
"vite.config.ts"
],
"exclude": ["node_modules", "dist", "**/*.js"]
// "references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
......@@ -11,6 +11,9 @@ import IconsResolver from "unplugin-icons/resolver";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import { viteMockServe } from "vite-plugin-mock";
import visualizer from "rollup-plugin-visualizer";
import UnoCSS from "unocss/vite";
import path from "path";
......@@ -20,6 +23,7 @@ const pathSrc = path.resolve(__dirname, "src");
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
const env = loadEnv(mode, process.cwd());
return {
resolve: {
alias: {
......@@ -45,11 +49,15 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
proxy: {
// 反向代理解决跨域
[env.VITE_APP_BASE_API]: {
target: "http://vapi.youlai.tech", // 线上接口地址
// target: 'http://localhost:8989', // 本地接口地址 , 后端工程仓库地址:https://gitee.com/youlaiorg/youlai-boot
// target: "http://vapi.youlai.tech", // 线上接口地址
// target: 'http://localhost:3001', // 本地接口地址 , 后端工程仓库地址:https://gitee.com/youlaiorg/youlai-boot
target: env.VITE_APP_TARGET_URL,
changeOrigin: true,
rewrite: (path) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 替换 /dev-api 为 target 接口地址
path.replace(
new RegExp("^" + env.VITE_APP_BASE_API),
env.VITE_APP_TARGET_BASE_API
), // 替换 /dev-api 为 target 接口地址
},
},
},
......@@ -112,6 +120,20 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
algorithm: "gzip", // 压缩算法
ext: ".gz", // 文件类型
}),
viteMockServe({
ignore: /^\_/,
mockPath: "mock",
enable: mode === "development",
// https://github.com/anncwb/vite-plugin-mock/issues/9
}),
visualizer({
filename: "./stats.html",
open: false,
gzipSize: true,
brotliSize: true,
}),
],
// 预加载项目必需的组件
optimizeDeps: {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册