增加配置格式化

pull/386/head
songjianhua 2024-05-09 18:02:02 +08:00
parent 921c38cea2
commit 1613c0012b
75 changed files with 15188 additions and 6302 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
# http://editorconfig.org
root = true
# 表示所有文件适用
[*]
charset = utf-8 # 设置文件字符集为 utf-8
end_of_line = lf # 控制换行类型(lf | cr | crlf)
indent_style = space # 缩进风格tab | space
indent_size = 2 # 缩进大小
insert_final_newline = true # 始终在文件末尾插入一个新行
# 表示仅 md 文件适用以下规则
[*.md]
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪

16
.env.development Normal file
View File

@ -0,0 +1,16 @@
## 开发环境
NODE_ENV='development'
# 应用端口
VITE_APP_PORT = 3000
# 代理前缀
VITE_APP_BASE_API = '/dev-api'
# 线上接口地址
VITE_APP_API_URL = http://vapi.youlai.tech
# 开发接口地址
# VITE_APP_API_URL = http://localhost:8989
# 是否启用 Mock 服务
VITE_MOCK_DEV_SERVER = false

6
.env.production Normal file
View File

@ -0,0 +1,6 @@
## 生产环境
NODE_ENV='production'
# 代理前缀
VITE_APP_BASE_API = '/prod-api'

14
.eslintignore Normal file
View File

@ -0,0 +1,14 @@
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
src/assets
.eslintrc.cjs
.prettierrc.cjs
.stylelintrc.cjs

284
.eslintrc-auto-import.json Normal file
View File

@ -0,0 +1,284 @@
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true,
"ElMessage": true,
"ElMessageBox": true,
"ElNotification": true,
"InjectionKey": true,
"PropType": true,
"Ref": true,
"VNode": true,
"asyncComputed": true,
"autoResetRef": true,
"computed": true,
"computedAsync": true,
"computedEager": true,
"computedInject": true,
"computedWithControl": true,
"controlledComputed": true,
"controlledRef": true,
"createApp": true,
"createEventHook": true,
"createGlobalState": true,
"createInjectionState": true,
"createReactiveFn": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
"createUnrefFn": true,
"customRef": true,
"debouncedRef": true,
"debouncedWatch": true,
"defineAsyncComponent": true,
"defineComponent": true,
"eagerComputed": true,
"effectScope": true,
"extendRef": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"ignorableWatch": true,
"inject": true,
"isDefined": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"makeDestructurable": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onClickOutside": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onKeyStroke": true,
"onLongPress": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onStartTyping": true,
"onUnmounted": true,
"onUpdated": true,
"pausableWatch": true,
"provide": true,
"reactify": true,
"reactifyObject": true,
"reactive": true,
"reactiveComputed": true,
"reactiveOmit": true,
"reactivePick": true,
"readonly": true,
"ref": true,
"refAutoReset": true,
"refDebounced": true,
"refDefault": true,
"refThrottled": true,
"refWithControl": true,
"resolveComponent": true,
"resolveRef": true,
"resolveUnref": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"syncRef": true,
"syncRefs": true,
"templateRef": true,
"throttledRef": true,
"throttledWatch": true,
"toRaw": true,
"toReactive": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"tryOnBeforeMount": true,
"tryOnBeforeUnmount": true,
"tryOnMounted": true,
"tryOnScopeDispose": true,
"tryOnUnmounted": true,
"unref": true,
"unrefElement": true,
"until": true,
"useActiveElement": true,
"useAnimate": true,
"useArrayDifference": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayFindLast": true,
"useArrayIncludes": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
"useArraySome": true,
"useArrayUnique": true,
"useAsyncQueue": true,
"useAsyncState": true,
"useAttrs": true,
"useBase64": true,
"useBattery": true,
"useBluetooth": true,
"useBreakpoints": true,
"useBroadcastChannel": true,
"useBrowserLocation": true,
"useCached": true,
"useClipboard": true,
"useCloned": true,
"useColorMode": true,
"useConfirmDialog": true,
"useCounter": true,
"useCssModule": true,
"useCssVar": true,
"useCssVars": true,
"useCurrentElement": true,
"useCycleList": true,
"useDark": true,
"useDateFormat": true,
"useDebounce": true,
"useDebounceFn": true,
"useDebouncedRefHistory": true,
"useDeviceMotion": true,
"useDeviceOrientation": true,
"useDevicePixelRatio": true,
"useDevicesList": true,
"useDisplayMedia": true,
"useDocumentVisibility": true,
"useDraggable": true,
"useDropZone": true,
"useElementBounding": true,
"useElementByPoint": true,
"useElementHover": true,
"useElementSize": true,
"useElementVisibility": true,
"useEventBus": true,
"useEventListener": true,
"useEventSource": true,
"useEyeDropper": true,
"useFavicon": true,
"useFetch": true,
"useFileDialog": true,
"useFileSystemAccess": true,
"useFocus": true,
"useFocusWithin": true,
"useFps": true,
"useFullscreen": true,
"useGamepad": true,
"useGeolocation": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,
"useIntersectionObserver": true,
"useInterval": true,
"useIntervalFn": true,
"useKeyModifier": true,
"useLastChanged": true,
"useLocalStorage": true,
"useMagicKeys": true,
"useManualRefHistory": true,
"useMediaControls": true,
"useMediaQuery": true,
"useMemoize": true,
"useMemory": true,
"useMounted": true,
"useMouse": true,
"useMouseInElement": true,
"useMousePressed": true,
"useMutationObserver": true,
"useNavigatorLanguage": true,
"useNetwork": true,
"useNow": true,
"useObjectUrl": true,
"useOffsetPagination": true,
"useOnline": true,
"usePageLeave": true,
"useParallax": true,
"useParentElement": true,
"usePerformanceObserver": true,
"usePermission": true,
"usePointer": true,
"usePointerLock": true,
"usePointerSwipe": true,
"usePreferredColorScheme": true,
"usePreferredContrast": true,
"usePreferredDark": true,
"usePreferredLanguages": true,
"usePreferredReducedMotion": true,
"usePrevious": true,
"useRafFn": true,
"useRefHistory": true,
"useResizeObserver": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
"useScriptTag": true,
"useScroll": true,
"useScrollLock": true,
"useSessionStorage": true,
"useShare": true,
"useSlots": true,
"useSorted": true,
"useSpeechRecognition": true,
"useSpeechSynthesis": true,
"useStepper": true,
"useStorage": true,
"useStorageAsync": true,
"useStyleTag": true,
"useSupported": true,
"useSwipe": true,
"useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true,
"useTextareaAutosize": true,
"useThrottle": true,
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
"useTimestamp": true,
"useTitle": true,
"useToNumber": true,
"useToString": true,
"useToggle": true,
"useTransition": true,
"useUrlSearchParams": true,
"useUserMedia": true,
"useVModel": true,
"useVModels": true,
"useVibrate": true,
"useVirtualList": true,
"useWakeLock": true,
"useWebNotification": true,
"useWebSocket": true,
"useWebWorker": true,
"useWebWorkerFn": true,
"useWindowFocus": true,
"useWindowScroll": true,
"useWindowSize": true,
"watch": true,
"watchArray": true,
"watchAtMost": true,
"watchDebounced": true,
"watchDeep": true,
"watchEffect": true,
"watchIgnorable": true,
"watchImmediate": true,
"watchOnce": true,
"watchPausable": true,
"watchPostEffect": true,
"watchSyncEffect": true,
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true
}
}

88
.eslintrc.cjs Normal file
View File

@ -0,0 +1,88 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
parser: "vue-eslint-parser",
extends: [
// https://eslint.vuejs.org/user-guide/#usage
"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: {
// https://eslint.vuejs.org/rules/#priority-a-essential-error-prevention
"vue/multi-word-component-names": "off",
"vue/no-v-model-argument": "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",
},
],
"@typescript-eslint/no-empty-function": "off", // 关闭空方法检查
"@typescript-eslint/no-explicit-any": "off", // 关闭any类型的警告
"@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",
"@typescript-eslint/no-unused-vars": "off",
"prettier/prettier": [
"error",
{
useTabs: false, // 不使用制表符
},
],
},
// eslint不能对html文件生效
overrides: [
{
files: ["*.html"],
processor: "vue/.vue",
},
],
// https://eslint.org/docs/latest/use/configure/language-options#specifying-globals
globals: {
OptionType: "readonly",
},
};

11
.prettierignore Normal file
View File

@ -0,0 +1,11 @@
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
src/assets
stats.html

46
.prettierrc.cjs Normal file
View File

@ -0,0 +1,46 @@
module.exports = {
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
arrowParens: "always",
// 开始标签的右尖括号是否跟随在最后一行属性末尾默认false
bracketSameLine: false,
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
bracketSpacing: true,
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
embeddedLanguageFormatting: "auto",
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
htmlWhitespaceSensitivity: "css",
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记默认false
insertPragma: false,
// 在 JSX 中使用单引号替代双引号默认false
jsxSingleQuote: false,
// 每行最多字符数量,超出换行(默认80)
printWidth: 80,
// 超出打印宽度 (always | never | preserve )
proseWrap: "preserve",
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
quoteProps: "as-needed",
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件默认false
requirePragma: false,
// 结尾添加分号
semi: true,
// 使用单引号 (true:单引号;false:双引号)
singleQuote: false,
// 缩进空格数默认2个空格
tabWidth: 2,
// 元素末尾是否加逗号默认es5: ES5中的 objects, arrays 等会添加逗号TypeScript 中的 type 后不加逗号
trailingComma: "es5",
// 指定缩进方式空格或tab默认false即使用空格
useTabs: false,
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
vueIndentScriptAndStyle: false,
endOfLine: "auto",
overrides: [
{
files: "*.html",
options: {
parser: "html",
},
},
],
};

6
auto-imports.d.ts vendored
View File

@ -1,5 +1,3 @@
// Generated by 'unplugin-auto-import' // Generated by 'unplugin-auto-import'
export {} export {};
declare global { declare global {}
}

37
components.d.ts vendored
View File

@ -10,64 +10,27 @@ declare module '@vue/runtime-core' {
Countup: typeof import('./src/components/countup.vue')['default'] Countup: typeof import('./src/components/countup.vue')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElCountdown: typeof import('element-plus/es')['ElCountdown']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink'] ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress'] ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRate: typeof import('element-plus/es')['ElRate']
ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline'] ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTour: typeof import('element-plus/es')['ElTour']
ElTourStep: typeof import('element-plus/es')['ElTourStep']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElUpload: typeof import('element-plus/es')['ElUpload']
ElWatermark: typeof import('element-plus/es')['ElWatermark']
Header: typeof import('./src/components/header.vue')['default'] Header: typeof import('./src/components/header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

View File

@ -1,22 +1,26 @@
<!DOCTYPE html> <!doctype html>
<html lang=""> <html lang="">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>vue-manage-system后台管理系统</title> <title>vue-manage-system后台管理系统</title>
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_830376_92o68tc95je.css"> <link
rel="stylesheet"
href="//at.alicdn.com/t/c/font_830376_92o68tc95je.css"
/>
</head> </head>
<body> <body>
<noscript> <noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. <strong
Please enable it to continue.</strong> >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript> </noscript>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
</body> </body>
</html> </html>

5525
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,28 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
"serve": "vite preview" "serve": "vite preview",
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:eslint": "eslint --fix --ext .ts,.js,.vue ./src "
},
"lint-staged": {
"*.{js,ts}": [
"eslint --fix",
"prettier --write"
],
"*.{cjs,json}": [
"prettier --write"
],
"*.{vue,html}": [
"eslint --fix",
"prettier --write"
],
"*.{scss,css}": [
"prettier --write"
],
"*.md": [
"prettier --write"
]
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "*", "@element-plus/icons-vue": "*",
@ -27,8 +48,16 @@
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.12.11",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@vitejs/plugin-vue": "^3.0.0", "@vitejs/plugin-vue": "^3.0.0",
"@vue/compiler-sfc": "^3.1.2", "@vue/compiler-sfc": "^3.1.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.25.0",
"prettier": "^3.2.5",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"unplugin-auto-import": "^0.11.2", "unplugin-auto-import": "^0.11.2",
"unplugin-vue-components": "^0.22.4", "unplugin-vue-components": "^0.22.4",

View File

@ -5,13 +5,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ElConfigProvider } from 'element-plus'; import { ElConfigProvider } from "element-plus";
import zhCn from 'element-plus/es/locale/lang/zh-cn'; import zhCn from "element-plus/es/locale/lang/zh-cn";
import { useThemeStore } from './store/theme'; import { useThemeStore } from "./store/theme";
const theme = useThemeStore(); const theme = useThemeStore();
theme.initTheme(); theme.initTheme();
</script> </script>
<style> <style>
@import './assets/css/main.css'; @import "./assets/css/main.css";
</style> </style>

View File

@ -1,22 +1,22 @@
import request from '../utils/request'; import request from "../utils/request";
export const fetchData = () => { export const fetchData = () => {
return request({ return request({
url: './mock/table.json', url: "./mock/table.json",
method: 'get' method: "get",
}); });
}; };
export const fetchUserData = () => { export const fetchUserData = () => {
return request({ return request({
url: './mock/user.json', url: "./mock/user.json",
method: 'get' method: "get",
}); });
}; };
export const fetchRoleData = () => { export const fetchRoleData = () => {
return request({ return request({
url: './mock/role.json', url: "./mock/role.json",
method: 'get' method: "get",
}); });
}; };

View File

@ -3,8 +3,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from "vue";
import { CountUp } from 'countup.js'; import { CountUp } from "countup.js";
const props = defineProps({ const props = defineProps({
end: { end: {
@ -29,11 +29,12 @@ onMounted(() => {
countUp.start(); countUp.start();
}); });
watch(() => props.end, (newVal) => { watch(
() => props.end,
(newVal) => {
if (countUp) { if (countUp) {
countUp.update(newVal); countUp.update(newVal);
} }
}); }
);
</script> </script>

View File

@ -47,14 +47,22 @@
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank"> <a
href="https://github.com/lin-xin/vue-manage-system"
target="_blank"
>
<el-dropdown-item>项目仓库</el-dropdown-item> <el-dropdown-item>项目仓库</el-dropdown-item>
</a> </a>
<a href="https://lin-xin.gitee.io/example/vuems-doc/" target="_blank"> <a
href="https://lin-xin.gitee.io/example/vuems-doc/"
target="_blank"
>
<el-dropdown-item>官方文档</el-dropdown-item> <el-dropdown-item>官方文档</el-dropdown-item>
</a> </a>
<el-dropdown-item command="user">个人中心</el-dropdown-item> <el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item> <el-dropdown-item divided command="loginout"
>退出登录</el-dropdown-item
>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -63,12 +71,12 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue'; import { onMounted } from "vue";
import { useSidebarStore } from '../store/sidebar'; import { useSidebarStore } from "../store/sidebar";
import { useRouter } from 'vue-router'; import { useRouter } from "vue-router";
import imgurl from '../assets/img/img.jpg'; import imgurl from "../assets/img/img.jpg";
const username: string | null = localStorage.getItem('vuems_name'); const username: string | null = localStorage.getItem("vuems_name");
const message: number = 2; const message: number = 2;
const sidebar = useSidebarStore(); const sidebar = useSidebarStore();
@ -86,11 +94,11 @@ onMounted(() => {
// //
const router = useRouter(); const router = useRouter();
const handleCommand = (command: string) => { const handleCommand = (command: string) => {
if (command == 'loginout') { if (command == "loginout") {
localStorage.removeItem('vuems_name'); localStorage.removeItem("vuems_name");
router.push('/login'); router.push("/login");
} else if (command == 'user') { } else if (command == "user") {
router.push('/ucenter'); router.push("/ucenter");
} }
}; };

View File

@ -1,220 +1,220 @@
import { Menus } from '@/types/menu'; import { Menus } from "@/types/menu";
export const menuData: Menus[] = [ export const menuData: Menus[] = [
{ {
id: '0', id: "0",
title: '系统首页', title: "系统首页",
index: '/dashboard', index: "/dashboard",
icon: 'Odometer', icon: "Odometer",
}, },
{ {
id: '1', id: "1",
title: '系统管理', title: "系统管理",
index: '1', index: "1",
icon: 'HomeFilled', icon: "HomeFilled",
children: [ children: [
{ {
id: '11', id: "11",
pid: '1', pid: "1",
index: '/system-user', index: "/system-user",
title: '用户管理', title: "用户管理",
}, },
{ {
id: '12', id: "12",
pid: '1', pid: "1",
index: '/system-role', index: "/system-role",
title: '角色管理', title: "角色管理",
}, },
{ {
id: '13', id: "13",
pid: '1', pid: "1",
index: '/system-menu', index: "/system-menu",
title: '菜单管理', title: "菜单管理",
}, },
], ],
}, },
{ {
id: '2', id: "2",
title: '组件', title: "组件",
index: '2-1', index: "2-1",
icon: 'Calendar', icon: "Calendar",
children: [ children: [
{ {
id: '21', id: "21",
pid: '3', pid: "3",
index: '/form', index: "/form",
title: '表单', title: "表单",
}, },
{ {
id: '22', id: "22",
pid: '3', pid: "3",
index: '/upload', index: "/upload",
title: '上传', title: "上传",
}, },
{ {
id: '23', id: "23",
pid: '2', pid: "2",
index: '/carousel', index: "/carousel",
title: '走马灯', title: "走马灯",
}, },
{ {
id: '24', id: "24",
pid: '2', pid: "2",
index: '/calendar', index: "/calendar",
title: '日历', title: "日历",
}, },
{ {
id: '25', id: "25",
pid: '2', pid: "2",
index: '/watermark', index: "/watermark",
title: '水印', title: "水印",
}, },
{ {
id: '26', id: "26",
pid: '2', pid: "2",
index: '/tour', index: "/tour",
title: '分布引导', title: "分布引导",
}, },
{ {
id: '27', id: "27",
pid: '2', pid: "2",
index: '/steps', index: "/steps",
title: '步骤条', title: "步骤条",
}, },
{ {
id: '28', id: "28",
pid: '2', pid: "2",
index: '/statistic', index: "/statistic",
title: '统计', title: "统计",
}, },
{ {
id: '29', id: "29",
pid: '3', pid: "3",
index: '29', index: "29",
title: '三级菜单', title: "三级菜单",
children: [ children: [
{ {
id: '291', id: "291",
pid: '29', pid: "29",
index: '/editor', index: "/editor",
title: '富文本编辑器', title: "富文本编辑器",
}, },
{ {
id: '292', id: "292",
pid: '29', pid: "29",
index: '/markdown', index: "/markdown",
title: 'markdown编辑器', title: "markdown编辑器",
}, },
], ],
}, },
], ],
}, },
{ {
id: '3', id: "3",
title: '表格', title: "表格",
index: '3', index: "3",
icon: 'Calendar', icon: "Calendar",
children: [ children: [
{ {
id: '31', id: "31",
pid: '3', pid: "3",
index: '/table', index: "/table",
title: '基础表格', title: "基础表格",
}, },
{ {
id: '32', id: "32",
pid: '3', pid: "3",
index: '/table-editor', index: "/table-editor",
title: '可编辑表格', title: "可编辑表格",
}, },
{ {
id: '33', id: "33",
pid: '3', pid: "3",
index: '/import', index: "/import",
title: '导入Excel', title: "导入Excel",
}, },
{ {
id: '34', id: "34",
pid: '3', pid: "3",
index: '/export', index: "/export",
title: '导出Excel', title: "导出Excel",
}, },
], ],
}, },
{ {
id: '4', id: "4",
icon: 'PieChart', icon: "PieChart",
index: '4', index: "4",
title: '图表', title: "图表",
children: [ children: [
{ {
id: '41', id: "41",
pid: '4', pid: "4",
index: '/schart', index: "/schart",
title: 'schart图表', title: "schart图表",
}, },
{ {
id: '42', id: "42",
pid: '4', pid: "4",
index: '/echarts', index: "/echarts",
title: 'echarts图表', title: "echarts图表",
}, },
], ],
}, },
{ {
id: '5', id: "5",
icon: 'Guide', icon: "Guide",
index: '/icon', index: "/icon",
title: '图标', title: "图标",
permiss: '5', permiss: "5",
}, },
{ {
id: '7', id: "7",
icon: 'Brush', icon: "Brush",
index: '/theme', index: "/theme",
title: '主题', title: "主题",
}, },
{ {
id: '6', id: "6",
icon: 'DocumentAdd', icon: "DocumentAdd",
index: '6', index: "6",
title: '附加页面', title: "附加页面",
children: [ children: [
{ {
id: '61', id: "61",
pid: '6', pid: "6",
index: '/ucenter', index: "/ucenter",
title: '个人中心', title: "个人中心",
}, },
{ {
id: '62', id: "62",
pid: '6', pid: "6",
index: '/login', index: "/login",
title: '登录', title: "登录",
}, },
{ {
id: '63', id: "63",
pid: '6', pid: "6",
index: '/register', index: "/register",
title: '注册', title: "注册",
}, },
{ {
id: '64', id: "64",
pid: '6', pid: "6",
index: '/reset-pwd', index: "/reset-pwd",
title: '重设密码', title: "重设密码",
}, },
{ {
id: '65', id: "65",
pid: '6', pid: "6",
index: '/403', index: "/403",
title: '403', title: "403",
}, },
{ {
id: '66', id: "66",
pid: '6', pid: "6",
index: '/404', index: "/404",
title: '404', title: "404",
}, },
], ],
}, },

View File

@ -10,10 +10,14 @@
> >
<template v-for="item in menuData"> <template v-for="item in menuData">
<template v-if="item.children"> <template v-if="item.children">
<el-sub-menu :index="item.index" :key="item.index" v-permiss="item.id"> <el-sub-menu
:index="item.index"
:key="item.index"
v-permiss="item.id"
>
<template #title> <template #title>
<el-icon> <el-icon>
<component :is="item.icon"></component> <component :is="item.icon" />
</el-icon> </el-icon>
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
</template> </template>
@ -40,9 +44,13 @@
</el-sub-menu> </el-sub-menu>
</template> </template>
<template v-else> <template v-else>
<el-menu-item :index="item.index" :key="item.index" v-permiss="item.id"> <el-menu-item
:index="item.index"
:key="item.index"
v-permiss="item.id"
>
<el-icon> <el-icon>
<component :is="item.icon"></component> <component :is="item.icon" />
</el-icon> </el-icon>
<template #title>{{ item.title }}</template> <template #title>{{ item.title }}</template>
</el-menu-item> </el-menu-item>
@ -53,10 +61,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from "vue";
import { useSidebarStore } from '../store/sidebar'; import { useSidebarStore } from "../store/sidebar";
import { useRoute } from 'vue-router'; import { useRoute } from "vue-router";
import { menuData } from '@/components/menu'; import { menuData } from "@/components/menu";
const route = useRoute(); const route = useRoute();
const onRoutes = computed(() => { const onRoutes = computed(() => {

View File

@ -7,7 +7,10 @@
<div class="table-toolbar-right flex-center"> <div class="table-toolbar-right flex-center">
<template v-if="multipleSelection.length > 0"> <template v-if="multipleSelection.length > 0">
<el-tooltip effect="dark" content="删除选中" placement="top"> <el-tooltip effect="dark" content="删除选中" placement="top">
<el-icon class="columns-setting-icon" @click="delSelection(multipleSelection)"> <el-icon
class="columns-setting-icon"
@click="delSelection(multipleSelection)"
>
<Delete /> <Delete />
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
@ -35,25 +38,55 @@
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
<el-table class="mgb20" :style="{ width: '100%' }" border :data="tableData" :row-key="rowKey" <el-table
@selection-change="handleSelectionChange" table-layout="auto"> class="mgb20"
:style="{ width: '100%' }"
border
:data="tableData"
:row-key="rowKey"
@selection-change="handleSelectionChange"
table-layout="auto"
>
<template v-for="item in columns" :key="item.prop"> <template v-for="item in columns" :key="item.prop">
<el-table-column v-if="item.visible" :prop="item.prop" :label="item.label" :width="item.width" <el-table-column
:type="item.type" :align="item.align || 'center'"> v-if="item.visible"
:prop="item.prop"
<template #default="{ row, column, $index }" v-if="item.type === 'index'"> :label="item.label"
:width="item.width"
:type="item.type"
:align="item.align || 'center'"
>
<template
#default="{ row, column, $index }"
v-if="item.type === 'index'"
>
{{ getIndex($index) }} {{ getIndex($index) }}
</template> </template>
<template #default="{ row, column, $index }" v-if="!item.type"> <template #default="{ row, column, $index }" v-if="!item.type">
<slot :name="item.prop" :rows="row" :index="$index"> <slot :name="item.prop" :rows="row" :index="$index">
<template v-if="item.prop == 'operator'"> <template v-if="item.prop == 'operator'">
<el-button type="warning" size="small" :icon="View" @click="viewFunc(row)"> <el-button
type="warning"
size="small"
:icon="View"
@click="viewFunc(row)"
>
查看 查看
</el-button> </el-button>
<el-button type="primary" size="small" :icon="Edit" @click="editFunc(row)"> <el-button
type="primary"
size="small"
:icon="Edit"
@click="editFunc(row)"
>
编辑 编辑
</el-button> </el-button>
<el-button type="danger" size="small" :icon="Delete" @click="handleDelete(row)"> <el-button
type="danger"
size="small"
:icon="Delete"
@click="handleDelete(row)"
>
删除 删除
</el-button> </el-button>
</template> </template>
@ -68,81 +101,88 @@
</el-table-column> </el-table-column>
</template> </template>
</el-table> </el-table>
<el-pagination v-if="hasPagination" :current-page="currentPage" :page-size="pageSize" :background="true" <el-pagination
:layout="layout" :total="total" @current-change="handleCurrentChange" /> v-if="hasPagination"
:current-page="currentPage"
:page-size="pageSize"
:background="true"
:layout="layout"
:total="total"
@current-change="handleCurrentChange"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { toRefs, PropType, ref } from 'vue' import { toRefs, PropType, ref } from "vue";
import { Delete, Edit, View, Refresh } from '@element-plus/icons-vue'; import { Delete, Edit, View, Refresh } from "@element-plus/icons-vue";
import { ElMessageBox } from 'element-plus'; import { ElMessageBox } from "element-plus";
const props = defineProps({ const props = defineProps({
// //
tableData: { tableData: {
type: Array, type: Array,
default: [] default: [],
}, },
columns: { columns: {
type: Array as PropType<any[]>, type: Array as PropType<any[]>,
default: [] default: [],
}, },
rowKey: { rowKey: {
type: String, type: String,
default: 'id' default: "id",
}, },
hasToolbar: { hasToolbar: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
// //
hasPagination: { hasPagination: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
total: { total: {
type: Number, type: Number,
default: 0 default: 0,
}, },
currentPage: { currentPage: {
type: Number, type: Number,
default: 1 default: 1,
}, },
pageSize: { pageSize: {
type: Number, type: Number,
default: 10 default: 10,
}, },
layout: { layout: {
type: String, type: String,
default: 'total, prev, pager, next' default: "total, prev, pager, next",
}, },
delFunc: { delFunc: {
type: Function, type: Function,
default: () => { } default: () => {},
}, },
viewFunc: { viewFunc: {
type: Function, type: Function,
default: () => { } default: () => {},
}, },
editFunc: { editFunc: {
type: Function, type: Function,
default: () => { } default: () => {},
}, },
delSelection: { delSelection: {
type: Function, type: Function,
default: () => { } default: () => {},
}, },
refresh: { refresh: {
type: Function, type: Function,
default: () => { } default: () => {},
}, },
changePage: { changePage: {
type: Function, type: Function,
default: () => { } default: () => {},
} },
}) });
let { let {
tableData, tableData,
@ -154,28 +194,28 @@ let {
currentPage, currentPage,
pageSize, pageSize,
layout, layout,
} = toRefs(props) } = toRefs(props);
columns.value.forEach((item) => { columns.value.forEach((item) => {
if (item.visible === undefined) { if (item.visible === undefined) {
item.visible = true item.visible = true;
} }
}) });
// //
const multipleSelection = ref([]) const multipleSelection = ref([]);
const handleSelectionChange = (selection: any[]) => { const handleSelectionChange = (selection: any[]) => {
multipleSelection.value = selection multipleSelection.value = selection;
} };
// //
const handleCurrentChange = (val: number) => { const handleCurrentChange = (val: number) => {
props.changePage(val) props.changePage(val);
} };
const handleDelete = (row) => { const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', { ElMessageBox.confirm("确定要删除吗?", "提示", {
type: 'warning' type: "warning",
}) })
.then(async () => { .then(async () => {
props.delFunc(row); props.delFunc(row);
@ -184,9 +224,8 @@ const handleDelete = (row) => {
}; };
const getIndex = (index: number) => { const getIndex = (index: number) => {
return index + 1 + (currentPage.value - 1) * pageSize.value return index + 1 + (currentPage.value - 1) * pageSize.value;
} };
</script> </script>
<style scoped> <style scoped>

View File

@ -14,8 +14,7 @@ const props = defineProps({
data: { data: {
type: Object, type: Object,
required: true, required: true,
} },
}); });
const { row, title, column = 2, list } = props.data; const { row, title, column = 2, list } = props.data;
</script> </script>

View File

@ -1,32 +1,67 @@
<template> <template>
<el-form ref="formRef" :model="form" :rules="rules" :label-width="options.labelWidth"> <el-form
ref="formRef"
:model="form"
:rules="rules"
:label-width="options.labelWidth"
>
<el-row> <el-row>
<el-col :span="options.span" v-for="item in options.list"> <el-col :span="options.span" v-for="item in options.list">
<el-form-item :label="item.label" :prop="item.prop"> <el-form-item :label="item.label" :prop="item.prop">
<!-- 文本框数字框下拉框日期框开关上传 --> <!-- 文本框数字框下拉框日期框开关上传 -->
<el-input v-if="item.type === 'input'" v-model="form[item.prop]" :disabled="item.disabled" <el-input
:placeholder="item.placeholder" clearable></el-input> v-if="item.type === 'input'"
<el-input-number v-else-if="item.type === 'number'" v-model="form[item.prop]" v-model="form[item.prop]"
:disabled="item.disabled" controls-position="right"></el-input-number> :disabled="item.disabled"
<el-select v-else-if="item.type === 'select'" v-model="form[item.prop]" :disabled="item.disabled" :placeholder="item.placeholder"
:placeholder="item.placeholder" clearable> clearable
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option> />
<el-input-number
v-else-if="item.type === 'number'"
v-model="form[item.prop]"
:disabled="item.disabled"
controls-position="right"
/>
<el-select
v-else-if="item.type === 'select'"
v-model="form[item.prop]"
:disabled="item.disabled"
:placeholder="item.placeholder"
clearable
>
<el-option
v-for="opt in item.opts"
:label="opt.label"
:value="opt.value"
/>
</el-select> </el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="form[item.prop]" <el-date-picker
:value-format="item.format"></el-date-picker> v-else-if="item.type === 'date'"
<el-switch v-else-if="item.type === 'switch'" v-model="form[item.prop]" type="date"
:active-value="item.activeValue" :inactive-value="item.inactiveValue" v-model="form[item.prop]"
:active-text="item.activeText" :inactive-text="item.inactiveText"></el-switch> :value-format="item.format"
<el-upload v-else-if="item.type === 'upload'" class="avatar-uploader" action="#" />
:show-file-list="false" :on-success="handleAvatarSuccess"> <el-switch
v-else-if="item.type === 'switch'"
v-model="form[item.prop]"
:active-value="item.activeValue"
:inactive-value="item.inactiveValue"
:active-text="item.activeText"
:inactive-text="item.inactiveText"
/>
<el-upload
v-else-if="item.type === 'upload'"
class="avatar-uploader"
action="#"
:show-file-list="false"
:on-success="handleAvatarSuccess"
>
<img v-if="form[item.prop]" :src="form[item.prop]" class="avatar" /> <img v-if="form[item.prop]" :src="form[item.prop]" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"> <el-icon v-else class="avatar-uploader-icon">
<Plus /> <Plus />
</el-icon> </el-icon>
</el-upload> </el-upload>
<slot :name="item.prop" v-else> <slot :name="item.prop" v-else> </slot>
</slot>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -38,53 +73,59 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { FormOption } from '@/types/form-option'; import { FormOption } from "@/types/form-option";
import { FormInstance, FormRules, UploadProps } from 'element-plus'; import { FormInstance, FormRules, UploadProps } from "element-plus";
import { PropType, ref } from 'vue'; import { PropType, ref } from "vue";
const { options, formData, edit, update } = defineProps({ const { options, formData, edit, update } = defineProps({
options: { options: {
type: Object as PropType<FormOption>, type: Object as PropType<FormOption>,
required: true required: true,
}, },
formData: { formData: {
type: Object, type: Object,
required: true required: true,
}, },
edit: { edit: {
type: Boolean, type: Boolean,
required: false required: false,
}, },
update: { update: {
type: Function, type: Function,
required: true required: true,
} },
}); });
const form = ref({ ...(edit ? formData : {}) }); const form = ref({ ...(edit ? formData : {}) });
const rules: FormRules = options.list.map(item => { const rules: FormRules = options.list
.map((item) => {
if (item.required) { if (item.required) {
return { [item.prop]: [{ required: true, message: `${item.label}不能为空`, trigger: 'blur' }] }; return {
[item.prop]: [
{ required: true, message: `${item.label}不能为空`, trigger: "blur" },
],
};
} }
return {}; return {};
}).reduce((acc, cur) => ({ ...acc, ...cur }), {}); })
.reduce((acc, cur) => ({ ...acc, ...cur }), {});
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const saveEdit = (formEl: FormInstance | undefined) => { const saveEdit = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(valid => { formEl.validate((valid) => {
if (!valid) return false; if (!valid) return false;
update(form.value); update(form.value);
}); });
}; };
const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile) => { const handleAvatarSuccess: UploadProps["onSuccess"] = (
response,
uploadFile
) => {
form.value.thumb = URL.createObjectURL(uploadFile.raw!); form.value.thumb = URL.createObjectURL(uploadFile.raw!);
}; };
</script> </script>
<style> <style>

View File

@ -1,52 +1,78 @@
<template> <template>
<div class="search-container"> <div class="search-container">
<el-form ref="searchRef" :model="query" :inline="true"> <el-form ref="searchRef" :model="query" :inline="true">
<el-form-item :label="item.label" :prop="item.prop" v-for="item in options"> <el-form-item
:label="item.label"
:prop="item.prop"
v-for="item in options"
>
<!-- 文本框下拉框日期框 --> <!-- 文本框下拉框日期框 -->
<el-input v-if="item.type === 'input'" v-model="query[item.prop]" :disabled="item.disabled" <el-input
:placeholder="item.placeholder" clearable></el-input> v-if="item.type === 'input'"
<el-select v-else-if="item.type === 'select'" v-model="query[item.prop]" :disabled="item.disabled" v-model="query[item.prop]"
:placeholder="item.placeholder" clearable> :disabled="item.disabled"
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option> :placeholder="item.placeholder"
clearable
/>
<el-select
v-else-if="item.type === 'select'"
v-model="query[item.prop]"
:disabled="item.disabled"
:placeholder="item.placeholder"
clearable
>
<el-option
v-for="opt in item.opts"
:label="opt.label"
:value="opt.value"
/>
</el-select> </el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="query[item.prop]" <el-date-picker
:value-format="item.format"></el-date-picker> v-else-if="item.type === 'date'"
type="date"
v-model="query[item.prop]"
:value-format="item.format"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" :icon="Search" @click="search"></el-button> <el-button type="primary" :icon="Search" @click="search"
<el-button :icon="Refresh" @click="resetForm(searchRef)"></el-button> >搜索</el-button
>
<el-button :icon="Refresh" @click="resetForm(searchRef)"
>重置</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { FormInstance } from 'element-plus'; import { FormInstance } from "element-plus";
import { Search, Refresh } from '@element-plus/icons-vue'; import { Search, Refresh } from "@element-plus/icons-vue";
import { PropType, ref } from 'vue'; import { PropType, ref } from "vue";
import { FormOptionList } from '@/types/form-option'; import { FormOptionList } from "@/types/form-option";
const props = defineProps({ const props = defineProps({
query: { query: {
type: Object, type: Object,
required: true required: true,
}, },
options: { options: {
type: Array as PropType<Array<FormOptionList>>, type: Array as PropType<Array<FormOptionList>>,
required: true required: true,
}, },
search: { search: {
type: Function, type: Function,
default: () => { } default: () => {},
} },
}); });
const searchRef = ref<FormInstance>(); const searchRef = ref<FormInstance>();
const resetForm = (formEl: FormInstance | undefined) => { const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return;
formEl.resetFields() formEl.resetFields();
props.search(); props.search();
} };
</script> </script>
<style scoped> <style scoped>
@ -55,6 +81,6 @@ const resetForm = (formEl: FormInstance | undefined) => {
background-color: #fff; background-color: #fff;
margin-bottom: 10px; margin-bottom: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 5px border-radius: 5px;
} }
</style> </style>

View File

@ -1,13 +1,20 @@
<template> <template>
<div class="tabs-container"> <div class="tabs-container">
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs"> <el-tabs
v-model="activePath"
class="tabs"
type="card"
closable
@tab-click="clickTabls"
@tab-remove="closeTabs"
>
<el-tab-pane <el-tab-pane
v-for="item in tabs.list" v-for="item in tabs.list"
:key="item.path" :key="item.path"
:label="item.title" :label="item.title"
:name="item.path" :name="item.path"
@click="setTags(item)" @click="setTags(item)"
></el-tab-pane> />
</el-tabs> </el-tabs>
<div class="Tabs-close-box"> <div class="Tabs-close-box">
<el-dropdown @command="handleTags"> <el-dropdown @command="handleTags">
@ -30,9 +37,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'; import { ref, watch } from "vue";
import { useTabsStore } from '../store/tabs'; import { useTabsStore } from "../store/tabs";
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'; import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -59,7 +66,7 @@ onBeforeRouteUpdate((to) => {
// //
const closeAll = () => { const closeAll = () => {
tabs.clearTabs(); tabs.clearTabs();
router.push('/'); router.push("/");
}; };
// //
const closeOther = () => { const closeOther = () => {
@ -70,18 +77,18 @@ const closeOther = () => {
}; };
const handleTags = (command: string) => { const handleTags = (command: string) => {
switch (command) { switch (command) {
case 'current': case "current":
// //
tabs.closeCurrentTag({ tabs.closeCurrentTag({
$router: router, $router: router,
$route: route, $route: route,
}); });
break; break;
case 'all': case "all":
closeAll(); closeAll();
break; break;
case 'other': case "other":
closeOther(); closeOther();
break; break;
} }
@ -94,7 +101,7 @@ const closeTabs = (path: string) => {
const index = tabs.list.findIndex((item) => item.path === path); const index = tabs.list.findIndex((item) => item.path === path);
tabs.delTabsItem(index); tabs.delTabsItem(index);
const item = tabs.list[index] || tabs.list[index - 1]; const item = tabs.list[index] || tabs.list[index - 1];
router.push(item ? item.path : '/'); router.push(item ? item.path : "/");
}; };
watch( watch(

View File

@ -1,11 +1,11 @@
import { createApp } from 'vue'; import { createApp } from "vue";
import { createPinia } from 'pinia'; import { createPinia } from "pinia";
import * as ElementPlusIconsVue from '@element-plus/icons-vue'; import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import App from './App.vue'; import App from "./App.vue";
import router from './router'; import router from "./router";
import { usePermissStore } from './store/permiss'; import { usePermissStore } from "./store/permiss";
import 'element-plus/dist/index.css'; import "element-plus/dist/index.css";
import './assets/css/icon.css'; import "./assets/css/icon.css";
const app = createApp(App); const app = createApp(App);
app.use(createPinia()); app.use(createPinia());
@ -17,12 +17,12 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
} }
// 自定义权限指令 // 自定义权限指令
const permiss = usePermissStore(); const permiss = usePermissStore();
app.directive('permiss', { app.directive("permiss", {
mounted(el, binding) { mounted(el, binding) {
if (binding.value && !permiss.key.includes(String(binding.value))) { if (binding.value && !permiss.key.includes(String(binding.value))) {
el['hidden'] = true; el["hidden"] = true;
} }
}, },
}); });
app.mount('#app'); app.mount("#app");

View File

@ -1,269 +1,325 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import { usePermissStore } from '../store/permiss'; import { usePermissStore } from "../store/permiss";
import Home from '../views/home.vue'; import Home from "../views/home.vue";
import NProgress from 'nprogress'; import NProgress from "nprogress";
import 'nprogress/nprogress.css'; import "nprogress/nprogress.css";
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
path: '/', path: "/",
redirect: '/dashboard', redirect: "/dashboard",
}, },
{ {
path: '/', path: "/",
name: 'Home', name: "Home",
component: Home, component: Home,
children: [ children: [
{ {
path: '/dashboard', path: "/dashboard",
name: 'dashboard', name: "dashboard",
meta: { meta: {
title: '系统首页', title: "系统首页",
noAuth: true, noAuth: true,
}, },
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'), component: () =>
import(/* webpackChunkName: "dashboard" */ "../views/dashboard.vue"),
}, },
{ {
path: '/system-user', path: "/system-user",
name: 'system-user', name: "system-user",
meta: { meta: {
title: '用户管理', title: "用户管理",
permiss: '11', permiss: "11",
}, },
component: () => import(/* webpackChunkName: "system-user" */ '../views/system/user.vue'), component: () =>
import(
/* webpackChunkName: "system-user" */ "../views/system/user.vue"
),
}, },
{ {
path: '/system-role', path: "/system-role",
name: 'system-role', name: "system-role",
meta: { meta: {
title: '角色管理', title: "角色管理",
permiss: '12', permiss: "12",
}, },
component: () => import(/* webpackChunkName: "system-role" */ '../views/system/role.vue'), component: () =>
import(
/* webpackChunkName: "system-role" */ "../views/system/role.vue"
),
}, },
{ {
path: '/system-menu', path: "/system-menu",
name: 'system-menu', name: "system-menu",
meta: { meta: {
title: '菜单管理', title: "菜单管理",
permiss: '13', permiss: "13",
}, },
component: () => import(/* webpackChunkName: "system-menu" */ '../views/system/menu.vue'), component: () =>
import(
/* webpackChunkName: "system-menu" */ "../views/system/menu.vue"
),
}, },
{ {
path: '/table', path: "/table",
name: 'basetable', name: "basetable",
meta: { meta: {
title: '基础表格', title: "基础表格",
permiss: '31', permiss: "31",
}, },
component: () => import(/* webpackChunkName: "table" */ '../views/table/basetable.vue'), component: () =>
import(
/* webpackChunkName: "table" */ "../views/table/basetable.vue"
),
}, },
{ {
path: '/table-editor', path: "/table-editor",
name: 'table-editor', name: "table-editor",
meta: { meta: {
title: '可编辑表格', title: "可编辑表格",
permiss: '32', permiss: "32",
}, },
component: () => import(/* webpackChunkName: "table-editor" */ '../views/table/table-editor.vue'), component: () =>
import(
/* webpackChunkName: "table-editor" */ "../views/table/table-editor.vue"
),
}, },
{ {
path: '/schart', path: "/schart",
name: 'schart', name: "schart",
meta: { meta: {
title: 'schart图表', title: "schart图表",
permiss: '41', permiss: "41",
}, },
component: () => import(/* webpackChunkName: "schart" */ '../views/chart/schart.vue'), component: () =>
import(/* webpackChunkName: "schart" */ "../views/chart/schart.vue"),
}, },
{ {
path: '/echarts', path: "/echarts",
name: 'echarts', name: "echarts",
meta: { meta: {
title: 'echarts图表', title: "echarts图表",
permiss: '42', permiss: "42",
}, },
component: () => import(/* webpackChunkName: "echarts" */ '../views/chart/echarts.vue'), component: () =>
import(
/* webpackChunkName: "echarts" */ "../views/chart/echarts.vue"
),
}, },
{ {
path: '/icon', path: "/icon",
name: 'icon', name: "icon",
meta: { meta: {
title: '图标', title: "图标",
permiss: '5', permiss: "5",
}, },
component: () => import(/* webpackChunkName: "icon" */ '../views/pages/icon.vue'), component: () =>
import(/* webpackChunkName: "icon" */ "../views/pages/icon.vue"),
}, },
{ {
path: '/ucenter', path: "/ucenter",
name: 'ucenter', name: "ucenter",
meta: { meta: {
title: '个人中心', title: "个人中心",
}, },
component: () => import(/* webpackChunkName: "ucenter" */ '../views/pages/ucenter.vue'), component: () =>
import(
/* webpackChunkName: "ucenter" */ "../views/pages/ucenter.vue"
),
}, },
{ {
path: '/editor', path: "/editor",
name: 'editor', name: "editor",
meta: { meta: {
title: '富文本编辑器', title: "富文本编辑器",
permiss: '291', permiss: "291",
}, },
component: () => import(/* webpackChunkName: "editor" */ '../views/pages/editor.vue'), component: () =>
import(/* webpackChunkName: "editor" */ "../views/pages/editor.vue"),
}, },
{ {
path: '/markdown', path: "/markdown",
name: 'markdown', name: "markdown",
meta: { meta: {
title: 'markdown编辑器', title: "markdown编辑器",
permiss: '292', permiss: "292",
}, },
component: () => import(/* webpackChunkName: "markdown" */ '../views/pages/markdown.vue'), component: () =>
import(
/* webpackChunkName: "markdown" */ "../views/pages/markdown.vue"
),
}, },
{ {
path: '/export', path: "/export",
name: 'export', name: "export",
meta: { meta: {
title: '导出Excel', title: "导出Excel",
permiss: '34', permiss: "34",
}, },
component: () => import(/* webpackChunkName: "export" */ '../views/table/export.vue'), component: () =>
import(/* webpackChunkName: "export" */ "../views/table/export.vue"),
}, },
{ {
path: '/import', path: "/import",
name: 'import', name: "import",
meta: { meta: {
title: '导入Excel', title: "导入Excel",
permiss: '33', permiss: "33",
}, },
component: () => import(/* webpackChunkName: "import" */ '../views/table/import.vue'), component: () =>
import(/* webpackChunkName: "import" */ "../views/table/import.vue"),
}, },
{ {
path: '/theme', path: "/theme",
name: 'theme', name: "theme",
meta: { meta: {
title: '主题设置', title: "主题设置",
permiss: '7', permiss: "7",
}, },
component: () => import(/* webpackChunkName: "theme" */ '../views/pages/theme.vue'), component: () =>
import(/* webpackChunkName: "theme" */ "../views/pages/theme.vue"),
}, },
{ {
path: '/calendar', path: "/calendar",
name: 'calendar', name: "calendar",
meta: { meta: {
title: '日历', title: "日历",
permiss: '24', permiss: "24",
}, },
component: () => import(/* webpackChunkName: "calendar" */ '../views/element/calendar.vue'), component: () =>
import(
/* webpackChunkName: "calendar" */ "../views/element/calendar.vue"
),
}, },
{ {
path: '/watermark', path: "/watermark",
name: 'watermark', name: "watermark",
meta: { meta: {
title: '水印', title: "水印",
permiss: '25', permiss: "25",
}, },
component: () => import(/* webpackChunkName: "watermark" */ '../views/element/watermark.vue'), component: () =>
import(
/* webpackChunkName: "watermark" */ "../views/element/watermark.vue"
),
}, },
{ {
path: '/carousel', path: "/carousel",
name: 'carousel', name: "carousel",
meta: { meta: {
title: '走马灯', title: "走马灯",
permiss: '23', permiss: "23",
}, },
component: () => import(/* webpackChunkName: "carousel" */ '../views/element/carousel.vue'), component: () =>
import(
/* webpackChunkName: "carousel" */ "../views/element/carousel.vue"
),
}, },
{ {
path: '/tour', path: "/tour",
name: 'tour', name: "tour",
meta: { meta: {
title: '分步引导', title: "分步引导",
permiss: '26', permiss: "26",
}, },
component: () => import(/* webpackChunkName: "tour" */ '../views/element/tour.vue'), component: () =>
import(/* webpackChunkName: "tour" */ "../views/element/tour.vue"),
}, },
{ {
path: '/steps', path: "/steps",
name: 'steps', name: "steps",
meta: { meta: {
title: '步骤条', title: "步骤条",
permiss: '27', permiss: "27",
}, },
component: () => import(/* webpackChunkName: "steps" */ '../views/element/steps.vue'), component: () =>
import(/* webpackChunkName: "steps" */ "../views/element/steps.vue"),
}, },
{ {
path: '/form', path: "/form",
name: 'forms', name: "forms",
meta: { meta: {
title: '表单', title: "表单",
permiss: '21', permiss: "21",
}, },
component: () => import(/* webpackChunkName: "form" */ '../views/element/form.vue'), component: () =>
import(/* webpackChunkName: "form" */ "../views/element/form.vue"),
}, },
{ {
path: '/upload', path: "/upload",
name: 'upload', name: "upload",
meta: { meta: {
title: '上传', title: "上传",
permiss: '22', permiss: "22",
}, },
component: () => import(/* webpackChunkName: "upload" */ '../views/element/upload.vue'), component: () =>
import(
/* webpackChunkName: "upload" */ "../views/element/upload.vue"
),
}, },
{ {
path: '/statistic', path: "/statistic",
name: 'statistic', name: "statistic",
meta: { meta: {
title: '统计', title: "统计",
permiss: '28', permiss: "28",
}, },
component: () => import(/* webpackChunkName: "statistic" */ '../views/element/statistic.vue'), component: () =>
import(
/* webpackChunkName: "statistic" */ "../views/element/statistic.vue"
),
}, },
], ],
}, },
{ {
path: '/login', path: "/login",
meta: { meta: {
title: '登录', title: "登录",
noAuth: true, noAuth: true,
}, },
component: () => import(/* webpackChunkName: "login" */ '../views/pages/login.vue'), component: () =>
import(/* webpackChunkName: "login" */ "../views/pages/login.vue"),
}, },
{ {
path: '/register', path: "/register",
meta: { meta: {
title: '注册', title: "注册",
noAuth: true, noAuth: true,
}, },
component: () => import(/* webpackChunkName: "register" */ '../views/pages/register.vue'), component: () =>
import(/* webpackChunkName: "register" */ "../views/pages/register.vue"),
}, },
{ {
path: '/reset-pwd', path: "/reset-pwd",
meta: { meta: {
title: '重置密码', title: "重置密码",
noAuth: true, noAuth: true,
}, },
component: () => import(/* webpackChunkName: "reset-pwd" */ '../views/pages/reset-pwd.vue'), component: () =>
import(
/* webpackChunkName: "reset-pwd" */ "../views/pages/reset-pwd.vue"
),
}, },
{ {
path: '/403', path: "/403",
meta: { meta: {
title: '没有权限', title: "没有权限",
noAuth: true, noAuth: true,
}, },
component: () => import(/* webpackChunkName: "403" */ '../views/pages/403.vue'), component: () =>
import(/* webpackChunkName: "403" */ "../views/pages/403.vue"),
}, },
{ {
path: '/404', path: "/404",
meta: { meta: {
title: '找不到页面', title: "找不到页面",
noAuth: true, noAuth: true,
}, },
component: () => import(/* webpackChunkName: "404" */ '../views/pages/404.vue'), component: () =>
import(/* webpackChunkName: "404" */ "../views/pages/404.vue"),
}, },
{ path: '/:path(.*)', redirect: '/404' }, { path: "/:path(.*)", redirect: "/404" },
]; ];
const router = createRouter({ const router = createRouter({
@ -273,14 +329,17 @@ const router = createRouter({
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
NProgress.start(); NProgress.start();
const role = localStorage.getItem('vuems_name'); const role = localStorage.getItem("vuems_name");
const permiss = usePermissStore(); const permiss = usePermissStore();
if (!role && to.meta.noAuth !== true) { if (!role && to.meta.noAuth !== true) {
next('/login'); next("/login");
} else if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) { } else if (
typeof to.meta.permiss == "string" &&
!permiss.key.includes(to.meta.permiss)
) {
// 如果没有权限则进入403 // 如果没有权限则进入403
next('/403'); next("/403");
} else { } else {
next(); next();
} }

View File

@ -1,54 +1,56 @@
import { defineStore } from 'pinia'; import { defineStore } from "pinia";
interface ObjectList { interface ObjectList {
[key: string]: string[]; [key: string]: string[];
} }
export const usePermissStore = defineStore('permiss', { export const usePermissStore = defineStore("permiss", {
state: () => { state: () => {
const defaultList: ObjectList = { const defaultList: ObjectList = {
admin: [ admin: [
'0', "0",
'1', "1",
'11', "11",
'12', "12",
'13', "13",
'2', "2",
'21', "21",
'22', "22",
'23', "23",
'24', "24",
'25', "25",
'26', "26",
'27', "27",
'28', "28",
'29', "29",
'291', "291",
'292', "292",
'3', "3",
'31', "31",
'32', "32",
'33', "33",
'34', "34",
'4', "4",
'41', "41",
'42', "42",
'5', "5",
'7', "7",
'6', "6",
'61', "61",
'62', "62",
'63', "63",
'64', "64",
'65', "65",
'66', "66",
], ],
user: ['0', '1', '11', '12', '13'], user: ["0", "1", "11", "12", "13"],
}; };
const username = localStorage.getItem('vuems_name'); const username = localStorage.getItem("vuems_name");
console.log(username); console.log(username);
return { return {
key: (username == 'admin' ? defaultList.admin : defaultList.user) as string[], key: (username == "admin"
? defaultList.admin
: defaultList.user) as string[],
defaultList, defaultList,
}; };
}, },

View File

@ -1,11 +1,11 @@
import { defineStore } from 'pinia'; import { defineStore } from "pinia";
export const useSidebarStore = defineStore('sidebar', { export const useSidebarStore = defineStore("sidebar", {
state: () => { state: () => {
return { return {
collapse: false, collapse: false,
bgColor: localStorage.getItem('sidebar-bg-color') || '#324157', bgColor: localStorage.getItem("sidebar-bg-color") || "#324157",
textColor: localStorage.getItem('sidebar-text-color') || '#bfcbd9' textColor: localStorage.getItem("sidebar-text-color") || "#bfcbd9",
}; };
}, },
getters: {}, getters: {},
@ -15,11 +15,11 @@ export const useSidebarStore = defineStore('sidebar', {
}, },
setBgColor(color: string) { setBgColor(color: string) {
this.bgColor = color; this.bgColor = color;
localStorage.setItem('sidebar-bg-color', color); localStorage.setItem("sidebar-bg-color", color);
}, },
setTextColor(color: string) { setTextColor(color: string) {
this.textColor = color; this.textColor = color;
localStorage.setItem('sidebar-text-color', color); localStorage.setItem("sidebar-text-color", color);
} },
} },
}); });

View File

@ -1,4 +1,4 @@
import { defineStore } from 'pinia'; import { defineStore } from "pinia";
interface ListItem { interface ListItem {
name: string; name: string;
@ -6,19 +6,19 @@ interface ListItem {
title: string; title: string;
} }
export const useTabsStore = defineStore('tabs', { export const useTabsStore = defineStore("tabs", {
state: () => { state: () => {
return { return {
list: <ListItem[]>[] list: <ListItem[]>[],
}; };
}, },
getters: { getters: {
show: state => { show: (state) => {
return state.list.length > 0; return state.list.length > 0;
}, },
nameList: state => { nameList: (state) => {
return state.list.map(item => item.name); return state.list.map((item) => item.name);
} },
}, },
actions: { actions: {
delTabsItem(index: number) { delTabsItem(index: number) {
@ -42,12 +42,12 @@ export const useTabsStore = defineStore('tabs', {
} else if (i > 0) { } else if (i > 0) {
data.$router.push(this.list[i - 1].path); data.$router.push(this.list[i - 1].path);
} else { } else {
data.$router.push('/'); data.$router.push("/");
} }
this.list.splice(i, 1); this.list.splice(i, 1);
break; break;
} }
} }
} },
} },
}); });

View File

@ -1,58 +1,61 @@
import { mix, setProperty } from '@/utils'; import { mix, setProperty } from "@/utils";
import { defineStore } from 'pinia'; import { defineStore } from "pinia";
export const useThemeStore = defineStore('theme', { export const useThemeStore = defineStore("theme", {
state: () => { state: () => {
return { return {
primary: '', primary: "",
success: '', success: "",
warning: '', warning: "",
danger: '', danger: "",
info: '', info: "",
headerBgColor: '#242f42', headerBgColor: "#242f42",
headerTextColor: '#fff', headerTextColor: "#fff",
}; };
}, },
getters: {}, getters: {},
actions: { actions: {
initTheme() { initTheme() {
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => { ["primary", "success", "warning", "danger", "info"].forEach((type) => {
const color = localStorage.getItem(`theme-${type}`) || ''; const color = localStorage.getItem(`theme-${type}`) || "";
if (color) { if (color) {
this.setPropertyColor(color, type); // 设置主题色 this.setPropertyColor(color, type); // 设置主题色
} }
}); });
const headerBgColor = localStorage.getItem('header-bg-color'); const headerBgColor = localStorage.getItem("header-bg-color");
headerBgColor && this.setHeaderBgColor(headerBgColor); headerBgColor && this.setHeaderBgColor(headerBgColor);
const headerTextColor = localStorage.getItem('header-text-color'); const headerTextColor = localStorage.getItem("header-text-color");
headerTextColor && this.setHeaderTextColor(headerTextColor); headerTextColor && this.setHeaderTextColor(headerTextColor);
}, },
resetTheme() { resetTheme() {
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => { ["primary", "success", "warning", "danger", "info"].forEach((type) => {
this.setPropertyColor('', type); // 重置主题色 this.setPropertyColor("", type); // 重置主题色
}); });
}, },
setPropertyColor(color: string, type: string = 'primary') { setPropertyColor(color: string, type: string = "primary") {
this[type] = color; this[type] = color;
setProperty(`--el-color-${type}`, color); setProperty(`--el-color-${type}`, color);
localStorage.setItem(`theme-${type}`, color); localStorage.setItem(`theme-${type}`, color);
this.setThemeLight(type); this.setThemeLight(type);
}, },
setThemeLight(type: string = 'primary') { setThemeLight(type: string = "primary") {
[3, 5, 7, 8, 9].forEach((v) => { [3, 5, 7, 8, 9].forEach((v) => {
setProperty(`--el-color-${type}-light-${v}`, mix('#ffffff', this[type], v / 10)); setProperty(
`--el-color-${type}-light-${v}`,
mix("#ffffff", this[type], v / 10)
);
}); });
setProperty(`--el-color-${type}-dark-2`, mix('#ffffff', this[type], 0.2)); setProperty(`--el-color-${type}-dark-2`, mix("#ffffff", this[type], 0.2));
}, },
setHeaderBgColor(color: string) { setHeaderBgColor(color: string) {
this.headerBgColor = color; this.headerBgColor = color;
setProperty('--header-bg-color', color); setProperty("--header-bg-color", color);
localStorage.setItem(`header-bg-color`, color); localStorage.setItem(`header-bg-color`, color);
}, },
setHeaderTextColor(color: string) { setHeaderTextColor(color: string) {
this.headerTextColor = color; this.headerTextColor = color;
setProperty('--header-text-color', color); setProperty("--header-text-color", color);
localStorage.setItem(`header-text-color`, color); localStorage.setItem(`header-text-color`, color);
} },
} },
}); });

View File

@ -2,7 +2,6 @@ export interface FormOption {
list: FormOptionList[]; list: FormOptionList[];
labelWidth?: number | string; labelWidth?: number | string;
span?: number; span?: number;
} }
export interface FormOptionList { export interface FormOptionList {

View File

@ -1,8 +1,7 @@
export interface Role { export interface Role {
id: number; id: number;
name: string; name: string;
key: string; key: string;
status: boolean; status: boolean;
permiss: string[] permiss: string[];
} }

View File

@ -1,4 +1,3 @@
export interface User { export interface User {
id: number; id: number;
name: string; name: string;

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,22 @@
export const setProperty = (prop: string, val: any, dom = document.documentElement) => { export const setProperty = (
prop: string,
val: any,
dom = document.documentElement
) => {
dom.style.setProperty(prop, val); dom.style.setProperty(prop, val);
}; };
export const mix = (color1: string, color2: string, weight: number = 0.5): string => { export const mix = (
let color = '#'; color1: string,
color2: string,
weight: number = 0.5
): string => {
let color = "#";
for (let i = 0; i <= 2; i++) { for (let i = 0; i <= 2; i++) {
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16); const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16);
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16); const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16);
const c = Math.round(c1 * weight + c2 * (1 - weight)); const c = Math.round(c1 * weight + c2 * (1 - weight));
color += c.toString(16).padStart(2, '0'); color += c.toString(16).padStart(2, "0");
} }
return color; return color;
}; };

View File

@ -1,7 +1,13 @@
import axios, { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import axios, {
AxiosInstance,
AxiosError,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
timeout: 5000 baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000,
}); });
service.interceptors.request.use( service.interceptors.request.use(

View File

@ -2,7 +2,9 @@
<div class="container"> <div class="container">
<div class="plugins-tips"> <div class="plugins-tips">
vue-echartsApache ECharts Vue.js 组件 访问地址 vue-echartsApache ECharts Vue.js 组件 访问地址
<a href="https://github.com/ecomfe/vue-echarts" target="_blank">vue-echarts</a> <a href="https://github.com/ecomfe/vue-echarts" target="_blank"
>vue-echarts</a
>
</div> </div>
<el-card class="mgb20" shadow="hover"> <el-card class="mgb20" shadow="hover">
<template #header> <template #header>
@ -44,20 +46,27 @@
</template> </template>
<script setup lang="ts" name="echarts"> <script setup lang="ts" name="echarts">
import { registerMap, use } from 'echarts/core'; import { registerMap, use } from "echarts/core";
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts'; import { BarChart, LineChart, PieChart, MapChart } from "echarts/charts";
import { import {
GridComponent, GridComponent,
TooltipComponent, TooltipComponent,
LegendComponent, LegendComponent,
TitleComponent, TitleComponent,
VisualMapComponent, VisualMapComponent,
} from 'echarts/components'; } from "echarts/components";
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from "echarts/renderers";
import VChart from 'vue-echarts'; import VChart from "vue-echarts";
import 'echarts-wordcloud'; import "echarts-wordcloud";
import { barOptions, lineOptions, pieOptions, ringOptions, wordOptions, mapOptions } from './options'; import {
import chinaMap from '@/utils/china'; barOptions,
lineOptions,
pieOptions,
ringOptions,
wordOptions,
mapOptions,
} from "./options";
import chinaMap from "@/utils/china";
use([ use([
CanvasRenderer, CanvasRenderer,
BarChart, BarChart,
@ -70,7 +79,7 @@ use([
TitleComponent, TitleComponent,
VisualMapComponent, VisualMapComponent,
]); ]);
registerMap('china', chinaMap); registerMap("china", chinaMap);
</script> </script>
<style scoped> <style scoped>

View File

@ -1,63 +1,63 @@
import { graphic } from 'echarts/core'; import { graphic } from "echarts/core";
export const barOptions = { export const barOptions = {
xAxis: { xAxis: {
type: 'category', type: "category",
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
}, },
yAxis: { yAxis: {
type: 'value', type: "value",
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: "axis",
axisPointer: { axisPointer: {
type: 'shadow', type: "shadow",
}, },
}, },
color: ['#009688', '#f44336'], color: ["#009688", "#f44336"],
series: [ series: [
{ {
data: [120, 200, 150, 80, 70, 110, 130], data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar', type: "bar",
}, },
{ {
data: [180, 230, 190, 120, 110, 230, 235], data: [180, 230, 190, 120, 110, 230, 235],
type: 'bar', type: "bar",
}, },
], ],
}; };
export const lineOptions = { export const lineOptions = {
tooltip: { tooltip: {
trigger: 'axis', trigger: "axis",
}, },
grid: { grid: {
left: '3%', left: "3%",
right: '4%', right: "4%",
bottom: '3%', bottom: "3%",
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
type: 'category', type: "category",
boundaryGap: false, boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
}, },
yAxis: { yAxis: {
type: 'value', type: "value",
}, },
color: ['#009688', '#f44336'], color: ["#009688", "#f44336"],
series: [ series: [
{ {
name: 'Email', name: "Email",
type: 'line', type: "line",
stack: 'Total', stack: "Total",
areaStyle: {}, areaStyle: {},
smooth: true, smooth: true,
data: [120, 132, 101, 134, 90, 230, 210], data: [120, 132, 101, 134, 90, 230, 210],
}, },
{ {
name: 'Union Ads', name: "Union Ads",
type: 'line', type: "line",
stack: 'Total', stack: "Total",
areaStyle: {}, areaStyle: {},
smooth: true, smooth: true,
data: [220, 182, 191, 234, 290, 330, 310], data: [220, 182, 191, 234, 290, 330, 310],
@ -67,34 +67,34 @@ export const lineOptions = {
export const pieOptions = { export const pieOptions = {
title: { title: {
text: 'Referer of a Website', text: "Referer of a Website",
subtext: 'Fake Data', subtext: "Fake Data",
left: 'center', left: "center",
}, },
tooltip: { tooltip: {
trigger: 'item', trigger: "item",
}, },
legend: { legend: {
orient: 'vertical', orient: "vertical",
left: 'left', left: "left",
}, },
series: [ series: [
{ {
name: 'Access From', name: "Access From",
type: 'pie', type: "pie",
radius: '50%', radius: "50%",
data: [ data: [
{ value: 1048, name: 'Search Engine' }, { value: 1048, name: "Search Engine" },
{ value: 735, name: 'Direct' }, { value: 735, name: "Direct" },
{ value: 580, name: 'Email' }, { value: 580, name: "Email" },
{ value: 484, name: 'Union Ads' }, { value: 484, name: "Union Ads" },
{ value: 300, name: 'Video Ads' }, { value: 300, name: "Video Ads" },
], ],
emphasis: { emphasis: {
itemStyle: { itemStyle: {
shadowBlur: 10, shadowBlur: 10,
shadowOffsetX: 0, shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)', shadowColor: "rgba(0, 0, 0, 0.5)",
}, },
}, },
}, },
@ -104,73 +104,73 @@ export const pieOptions = {
export const wordOptions = { export const wordOptions = {
series: [ series: [
{ {
type: 'wordCloud', type: "wordCloud",
rotationRange: [0, 0], rotationRange: [0, 0],
autoSize: { autoSize: {
enable: true, enable: true,
minSize: 14, minSize: 14,
}, },
textStyle: { textStyle: {
fontFamily: '微软雅黑,sans-serif', fontFamily: "微软雅黑,sans-serif",
color: function () { color: function () {
return ( return (
'rgb(' + "rgb(" +
[ [
Math.round(Math.random() * 160), Math.round(Math.random() * 160),
Math.round(Math.random() * 160), Math.round(Math.random() * 160),
Math.round(Math.random() * 160), Math.round(Math.random() * 160),
].join(',') + ].join(",") +
')' ")"
); );
}, },
}, },
data: [ data: [
{ {
name: 'Vue', name: "Vue",
value: 10000, value: 10000,
}, },
{ {
name: 'React', name: "React",
value: 9000, value: 9000,
}, },
{ {
name: '图表', name: "图表",
value: 4000, value: 4000,
}, },
{ {
name: '产品', name: "产品",
value: 7000, value: 7000,
}, },
{ {
name: 'vue-manage-system', name: "vue-manage-system",
value: 2000, value: 2000,
}, },
{ {
name: 'element-plus', name: "element-plus",
value: 6000, value: 6000,
}, },
{ {
name: '管理系统', name: "管理系统",
value: 5000, value: 5000,
}, },
{ {
name: '前端', name: "前端",
value: 4000, value: 4000,
}, },
{ {
name: '测试', name: "测试",
value: 3000, value: 3000,
}, },
{ {
name: '后端', name: "后端",
value: 8000, value: 8000,
}, },
{ {
name: '软件开发', name: "软件开发",
value: 6000, value: 6000,
}, },
{ {
name: '程序员', name: "程序员",
value: 4000, value: 4000,
}, },
], ],
@ -180,44 +180,44 @@ export const wordOptions = {
export const ringOptions = { export const ringOptions = {
tooltip: { tooltip: {
trigger: 'item', trigger: "item",
}, },
legend: { legend: {
top: '5%', top: "5%",
left: 'center', left: "center",
}, },
series: [ series: [
{ {
name: 'Access From', name: "Access From",
type: 'pie', type: "pie",
radius: ['40%', '70%'], radius: ["40%", "70%"],
avoidLabelOverlap: false, avoidLabelOverlap: false,
itemStyle: { itemStyle: {
borderRadius: 10, borderRadius: 10,
borderColor: '#fff', borderColor: "#fff",
borderWidth: 2, borderWidth: 2,
}, },
label: { label: {
show: false, show: false,
position: 'center', position: "center",
}, },
emphasis: { emphasis: {
label: { label: {
show: true, show: true,
fontSize: 40, fontSize: 40,
fontWeight: 'bold', fontWeight: "bold",
}, },
}, },
labelLine: { labelLine: {
show: false, show: false,
}, },
data: [ data: [
{ value: 1048, name: 'Search Engine' }, { value: 1048, name: "Search Engine" },
{ value: 735, name: 'Direct' }, { value: 735, name: "Direct" },
{ value: 580, name: 'Email' }, { value: 580, name: "Email" },
{ value: 484, name: 'Union Ads' }, { value: 484, name: "Union Ads" },
{ value: 300, name: 'Video Ads' }, { value: 300, name: "Video Ads" },
], ],
}, },
], ],
@ -225,33 +225,33 @@ export const ringOptions = {
export const dashOpt1 = { export const dashOpt1 = {
xAxis: { xAxis: {
type: 'category', type: "category",
boundaryGap: false, boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
}, },
yAxis: { yAxis: {
type: 'value', type: "value",
}, },
grid: { grid: {
top: '2%', top: "2%",
left: '2%', left: "2%",
right: '3%', right: "3%",
bottom: '2%', bottom: "2%",
containLabel: true, containLabel: true,
}, },
color: ['#009688', '#f44336'], color: ["#009688", "#f44336"],
series: [ series: [
{ {
type: 'line', type: "line",
areaStyle: { areaStyle: {
color: new graphic.LinearGradient(0, 0, 0, 1, [ color: new graphic.LinearGradient(0, 0, 0, 1, [
{ {
offset: 0, offset: 0,
color: 'rgba(0, 150, 136,0.8)', color: "rgba(0, 150, 136,0.8)",
}, },
{ {
offset: 1, offset: 1,
color: 'rgba(0, 150, 136,0.2)', color: "rgba(0, 150, 136,0.2)",
}, },
]), ]),
}, },
@ -259,7 +259,7 @@ export const dashOpt1 = {
data: [120, 132, 301, 134, 90, 230, 210], data: [120, 132, 301, 134, 90, 230, 210],
}, },
{ {
type: 'line', type: "line",
smooth: true, smooth: true,
data: [220, 122, 191, 234, 190, 130, 310], data: [220, 122, 191, 234, 190, 130, 310],
}, },
@ -268,26 +268,26 @@ export const dashOpt1 = {
export const dashOpt2 = { export const dashOpt2 = {
legend: { legend: {
bottom: '1%', bottom: "1%",
left: 'center', left: "center",
}, },
color: ['#3f51b5', '#009688', '#f44336', '#00bcd4', '#1ABC9C'], color: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
series: [ series: [
{ {
type: 'pie', type: "pie",
radius: ['40%', '70%'], radius: ["40%", "70%"],
avoidLabelOverlap: false, avoidLabelOverlap: false,
itemStyle: { itemStyle: {
borderRadius: 10, borderRadius: 10,
borderColor: '#fff', borderColor: "#fff",
borderWidth: 2, borderWidth: 2,
}, },
data: [ data: [
{ value: 1048, name: '数码' }, { value: 1048, name: "数码" },
{ value: 735, name: '食品' }, { value: 735, name: "食品" },
{ value: 580, name: '母婴' }, { value: 580, name: "母婴" },
{ value: 484, name: '家电' }, { value: 484, name: "家电" },
{ value: 300, name: '运动' }, { value: 300, name: "运动" },
], ],
}, },
], ],
@ -295,10 +295,10 @@ export const dashOpt2 = {
export const mapOptions = { export const mapOptions = {
tooltip: { tooltip: {
trigger: 'item', trigger: "item",
}, },
geo: { geo: {
map: 'china', map: "china",
roam: false, roam: false,
emphasis: { emphasis: {
label: { label: {
@ -313,32 +313,32 @@ export const mapOptions = {
realtime: false, realtime: false,
calculable: false, calculable: false,
inRange: { inRange: {
color: ['#d2e0f5', '#71A9FF'], color: ["#d2e0f5", "#71A9FF"],
}, },
}, },
series: [ series: [
{ {
geoIndex: 0, geoIndex: 0,
name: '地域分布', name: "地域分布",
type: 'map', type: "map",
coordinateSystem: 'geo', coordinateSystem: "geo",
map: 'china', map: "china",
data: [ data: [
{ name: '北京', value: 100 }, { name: "北京", value: 100 },
{ name: '上海', value: 100 }, { name: "上海", value: 100 },
{ name: '广东', value: 100 }, { name: "广东", value: 100 },
{ name: '浙江', value: 90 }, { name: "浙江", value: 90 },
{ name: '江西', value: 80 }, { name: "江西", value: 80 },
{ name: '山东', value: 70 }, { name: "山东", value: 70 },
{ name: '广西', value: 60 }, { name: "广西", value: 60 },
{ name: '河南', value: 50 }, { name: "河南", value: 50 },
{ name: '河南', value: 40 }, { name: "河南", value: 40 },
{ name: '青海', value: 70 }, { name: "青海", value: 70 },
{ name: '河南', value: 30 }, { name: "河南", value: 30 },
{ name: '黑龙江', value: 20 }, { name: "黑龙江", value: 20 },
{ name: '新疆', value: 20 }, { name: "新疆", value: 20 },
{ name: '云南', value: 20 }, { name: "云南", value: 20 },
{ name: '甘肃', value: 20 }, { name: "甘肃", value: 20 },
], ],
}, },
], ],

View File

@ -2,116 +2,126 @@
<div class="container"> <div class="container">
<div class="plugins-tips"> <div class="plugins-tips">
vue-schartvue.js封装sChart.js的图表组件 访问地址 vue-schartvue.js封装sChart.js的图表组件 访问地址
<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a> <a href="https://github.com/lin-xin/vue-schart" target="_blank"
>vue-schart</a
>
</div> </div>
<el-card class="mgb20" shadow="hover"> <el-card class="mgb20" shadow="hover">
<template #header> <template #header>
<div class="content-title">柱状图</div> <div class="content-title">柱状图</div>
</template> </template>
<schart class="schart" canvasId="bar" :options="options1"></schart> <schart class="schart" canvasId="bar" :options="options1" />
</el-card> </el-card>
<el-card class="mgb20" shadow="hover"> <el-card class="mgb20" shadow="hover">
<template #header> <template #header>
<div class="content-title">折线图</div> <div class="content-title">折线图</div>
</template> </template>
<schart class="schart" canvasId="line" :options="options2"></schart> <schart class="schart" canvasId="line" :options="options2" />
</el-card> </el-card>
<el-card class="mgb20" shadow="hover"> <el-card class="mgb20" shadow="hover">
<template #header> <template #header>
<div class="content-title">饼状图</div> <div class="content-title">饼状图</div>
</template> </template>
<schart class="schart" canvasId="pie" :options="options3"></schart> <schart class="schart" canvasId="pie" :options="options3" />
</el-card> </el-card>
<el-card class="mgb20" shadow="hover"> <el-card class="mgb20" shadow="hover">
<template #header> <template #header>
<div class="content-title">环形图</div> <div class="content-title">环形图</div>
</template> </template>
<schart class="schart" canvasId="ring" :options="options4"></schart> <schart class="schart" canvasId="ring" :options="options4" />
</el-card> </el-card>
</div> </div>
</template> </template>
<script setup lang="ts" name="schart"> <script setup lang="ts" name="schart">
import Schart from 'vue-schart'; import Schart from "vue-schart";
const options1 = { const options1 = {
type: 'bar', type: "bar",
title: { title: {
text: '最近一周各品类销售图' text: "最近一周各品类销售图",
}, },
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"], colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
labels: ['周一', '周二', '周三', '周四', '周五'], labels: ["周一", "周二", "周三", "周四", "周五"],
datasets: [ datasets: [
{ {
label: '家电', label: "家电",
// fillColor: 'rgba(241, 49, 74, 0.5)', // fillColor: 'rgba(241, 49, 74, 0.5)',
data: [234, 278, 270, 190, 230] data: [234, 278, 270, 190, 230],
}, },
{ {
label: '百货', label: "百货",
data: [164, 178, 190, 135, 160] data: [164, 178, 190, 135, 160],
}, },
{ {
label: '食品', label: "食品",
data: [144, 198, 150, 235, 120] data: [144, 198, 150, 235, 120],
} },
] ],
}; };
const options2 = { const options2 = {
type: 'line', type: "line",
title: { title: {
text: '最近几个月各品类销售趋势图' text: "最近几个月各品类销售趋势图",
}, },
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"], colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
labels: ['6月', '7月', '8月', '9月', '10月'], labels: ["6月", "7月", "8月", "9月", "10月"],
datasets: [ datasets: [
{ {
label: '家电', label: "家电",
data: [234, 278, 270, 190, 230] data: [234, 278, 270, 190, 230],
}, },
{ {
label: '百货', label: "百货",
data: [164, 178, 150, 135, 160] data: [164, 178, 150, 135, 160],
}, },
{ {
label: '食品', label: "食品",
data: [114, 138, 200, 235, 190] data: [114, 138, 200, 235, 190],
} },
] ],
}; };
const options3 = { const options3 = {
type: 'pie', type: "pie",
title: { title: {
text: '服装品类销售饼状图' text: "服装品类销售饼状图",
}, },
legend: { legend: {
position: 'left' position: "left",
}, },
colorList: ["#2196f3", '#673ab7', "#009688", "#1ABC9C", "#3f51b5", "#f44336", "#00bcd4"], colorList: [
labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'], "#2196f3",
"#673ab7",
"#009688",
"#1ABC9C",
"#3f51b5",
"#f44336",
"#00bcd4",
],
labels: ["T恤", "牛仔裤", "连衣裙", "毛衣", "七分裤", "短裙", "羽绒服"],
datasets: [ datasets: [
{ {
data: [334, 278, 190, 235, 260, 200, 141] data: [334, 278, 190, 235, 260, 200, 141],
} },
] ],
}; };
const options4 = { const options4 = {
type: 'ring', type: "ring",
title: { title: {
text: '环形三等分' text: "环形三等分",
}, },
showValue: false, showValue: false,
legend: { legend: {
position: 'bottom', position: "bottom",
bottom: 40 bottom: 40,
}, },
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"], colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
labels: ['vue', 'react', 'angular'], labels: ["vue", "react", "angular"],
datasets: [ datasets: [
{ {
data: [500, 500, 500] data: [500, 500, 500],
} },
] ],
}; };
</script> </script>

View File

@ -52,7 +52,9 @@
<el-card shadow="hover"> <el-card shadow="hover">
<div class="card-header"> <div class="card-header">
<p class="card-header-title">订单动态</p> <p class="card-header-title">订单动态</p>
<p class="card-header-desc">最近一周订单状态包括订单成交量和订单退货量</p> <p class="card-header-desc">
最近一周订单状态包括订单成交量和订单退货量
</p>
</div> </div>
<v-chart class="chart" :option="dashOpt1" /> <v-chart class="chart" :option="dashOpt1" />
</el-card> </el-card>
@ -75,7 +77,11 @@
<p class="card-header-desc">最新的销售动态和活动信息</p> <p class="card-header-desc">最新的销售动态和活动信息</p>
</div> </div>
<el-timeline> <el-timeline>
<el-timeline-item v-for="(activity, index) in activities" :key="index" :color="activity.color"> <el-timeline-item
v-for="(activity, index) in activities"
:key="index"
:color="activity.color"
>
<div class="timeline-item"> <div class="timeline-item">
<div> <div>
<p>{{ activity.content }}</p> <p>{{ activity.content }}</p>
@ -127,20 +133,20 @@
</template> </template>
<script setup lang="ts" name="dashboard"> <script setup lang="ts" name="dashboard">
import countup from '@/components/countup.vue'; import countup from "@/components/countup.vue";
import { use, registerMap } from 'echarts/core'; import { use, registerMap } from "echarts/core";
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts'; import { BarChart, LineChart, PieChart, MapChart } from "echarts/charts";
import { import {
GridComponent, GridComponent,
TooltipComponent, TooltipComponent,
LegendComponent, LegendComponent,
TitleComponent, TitleComponent,
VisualMapComponent, VisualMapComponent,
} from 'echarts/components'; } from "echarts/components";
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from "echarts/renderers";
import VChart from 'vue-echarts'; import VChart from "vue-echarts";
import { dashOpt1, dashOpt2, mapOptions } from './chart/options'; import { dashOpt1, dashOpt2, mapOptions } from "./chart/options";
import chinaMap from '@/utils/china'; import chinaMap from "@/utils/china";
use([ use([
CanvasRenderer, CanvasRenderer,
BarChart, BarChart,
@ -153,70 +159,70 @@ use([
VisualMapComponent, VisualMapComponent,
MapChart, MapChart,
]); ]);
registerMap('china', chinaMap); registerMap("china", chinaMap);
const activities = [ const activities = [
{ {
content: '收藏商品', content: "收藏商品",
description: 'xxx收藏了你的商品就是不买', description: "xxx收藏了你的商品就是不买",
timestamp: '30分钟前', timestamp: "30分钟前",
color: '#00bcd4', color: "#00bcd4",
}, },
{ {
content: '用户评价', content: "用户评价",
description: 'xxx给了某某商品一个差评吐血啊', description: "xxx给了某某商品一个差评吐血啊",
timestamp: '55分钟前', timestamp: "55分钟前",
color: '#1ABC9C', color: "#1ABC9C",
}, },
{ {
content: '订单提交', content: "订单提交",
description: 'xxx提交了订单快去收钱吧', description: "xxx提交了订单快去收钱吧",
timestamp: '1小时前', timestamp: "1小时前",
color: '#3f51b5', color: "#3f51b5",
}, },
{ {
content: '退款申请', content: "退款申请",
description: 'xxx申请了仅退款又要亏钱了', description: "xxx申请了仅退款又要亏钱了",
timestamp: '15小时前', timestamp: "15小时前",
color: '#f44336', color: "#f44336",
}, },
{ {
content: '商品上架', content: "商品上架",
description: '运营专员瞒着你上架了一辆飞机', description: "运营专员瞒着你上架了一辆飞机",
timestamp: '1天前', timestamp: "1天前",
color: '#009688', color: "#009688",
}, },
]; ];
const ranks = [ const ranks = [
{ {
title: '手机', title: "手机",
value: 10000, value: 10000,
percent: 80, percent: 80,
color: '#f25e43', color: "#f25e43",
}, },
{ {
title: '电脑', title: "电脑",
value: 8000, value: 8000,
percent: 70, percent: 70,
color: '#00bcd4', color: "#00bcd4",
}, },
{ {
title: '相机', title: "相机",
value: 6000, value: 6000,
percent: 60, percent: 60,
color: '#64d572', color: "#64d572",
}, },
{ {
title: '衣服', title: "衣服",
value: 5000, value: 5000,
percent: 55, percent: 55,
color: '#e9a745', color: "#e9a745",
}, },
{ {
title: '书籍', title: "书籍",
value: 4000, value: 4000,
percent: 50, percent: 50,
color: '#009688', color: "#009688",
}, },
]; ];
</script> </script>

View File

@ -5,7 +5,9 @@
<div>{{ data.date.getDate() }}</div> <div>{{ data.date.getDate() }}</div>
<div class="notes-container" v-if="notes[data.day.toString()]"> <div class="notes-container" v-if="notes[data.day.toString()]">
<div class="notes" v-for="note in notes[data.day.toString()]"> <div class="notes" v-for="note in notes[data.day.toString()]">
<span :class="note.status === 1 ? 'text-success' : 'text-danger'"></span> <span
:class="note.status === 1 ? 'text-success' : 'text-danger'"
></span>
<div class="note-title">{{ note.title }}</div> <div class="note-title">{{ note.title }}</div>
</div> </div>
</div> </div>
@ -15,7 +17,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from "vue";
const today = new Date(); const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000); const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
@ -26,14 +28,14 @@ const yesterdayDate = yesterday.toISOString().slice(0, 10);
const notes: any = { const notes: any = {
[todayDate]: [ [todayDate]: [
{ title: '吃饭', status: 1 }, { title: "吃饭", status: 1 },
{ title: '睡觉', status: 0 }, { title: "睡觉", status: 0 },
{ title: '吃饭', status: 1 }, { title: "吃饭", status: 1 },
{ title: '睡觉', status: 0 }, { title: "睡觉", status: 0 },
{ title: '吃饭', status: 1 }, { title: "吃饭", status: 1 },
{ title: '睡觉', status: 0 }, { title: "睡觉", status: 0 },
], ],
[yesterdayDate]: [{ title: '参加会议', status: 0 }], [yesterdayDate]: [{ title: "参加会议", status: 0 }],
}; };
</script> </script>

View File

@ -36,10 +36,10 @@
<script lang="ts" setup> <script lang="ts" setup>
const imgs = [ const imgs = [
'https://cdn.pixabay.com/photo/2017/08/07/08/23/sea-2601374_640.jpg', "https://cdn.pixabay.com/photo/2017/08/07/08/23/sea-2601374_640.jpg",
'https://cdn.pixabay.com/photo/2020/02/11/10/24/lake-4839058_640.jpg', "https://cdn.pixabay.com/photo/2020/02/11/10/24/lake-4839058_640.jpg",
'https://cdn.pixabay.com/photo/2024/02/21/08/06/coast-8587004_640.jpg', "https://cdn.pixabay.com/photo/2024/02/21/08/06/coast-8587004_640.jpg",
'https://cdn.pixabay.com/photo/2023/07/29/10/21/grasshopper-8156626_640.jpg', "https://cdn.pixabay.com/photo/2023/07/29/10/21/grasshopper-8156626_640.jpg",
]; ];
</script> </script>

View File

@ -5,34 +5,43 @@
<el-radio-button value="right">Right</el-radio-button> <el-radio-button value="right">Right</el-radio-button>
<el-radio-button value="top">Top</el-radio-button> <el-radio-button value="top">Top</el-radio-button>
</el-radio-group> </el-radio-group>
<el-form ref="formRef" :rules="rules" :model="form" label-width="120px" :label-position="labelPosition"> <el-form
ref="formRef"
:rules="rules"
:model="form"
label-width="120px"
:label-position="labelPosition"
>
<el-row :gutter="50"> <el-row :gutter="50">
<el-col :span="10"> <el-col :span="10">
<el-form-item label="文本框" prop="name"> <el-form-item label="文本框" prop="name">
<el-input v-model="form.name"></el-input> <el-input v-model="form.name" />
</el-form-item> </el-form-item>
<el-form-item label="数字框" prop="num"> <el-form-item label="数字框" prop="num">
<el-input-number v-model="form.num" :min="1" :max="10" /> <el-input-number v-model="form.num" :min="1" :max="10" />
</el-form-item> </el-form-item>
<el-form-item label="日期选择" prop="date"> <el-form-item label="日期选择" prop="date">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date"></el-date-picker> <el-date-picker
type="date"
placeholder="选择日期"
v-model="form.date"
/>
</el-form-item> </el-form-item>
<el-form-item label="时间选择" prop="time"> <el-form-item label="时间选择" prop="time">
<el-time-picker placeholder="选择时间" v-model="form.time"> <el-time-picker placeholder="选择时间" v-model="form.time" />
</el-time-picker>
</el-form-item> </el-form-item>
<el-form-item label="选择器" prop="region"> <el-form-item label="选择器" prop="region">
<el-select v-model="form.region" placeholder="请选择"> <el-select v-model="form.region" placeholder="请选择">
<el-option key="小明" label="小明" value="小明"></el-option> <el-option key="小明" label="小明" value="小明" />
<el-option key="小红" label="小红" value="小红"></el-option> <el-option key="小红" label="小红" value="小红" />
<el-option key="小白" label="小白" value="小白"></el-option> <el-option key="小白" label="小白" value="小白" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="城市级联" prop="options"> <el-form-item label="城市级联" prop="options">
<el-cascader :options="options" v-model="form.options"></el-cascader> <el-cascader :options="options" v-model="form.options" />
</el-form-item> </el-form-item>
<el-form-item label="文本框" prop="desc"> <el-form-item label="文本框" prop="desc">
<el-input type="textarea" rows="5" v-model="form.desc"></el-input> <el-input type="textarea" rows="5" v-model="form.desc" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -43,23 +52,23 @@
<el-slider v-model="form.num" :step="1" show-stops :max="10" /> <el-slider v-model="form.num" :step="1" show-stops :max="10" />
</el-form-item> </el-form-item>
<el-form-item label="开关" prop="delivery"> <el-form-item label="开关" prop="delivery">
<el-switch v-model="form.delivery"></el-switch> <el-switch v-model="form.delivery" />
</el-form-item> </el-form-item>
<el-form-item label="颜色选择" prop="color"> <el-form-item label="颜色选择" prop="color">
<el-color-picker v-model="form.color" /> <el-color-picker v-model="form.color" />
</el-form-item> </el-form-item>
<el-form-item label="多选框" prop="type"> <el-form-item label="多选框" prop="type">
<el-checkbox-group v-model="form.type"> <el-checkbox-group v-model="form.type">
<el-checkbox label="小明" value="小明" name="type"></el-checkbox> <el-checkbox label="小明" value="小明" name="type" />
<el-checkbox label="小红" value="小红" name="type"></el-checkbox> <el-checkbox label="小红" value="小红" name="type" />
<el-checkbox label="小白" value="小白" name="type"></el-checkbox> <el-checkbox label="小白" value="小白" name="type" />
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<el-form-item label="单选框" prop="resource"> <el-form-item label="单选框" prop="resource">
<el-radio-group v-model="form.resource"> <el-radio-group v-model="form.resource">
<el-radio label="小明" value="小明"></el-radio> <el-radio label="小明" value="小明" />
<el-radio label="小红" value="小红"></el-radio> <el-radio label="小红" value="小红" />
<el-radio label="小白" value="小白"></el-radio> <el-radio label="小白" value="小白" />
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="穿梭框" prop="transfer"> <el-form-item label="穿梭框" prop="transfer">
@ -69,7 +78,9 @@
<el-col :span="24"> <el-col :span="24">
<el-form-item> <el-form-item>
<el-button type="primary" @click="onSubmit(formRef)"></el-button> <el-button type="primary" @click="onSubmit(formRef)"
>表单提交</el-button
>
<el-button @click="onReset(formRef)"></el-button> <el-button @click="onReset(formRef)"></el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -79,56 +90,56 @@
</template> </template>
<script setup lang="ts" name="forms"> <script setup lang="ts" name="forms">
import { reactive, ref } from 'vue'; import { reactive, ref } from "vue";
import { ElMessage } from 'element-plus'; import { ElMessage } from "element-plus";
import type { FormInstance, FormProps, FormRules } from 'element-plus'; import type { FormInstance, FormProps, FormRules } from "element-plus";
const labelPosition = ref<FormProps['labelPosition']>('right') const labelPosition = ref<FormProps["labelPosition"]>("right");
const options = [ const options = [
{ {
value: 'guangdong', value: "guangdong",
label: '广东省', label: "广东省",
children: [ children: [
{ {
value: 'guangzhou', value: "guangzhou",
label: '广州市', label: "广州市",
children: [ children: [
{ {
value: 'tianhe', value: "tianhe",
label: '天河区', label: "天河区",
}, },
{ {
value: 'haizhu', value: "haizhu",
label: '海珠区', label: "海珠区",
}, },
], ],
}, },
{ {
value: 'dongguan', value: "dongguan",
label: '东莞市', label: "东莞市",
children: [ children: [
{ {
value: 'changan', value: "changan",
label: '长安镇', label: "长安镇",
}, },
{ {
value: 'humen', value: "humen",
label: '虎门镇', label: "虎门镇",
}, },
], ],
}, },
], ],
}, },
{ {
value: 'hunan', value: "hunan",
label: '湖南省', label: "湖南省",
children: [ children: [
{ {
value: 'changsha', value: "changsha",
label: '长沙市', label: "长沙市",
children: [ children: [
{ {
value: 'yuelu', value: "yuelu",
label: '岳麓区', label: "岳麓区",
}, },
], ],
}, },
@ -136,38 +147,37 @@ const options = [
}, },
]; ];
const rules: FormRules = { const rules: FormRules = {
name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }], name: [{ required: true, message: "请输入表单名称", trigger: "blur" }],
}; };
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const form = reactive({ const form = reactive({
name: '', name: "",
region: '', region: "",
date: '', date: "",
time: '', time: "",
delivery: true, delivery: true,
type: ['小明'], type: ["小明"],
resource: '小红', resource: "小红",
desc: '', desc: "",
options: [], options: [],
color: '', color: "",
num: 1, num: 1,
rate: 0, rate: 0,
transfer: [], transfer: [],
}); });
const generateData = () => { const generateData = () => {
const data = [] const data = [];
for (let i = 1; i <= 15; i++) { for (let i = 1; i <= 15; i++) {
data.push({ data.push({
key: i, key: i,
label: `Option ${i}`, label: `Option ${i}`,
disabled: i % 4 === 0, disabled: i % 4 === 0,
}) });
}
return data
} }
return data;
};
const transferData = ref(generateData()) const transferData = ref(generateData());
// //
const onSubmit = (formEl: FormInstance | undefined) => { const onSubmit = (formEl: FormInstance | undefined) => {
// //
@ -175,7 +185,7 @@ const onSubmit = (formEl: FormInstance | undefined) => {
formEl.validate((valid) => { formEl.validate((valid) => {
if (valid) { if (valid) {
console.log(form); console.log(form);
ElMessage.success('提交成功!'); ElMessage.success("提交成功!");
} else { } else {
return false; return false;
} }

View File

@ -1,7 +1,5 @@
<template> <template>
<div> <div>
<el-card class="mgb20" shadow="hover"> <el-card class="mgb20" shadow="hover">
<template #header>基础用法</template> <template #header>基础用法</template>
<el-row> <el-row>
@ -31,7 +29,9 @@
<template #header>CountUp.js</template> <template #header>CountUp.js</template>
<div class="plugins-tips"> <div class="plugins-tips">
countup.js用于快速创建以更有趣的方式显示数字数据的动画 访问地址 countup.js用于快速创建以更有趣的方式显示数字数据的动画 访问地址
<a href="https://github.com/inorganik/countUp.js" target="_blank">countUp.js</a> <a href="https://github.com/inorganik/countUp.js" target="_blank"
>countUp.js</a
>
</div> </div>
<el-row> <el-row>
<el-col :span="8" style="text-align: center"> <el-col :span="8" style="text-align: center">
@ -97,7 +97,11 @@
<el-col :span="6"> <el-col :span="6">
<el-card shadow="hover" body-class="card-body"> <el-card shadow="hover" body-class="card-body">
<div class="card-content text-left"> <div class="card-content text-left">
<el-statistic :value-style="{ color: '#2d8cf0' }" title="日活跃用户量" :value="268500" /> <el-statistic
:value-style="{ color: '#2d8cf0' }"
title="日活跃用户量"
:value="268500"
/>
</div> </div>
<el-icon class="card-icon color1"> <el-icon class="card-icon color1">
<User /> <User />
@ -107,7 +111,11 @@
<el-col :span="6"> <el-col :span="6">
<el-card shadow="hover" body-class="card-body"> <el-card shadow="hover" body-class="card-body">
<div class="card-content text-left"> <div class="card-content text-left">
<el-statistic :value-style="{ color: '#64d572' }" title="系统消息" :value="16800" /> <el-statistic
:value-style="{ color: '#64d572' }"
title="系统消息"
:value="16800"
/>
</div> </div>
<el-icon class="card-icon color2"> <el-icon class="card-icon color2">
<ChatDotRound /> <ChatDotRound />
@ -117,7 +125,11 @@
<el-col :span="6"> <el-col :span="6">
<el-card shadow="hover" body-class="card-body"> <el-card shadow="hover" body-class="card-body">
<div class="card-content text-left"> <div class="card-content text-left">
<el-statistic :value-style="{ color: '#f25e43' }" title="商品数量" :value="8888" /> <el-statistic
:value-style="{ color: '#f25e43' }"
title="商品数量"
:value="8888"
/>
</div> </div>
<el-icon class="card-icon color3"> <el-icon class="card-icon color3">
<Goods /> <Goods />
@ -127,7 +139,11 @@
<el-col :span="6"> <el-col :span="6">
<el-card shadow="hover" body-class="card-body"> <el-card shadow="hover" body-class="card-body">
<div class="card-content text-left"> <div class="card-content text-left">
<el-statistic :value-style="{ color: '#e9a745' }" title="今日订单量" :value="56888" /> <el-statistic
:value-style="{ color: '#e9a745' }"
title="今日订单量"
:value="56888"
/>
</div> </div>
<el-icon class="card-icon color4"> <el-icon class="card-icon color4">
<ShoppingCartFull /> <ShoppingCartFull />
@ -232,17 +248,17 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from "vue";
import { useTransition } from '@vueuse/core' import { useTransition } from "@vueuse/core";
import countup from '@/components/countup.vue'; import countup from "@/components/countup.vue";
const source = ref(0) const source = ref(0);
const outputValue = useTransition(source, { const outputValue = useTransition(source, {
duration: 1500, duration: 1500,
}) });
source.value = 172000 source.value = 172000;
const value = ref(Date.now() + 1000 * 60 * 60 * 7) const value = ref(Date.now() + 1000 * 60 * 60 * 7);
const value1 = ref(1000); const value1 = ref(1000);
setTimeout(() => { setTimeout(() => {
value1.value = 8000; value1.value = 8000;
@ -252,11 +268,11 @@ const options = {
decimalPlaces: 2, decimalPlaces: 2,
duration: 5, duration: 5,
useGrouping: false, useGrouping: false,
prefix: '$', prefix: "$",
separator: ',', separator: ",",
decimal: '.', decimal: ".",
suffix: '', suffix: "",
} };
</script> </script>
<style> <style>
@ -309,7 +325,6 @@ const options = {
color: #fff; color: #fff;
} }
.color0 { .color0 {
color: #fff; color: #fff;
} }

View File

@ -2,24 +2,35 @@
<div class="container"> <div class="container">
<div class="step-div" v-if="step === 0"> <div class="step-div" v-if="step === 0">
<p>输入注册时的邮箱我们会发送验证码到您的邮箱</p> <p>输入注册时的邮箱我们会发送验证码到您的邮箱</p>
<el-input placeholder="请输入邮箱"></el-input> <el-input placeholder="请输入邮箱" />
<el-button class="step-btn" type="primary" @click="step++"></el-button> <el-button class="step-btn" type="primary" @click="step++"
>下一步</el-button
>
</div> </div>
<div class="step-div" v-else-if="step === 1"> <div class="step-div" v-else-if="step === 1">
<p>验证码已发送至您的邮箱请输入验证码</p> <p>验证码已发送至您的邮箱请输入验证码</p>
<el-input placeholder="请输入验证码"></el-input> <el-input placeholder="请输入验证码" />
<el-button class="step-btn" type="primary" @click="step++"></el-button> <el-button class="step-btn" type="primary" @click="step++"
>下一步</el-button
>
</div> </div>
<div class="step-div" v-else-if="step === 2"> <div class="step-div" v-else-if="step === 2">
<p>请输入6位以上密码</p> <p>请输入6位以上密码</p>
<el-input placeholder="请输入新密码"></el-input> <el-input placeholder="请输入新密码" />
<el-button class="step-btn" type="primary" @click="step++"></el-button> <el-button class="step-btn" type="primary" @click="step++"
>保存</el-button
>
</div> </div>
<div v-else> <div v-else>
<el-result icon="success" title="保存成功" sub-title="退"></el-result> <el-result icon="success" title="保存成功" sub-title="退" />
</div> </div>
<el-steps class="step-style" :active="step" align-center finish-status="success"> <el-steps
class="step-style"
:active="step"
align-center
finish-status="success"
>
<el-step title="Step 1" description="填写邮箱" /> <el-step title="Step 1" description="填写邮箱" />
<el-step title="Step 2" description="填写验证码" /> <el-step title="Step 2" description="填写验证码" />
<el-step title="Step 3" description="修改密码" /> <el-step title="Step 3" description="修改密码" />
@ -33,8 +44,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from "vue";
const step = ref(0) const step = ref(0);
</script> </script>
<style scoped> <style scoped>

View File

@ -7,10 +7,12 @@
<span class="message-title">{{ scope.row.title }}</span> <span class="message-title">{{ scope.row.title }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="date" width="180"></el-table-column> <el-table-column prop="date" width="180" />
<el-table-column width="120"> <el-table-column width="120">
<template #default="scope"> <template #default="scope">
<el-button size="small" @click="handleRead(scope.$index)"></el-button> <el-button size="small" @click="handleRead(scope.$index)"
>标为已读</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -26,10 +28,15 @@
<span class="message-title">{{ scope.row.title }}</span> <span class="message-title">{{ scope.row.title }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="date" width="180"></el-table-column> <el-table-column prop="date" width="180" />
<el-table-column width="120"> <el-table-column width="120">
<template #default="scope"> <template #default="scope">
<el-button type="danger" size="small" @click="handleDel(scope.$index)"></el-button> <el-button
type="danger"
size="small"
@click="handleDel(scope.$index)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -40,16 +47,22 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third"> <el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
<template v-if="message === 'third'"> <template v-if="message === 'third'">
<el-table :data="state.recycle" :show-header="false" style="width: 100%"> <el-table
:data="state.recycle"
:show-header="false"
style="width: 100%"
>
<el-table-column> <el-table-column>
<template #default="scope"> <template #default="scope">
<span class="message-title">{{ scope.row.title }}</span> <span class="message-title">{{ scope.row.title }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="date" width="180"></el-table-column> <el-table-column prop="date" width="180" />
<el-table-column width="120"> <el-table-column width="120">
<template #default="scope"> <template #default="scope">
<el-button size="small" @click="handleRestore(scope.$index)"></el-button> <el-button size="small" @click="handleRestore(scope.$index)"
>还原</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -62,32 +75,32 @@
</template> </template>
<script setup lang="ts" name="tabs"> <script setup lang="ts" name="tabs">
import { ref, reactive } from 'vue'; import { ref, reactive } from "vue";
const message = ref('first'); const message = ref("first");
const state = reactive({ const state = reactive({
unread: [ unread: [
{ {
date: '2018-04-19 20:00:00', date: "2018-04-19 20:00:00",
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护' title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
}, },
{ {
date: '2018-04-19 21:00:00', date: "2018-04-19 21:00:00",
title: '今晚12点整发大红包先到先得' title: "今晚12点整发大红包先到先得",
} },
], ],
read: [ read: [
{ {
date: '2018-04-19 20:00:00', date: "2018-04-19 20:00:00",
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护' title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
} },
], ],
recycle: [ recycle: [
{ {
date: '2018-04-19 20:00:00', date: "2018-04-19 20:00:00",
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护' title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
} },
] ],
}); });
const handleRead = (index: number) => { const handleRead = (index: number) => {

View File

@ -12,22 +12,34 @@
<el-tour v-model="open"> <el-tour v-model="open">
<el-tour-step :target="ref1?.$el" title="上传文件"> <el-tour-step :target="ref1?.$el" title="上传文件">
<img style="width: 120px" src="../../assets/img/img.jpg" alt="tour.png" /> <img
style="width: 120px"
src="../../assets/img/img.jpg"
alt="tour.png"
/>
<div>点击这里选择文件</div> <div>点击这里选择文件</div>
</el-tour-step> </el-tour-step>
<el-tour-step :target="ref2?.$el" title="保存" description="点击进行上传" /> <el-tour-step
<el-tour-step :target="ref3?.$el" title="更多操作" description="点击查看更多操作" /> :target="ref2?.$el"
title="保存"
description="点击进行上传"
/>
<el-tour-step
:target="ref3?.$el"
title="更多操作"
description="点击查看更多操作"
/>
</el-tour> </el-tour>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from "vue";
import { MoreFilled } from '@element-plus/icons-vue' import { MoreFilled } from "@element-plus/icons-vue";
const ref1 = ref() const ref1 = ref();
const ref2 = ref() const ref2 = ref();
const ref3 = ref() const ref3 = ref();
const open = ref(false) const open = ref(false);
</script> </script>

View File

@ -3,10 +3,19 @@
<div class="content-title">支持拖拽</div> <div class="content-title">支持拖拽</div>
<div class="plugins-tips"> <div class="plugins-tips">
Element Plus自带上传组件 访问地址 Element Plus自带上传组件 访问地址
<a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a> <a
href="https://element-plus.org/zh-CN/component/upload.html"
target="_blank"
>Element Plus Upload</a
>
</div> </div>
<el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple <el-upload
:on-change="handle"> class="upload-demo"
drag
action="http://jsonplaceholder.typicode.com/api/posts/"
multiple
:on-change="handle"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon> <el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text"> <div class="el-upload__text">
将文件拖到此处 将文件拖到此处
@ -17,7 +26,9 @@
<div class="content-title">支持裁剪</div> <div class="content-title">支持裁剪</div>
<div class="plugins-tips"> <div class="plugins-tips">
vue-cropper一个简单的vue图片裁剪插件 访问地址 vue-cropper一个简单的vue图片裁剪插件 访问地址
<a href="https://github.com/xyxiao001/vue-cropper" target="_blank">vue-cropper</a> 示例请查看 <a href="https://github.com/xyxiao001/vue-cropper" target="_blank"
>vue-cropper</a
> 示例请查看
<router-link to="/ucenter">个人中心-我的头像</router-link> <router-link to="/ucenter">个人中心-我的头像</router-link>
</div> </div>
</div> </div>

View File

@ -2,13 +2,24 @@
<div class="container"> <div class="container">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="18"> <el-col :span="18">
<el-watermark :content="config.content" :font="config.font" :z-index="config.zIndex" <el-watermark
:rotate="config.rotate" :gap="config.gap" :offset="config.offset"> :content="config.content"
<div style="height: 600px" /> :font="config.font"
:z-index="config.zIndex"
:rotate="config.rotate"
:gap="config.gap"
:offset="config.offset"
>
<div style="height: 600px"></div>
</el-watermark> </el-watermark>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form class="form" :model="config" label-position="top" label-width="50px"> <el-form
class="form"
:model="config"
label-position="top"
label-width="50px"
>
<el-form-item label="Content"> <el-form-item label="Content">
<el-input v-model="config.content" /> <el-input v-model="config.content" />
</el-form-item> </el-form-item>
@ -26,37 +37,48 @@
</el-form-item> </el-form-item>
<el-form-item label="Gap"> <el-form-item label="Gap">
<el-space> <el-space>
<el-input-number v-model="config.gap[0]" controls-position="right" /> <el-input-number
<el-input-number v-model="config.gap[1]" controls-position="right" /> v-model="config.gap[0]"
controls-position="right"
/>
<el-input-number
v-model="config.gap[1]"
controls-position="right"
/>
</el-space> </el-space>
</el-form-item> </el-form-item>
<el-form-item label="Offset"> <el-form-item label="Offset">
<el-space> <el-space>
<el-input-number v-model="config.offset[0]" placeholder="offsetLeft" <el-input-number
controls-position="right" /> v-model="config.offset[0]"
<el-input-number v-model="config.offset[1]" placeholder="offsetTop" placeholder="offsetLeft"
controls-position="right" /> controls-position="right"
/>
<el-input-number
v-model="config.offset[1]"
placeholder="offsetTop"
controls-position="right"
/>
</el-space> </el-space>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from "vue";
const config = reactive({ const config = reactive({
content: 'vue-manage-system', content: "vue-manage-system",
font: { font: {
fontSize: 16, fontSize: 16,
color: 'rgba(0, 0, 0, 0.15)', color: "rgba(0, 0, 0, 0.15)",
}, },
zIndex: -1, zIndex: -1,
rotate: -22, rotate: -22,
gap: [100, 100] as [number, number], gap: [100, 100] as [number, number],
offset: [] as unknown as [number, number], offset: [] as unknown as [number, number],
}) });
</script> </script>

View File

@ -3,12 +3,12 @@
<v-header /> <v-header />
<v-sidebar /> <v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }"> <div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<v-tabs></v-tabs> <v-tabs />
<div class="content"> <div class="content">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition name="move" mode="out-in"> <transition name="move" mode="out-in">
<keep-alive :include="tabs.nameList"> <keep-alive :include="tabs.nameList">
<component :is="Component"></component> <component :is="Component" />
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
@ -17,11 +17,11 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useSidebarStore } from '@/store/sidebar'; import { useSidebarStore } from "@/store/sidebar";
import { useTabsStore } from '@/store/tabs'; import { useTabsStore } from "@/store/tabs";
import vHeader from '@/components/header.vue'; import vHeader from "@/components/header.vue";
import vSidebar from '@/components/sidebar.vue'; import vSidebar from "@/components/sidebar.vue";
import vTabs from '@/components/tabs.vue'; import vTabs from "@/components/tabs.vue";
const sidebar = useSidebarStore(); const sidebar = useSidebarStore();
const tabs = useTabsStore(); const tabs = useTabsStore();

View File

@ -7,14 +7,16 @@
<router-link to="/"> <router-link to="/">
<el-button type="primary" size="large">返回首页</el-button> <el-button type="primary" size="large">返回首页</el-button>
</router-link> </router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button> <el-button class="error-btn" size="large" @click="goBack"
>返回上一页</el-button
>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts" name="403"> <script setup lang="ts" name="403">
import { useRouter } from 'vue-router'; import { useRouter } from "vue-router";
const router = useRouter(); const router = useRouter();
const goBack = () => { const goBack = () => {

View File

@ -7,14 +7,16 @@
<router-link to="/"> <router-link to="/">
<el-button type="primary" size="large">返回首页</el-button> <el-button type="primary" size="large">返回首页</el-button>
</router-link> </router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button> <el-button class="error-btn" size="large" @click="goBack"
>返回上一页</el-button
>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts" name="404"> <script setup lang="ts" name="404">
import { useRouter } from 'vue-router'; import { useRouter } from "vue-router";
const router = useRouter(); const router = useRouter();
const goBack = () => { const goBack = () => {

View File

@ -5,12 +5,16 @@
<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a> <a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
</div> </div>
<div style="border: 1px solid #ccc; margin-bottom: 10px"> <div style="border: 1px solid #ccc; margin-bottom: 10px">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" /> <Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
/>
<Editor <Editor
style="height: 500px; overflow-y: hidden" style="height: 500px; overflow-y: hidden"
v-model="valueHtml" v-model="valueHtml"
:defaultConfig="editorConfig" :defaultConfig="editorConfig"
@onCreated="handleCreated" @on-created="handleCreated"
/> />
</div> </div>
<el-button type="primary" @click="syncHTML"></el-button> <el-button type="primary" @click="syncHTML"></el-button>
@ -18,24 +22,24 @@
</template> </template>
<script setup lang="ts" name="editor"> <script setup lang="ts" name="editor">
import '@wangeditor/editor/dist/css/style.css'; // css import "@wangeditor/editor/dist/css/style.css"; // css
import { onBeforeUnmount, ref, reactive, shallowRef, onMounted } from 'vue'; import { onBeforeUnmount, ref, reactive, shallowRef, onMounted } from "vue";
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'; import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
// shallowRef // shallowRef
const editorRef = shallowRef(); const editorRef = shallowRef();
// HTML // HTML
const valueHtml = ref('<p>hello</p>'); const valueHtml = ref("<p>hello</p>");
// ajax // ajax
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>'; valueHtml.value = "<p>模拟 Ajax 异步设置内容</p>";
}, 1500); }, 1500);
}); });
const toolbarConfig = {}; const toolbarConfig = {};
const editorConfig = { placeholder: '请输入内容...' }; const editorConfig = { placeholder: "请输入内容..." };
// //
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@ -1,27 +1,42 @@
<template> <template>
<el-tabs type="border-card"> <el-tabs type="border-card">
<el-tab-pane label="自定义图标"> <el-tab-pane label="自定义图标">
<h2>使用方法</h2> <h2>使用方法</h2>
<p style="line-height: 50px"> <p style="line-height: 50px">
直接通过设置类名为 el-icon-lx-iconName 来使用即可例如{{ iconList.length }}个图标 直接通过设置类名为 el-icon-lx-iconName 来使用即可例如{{
iconList.length
}}个图标
</p> </p>
<p class="example-p"> <p class="example-p">
<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i> <i
<span>&lt;i class=&quot;el-icon-lx-redpacket_fill&quot;&gt;&lt;/i&gt;</span> class="el-icon-lx-redpacket_fill"
style="font-size: 30px; color: #ff5900"
></i>
<span
>&lt;i class=&quot;el-icon-lx-redpacket_fill&quot;&gt;&lt;/i&gt;</span
>
</p> </p>
<p class="example-p"> <p class="example-p">
<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i> <i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
<span>&lt;i class=&quot;el-icon-lx-weibo&quot;&gt;&lt;/i&gt;</span> <span>&lt;i class=&quot;el-icon-lx-weibo&quot;&gt;&lt;/i&gt;</span>
</p> </p>
<p class="example-p"> <p class="example-p">
<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i> <i
class="el-icon-lx-emojifill"
style="font-size: 30px; color: #ffc300"
></i>
<span>&lt;i class=&quot;el-icon-lx-emojifill&quot;&gt;&lt;/i&gt;</span> <span>&lt;i class=&quot;el-icon-lx-emojifill&quot;&gt;&lt;/i&gt;</span>
</p> </p>
<br /> <br />
<h2>图标</h2> <h2>图标</h2>
<div class="search-box"> <div class="search-box">
<el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input> <el-input
class="search"
size="large"
v-model="keyword"
clearable
placeholder="请输入图标名称"
/>
</div> </div>
<ul> <ul>
<li class="icon-li" v-for="(item, index) in list" :key="index"> <li class="icon-li" v-for="(item, index) in list" :key="index">
@ -33,171 +48,175 @@
</ul> </ul>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Element图标"> <el-tab-pane label="Element图标">
<el-link type="primary" href="https://element-plus.org/zh-CN/component/icon.html#icon-collection" <el-link
target="_blank">前往官方文档查看</el-link> type="primary"
href="https://element-plus.org/zh-CN/component/icon.html#icon-collection"
target="_blank"
>前往官方文档查看</el-link
>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</template> </template>
<script setup lang="ts" name="icon"> <script setup lang="ts" name="icon">
import { computed, ref } from 'vue'; import { computed, ref } from "vue";
const iconList: Array<string> = [ const iconList: Array<string> = [
'attentionforbid', "attentionforbid",
'attentionforbidfill', "attentionforbidfill",
'attention', "attention",
'attentionfill', "attentionfill",
'tag', "tag",
'tagfill', "tagfill",
'people', "people",
'peoplefill', "peoplefill",
'notice', "notice",
'noticefill', "noticefill",
'mobile', "mobile",
'mobilefill', "mobilefill",
'voice', "voice",
'voicefill', "voicefill",
'unlock', "unlock",
'lock', "lock",
'home', "home",
'homefill', "homefill",
'delete', "delete",
'deletefill', "deletefill",
'notification', "notification",
'notificationfill', "notificationfill",
'notificationforbidfill', "notificationforbidfill",
'like', "like",
'likefill', "likefill",
'comment', "comment",
'commentfill', "commentfill",
'camera', "camera",
'camerafill', "camerafill",
'warn', "warn",
'warnfill', "warnfill",
'time', "time",
'timefill', "timefill",
'location', "location",
'locationfill', "locationfill",
'favor', "favor",
'favorfill', "favorfill",
'skin', "skin",
'skinfill', "skinfill",
'news', "news",
'newsfill', "newsfill",
'record', "record",
'recordfill', "recordfill",
'emoji', "emoji",
'emojifill', "emojifill",
'message', "message",
'messagefill', "messagefill",
'goods', "goods",
'goodsfill', "goodsfill",
'crown', "crown",
'crownfill', "crownfill",
'move', "move",
'add', "add",
'hot', "hot",
'hotfill', "hotfill",
'service', "service",
'servicefill', "servicefill",
'present', "present",
'presentfill', "presentfill",
'pic', "pic",
'picfill', "picfill",
'rank', "rank",
'rankfill', "rankfill",
'male', "male",
'female', "female",
'down', "down",
'top', "top",
'recharge', "recharge",
'rechargefill', "rechargefill",
'forward', "forward",
'forwardfill', "forwardfill",
'info', "info",
'infofill', "infofill",
'redpacket', "redpacket",
'redpacket_fill', "redpacket_fill",
'roundadd', "roundadd",
'roundaddfill', "roundaddfill",
'friendadd', "friendadd",
'friendaddfill', "friendaddfill",
'cart', "cart",
'cartfill', "cartfill",
'more', "more",
'moreandroid', "moreandroid",
'back', "back",
'right', "right",
'shop', "shop",
'shopfill', "shopfill",
'question', "question",
'questionfill', "questionfill",
'roundclose', "roundclose",
'roundclosefill', "roundclosefill",
'roundcheck', "roundcheck",
'roundcheckfill', "roundcheckfill",
'global', "global",
'mail', "mail",
'punch', "punch",
'exit', "exit",
'upload', "upload",
'read', "read",
'file', "file",
'link', "link",
'full', "full",
'group', "group",
'friend', "friend",
'profile', "profile",
'addressbook', "addressbook",
'calendar', "calendar",
'text', "text",
'copy', "copy",
'share', "share",
'wifi', "wifi",
'vipcard', "vipcard",
'weibo', "weibo",
'remind', "remind",
'refresh', "refresh",
'filter', "filter",
'settings', "settings",
'scan', "scan",
'qrcode', "qrcode",
'cascades', "cascades",
'apps', "apps",
'sort', "sort",
'searchlist', "searchlist",
'search', "search",
'edit', "edit",
'apple-line', "apple-line",
'baidu-fill', "baidu-fill",
'amazon-fill', "amazon-fill",
'netease-cloud-music-fill', "netease-cloud-music-fill",
'qq-line', "qq-line",
'wechat-fill', "wechat-fill",
'alipay-fill', "alipay-fill",
'android-fill', "android-fill",
'android-line', "android-line",
'whatsapp-line', "whatsapp-line",
'whatsapp-fill', "whatsapp-fill",
'bilibili-fill', "bilibili-fill",
'chrome-fill', "chrome-fill",
'dingding-fill', "dingding-fill",
'dingding-line', "dingding-line",
'apple-fill', "apple-fill",
'github-fill', "github-fill",
'qq-fill', "qq-fill",
'wechat-pay-fill', "wechat-pay-fill",
'windows-line', "windows-line",
'windows-fill', "windows-fill",
'youtube-line', "youtube-line",
'youtube-fill', "youtube-fill",
'wechat-pay-line', "wechat-pay-line",
'zhihu-line' "zhihu-line",
]; ];
const keyword = ref(''); const keyword = ref("");
const list = computed(() => { const list = computed(() => {
return iconList.filter(item => { return iconList.filter((item) => {
return item.indexOf(keyword.value) !== -1; return item.indexOf(keyword.value) !== -1;
}); });
}); });

View File

@ -30,13 +30,27 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<div class="pwd-tips"> <div class="pwd-tips">
<el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" /> <el-checkbox
<el-link type="primary" @click="$router.push('/reset-pwd')"></el-link> class="pwd-checkbox"
v-model="checked"
label="记住密码"
/>
<el-link type="primary" @click="$router.push('/reset-pwd')"
>忘记密码</el-link
>
</div> </div>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(login)"></el-button> <el-button
class="login-btn"
type="primary"
size="large"
@click="submitForm(login)"
>登录</el-button
>
<p class="login-tips">Tips : 用户名和密码随便填</p> <p class="login-tips">Tips : 用户名和密码随便填</p>
<p class="login-text"> <p class="login-text">
没有账号<el-link type="primary" @click="$router.push('/register')"></el-link> 没有账号<el-link type="primary" @click="$router.push('/register')"
>立即注册</el-link
>
</p> </p>
</el-form> </el-form>
</div> </div>
@ -44,37 +58,37 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue'; import { ref, reactive } from "vue";
import { useTabsStore } from '@/store/tabs'; import { useTabsStore } from "@/store/tabs";
import { usePermissStore } from '@/store/permiss'; import { usePermissStore } from "@/store/permiss";
import { useRouter } from 'vue-router'; import { useRouter } from "vue-router";
import { ElMessage } from 'element-plus'; import { ElMessage } from "element-plus";
import type { FormInstance, FormRules } from 'element-plus'; import type { FormInstance, FormRules } from "element-plus";
interface LoginInfo { interface LoginInfo {
username: string; username: string;
password: string; password: string;
} }
const lgStr = localStorage.getItem('login-param'); const lgStr = localStorage.getItem("login-param");
const defParam = lgStr ? JSON.parse(lgStr) : null; const defParam = lgStr ? JSON.parse(lgStr) : null;
const checked = ref(lgStr ? true : false); const checked = ref(lgStr ? true : false);
const router = useRouter(); const router = useRouter();
const param = reactive<LoginInfo>({ const param = reactive<LoginInfo>({
username: defParam ? defParam.username : '', username: defParam ? defParam.username : "",
password: defParam ? defParam.password : '', password: defParam ? defParam.password : "",
}); });
const rules: FormRules = { const rules: FormRules = {
username: [ username: [
{ {
required: true, required: true,
message: '请输入用户名', message: "请输入用户名",
trigger: 'blur', trigger: "blur",
}, },
], ],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }], password: [{ required: true, message: "请输入密码", trigger: "blur" }],
}; };
const permiss = usePermissStore(); const permiss = usePermissStore();
const login = ref<FormInstance>(); const login = ref<FormInstance>();
@ -82,18 +96,19 @@ const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate((valid: boolean) => { formEl.validate((valid: boolean) => {
if (valid) { if (valid) {
ElMessage.success('登录成功'); ElMessage.success("登录成功");
localStorage.setItem('vuems_name', param.username); localStorage.setItem("vuems_name", param.username);
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user']; const keys =
permiss.defaultList[param.username == "admin" ? "admin" : "user"];
permiss.handleSet(keys); permiss.handleSet(keys);
router.push('/'); router.push("/");
if (checked.value) { if (checked.value) {
localStorage.setItem('login-param', JSON.stringify(param)); localStorage.setItem("login-param", JSON.stringify(param));
} else { } else {
localStorage.removeItem('login-param'); localStorage.removeItem("login-param");
} }
} else { } else {
ElMessage.error('登录失败'); ElMessage.error("登录失败");
return false; return false;
} }
}); });

View File

@ -1,8 +1,11 @@
<template> <template>
<div class="container"> <div class="container">
<div class="plugins-tips"> <div class="plugins-tips">
md-editor-v3vue3版本的 markdown 编辑器配置丰富请详看文档 访问地址 md-editor-v3vue3版本的 markdown 编辑器配置丰富请详看文档
<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a> 访问地址
<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank"
>md-editor-v3</a
>
</div> </div>
<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" /> <md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
<el-button type="primary">提交</el-button> <el-button type="primary">提交</el-button>
@ -10,11 +13,11 @@
</template> </template>
<script setup lang="ts" name="md"> <script setup lang="ts" name="md">
import { ref } from 'vue'; import { ref } from "vue";
import MdEditor from 'md-editor-v3'; import MdEditor from "md-editor-v3";
import 'md-editor-v3/lib/style.css'; import "md-editor-v3/lib/style.css";
const text = ref('Hello Editor!'); const text = ref("Hello Editor!");
const onUploadImg = (files: any) => { const onUploadImg = (files: any) => {
console.log(files); console.log(files);
}; };

View File

@ -38,9 +38,17 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)"></el-button> <el-button
class="login-btn"
type="primary"
size="large"
@click="submitForm(register)"
>注册</el-button
>
<p class="login-text"> <p class="login-text">
已有账号<el-link type="primary" @click="$router.push('/login')"></el-link> 已有账号<el-link type="primary" @click="$router.push('/login')"
>立即登录</el-link
>
</p> </p>
</el-form> </el-form>
</div> </div>
@ -48,36 +56,36 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue'; import { ref, reactive } from "vue";
import { useRouter } from 'vue-router'; import { useRouter } from "vue-router";
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'; import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { Register } from '@/types/user'; import { Register } from "@/types/user";
const router = useRouter(); const router = useRouter();
const param = reactive<Register>({ const param = reactive<Register>({
username: '', username: "",
password: '', password: "",
email: '', email: "",
}); });
const rules: FormRules = { const rules: FormRules = {
username: [ username: [
{ {
required: true, required: true,
message: '请输入用户名', message: "请输入用户名",
trigger: 'blur', trigger: "blur",
}, },
], ],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }], password: [{ required: true, message: "请输入密码", trigger: "blur" }],
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }], email: [{ required: true, message: "请输入邮箱", trigger: "blur" }],
}; };
const register = ref<FormInstance>(); const register = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => { const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate((valid: boolean) => { formEl.validate((valid: boolean) => {
if (valid) { if (valid) {
ElMessage.success('注册成功,请登录'); ElMessage.success("注册成功,请登录");
router.push('/login'); router.push("/login");
} else { } else {
return false; return false;
} }

View File

@ -13,30 +13,38 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)" <el-button
class="login-btn"
type="primary"
size="large"
@click="submitForm(register)"
>发送邮件</el-button >发送邮件</el-button
> >
<p class="login-text"><el-link type="primary" @click="$router.push('/login')"></el-link></p> <p class="login-text">
<el-link type="primary" @click="$router.push('/login')"
>返回登录</el-link
>
</p>
</el-form> </el-form>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from "vue";
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'; import { ElMessage, type FormInstance, type FormRules } from "element-plus";
const param = ref({ const param = ref({
email: '', email: "",
}); });
const rules: FormRules = { const rules: FormRules = {
email: [ email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' }, { required: true, message: "请输入邮箱", trigger: "blur" },
{ {
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: '请输入正确的邮箱格式', message: "请输入正确的邮箱格式",
trigger: 'blur', trigger: "blur",
}, },
], ],
}; };
@ -45,7 +53,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate((valid: boolean) => { formEl.validate((valid: boolean) => {
if (valid) { if (valid) {
ElMessage.success('邮件已发送,请注意查收'); ElMessage.success("邮件已发送,请注意查收");
} else { } else {
return false; return false;
} }

View File

@ -5,8 +5,13 @@
<div class="content-title">系统主题</div> <div class="content-title">系统主题</div>
</template> </template>
<div class="theme-list mgb20"> <div class="theme-list mgb20">
<div class="theme-item" @click="setSystemTheme(item)" v-for="item in system" <div
:style="{ backgroundColor: item.color, color: '#fff' }">{{ item.name }} class="theme-item"
@click="setSystemTheme(item)"
v-for="item in system"
:style="{ backgroundColor: item.color, color: '#fff' }"
>
{{ item.name }}
</div> </div>
</div> </div>
<div class="flex-center"> <div class="flex-center">
@ -21,7 +26,10 @@
<div class="theme-item" v-for="theme in themes"> <div class="theme-item" v-for="theme in themes">
<el-button :type="theme.name">{{ theme.name }}</el-button> <el-button :type="theme.name">{{ theme.name }}</el-button>
<div class="theme-color">{{ theme.color }}</div> <div class="theme-color">{{ theme.color }}</div>
<el-color-picker v-model="color[theme.name]" @change="changeColor(theme.name)" /> <el-color-picker
v-model="color[theme.name]"
@change="changeColor(theme.name)"
/>
</div> </div>
</div> </div>
<div class="flex-center"> <div class="flex-center">
@ -39,14 +47,18 @@
<div class="theme-item"> <div class="theme-item">
<el-button :color="color.headerBgColor">背景颜色</el-button> <el-button :color="color.headerBgColor">背景颜色</el-button>
<div class="theme-color">{{ color.headerBgColor }}</div> <div class="theme-color">{{ color.headerBgColor }}</div>
<el-color-picker v-model="color.headerBgColor" <el-color-picker
@change="themeStore.setHeaderBgColor(color.headerBgColor)" /> v-model="color.headerBgColor"
@change="themeStore.setHeaderBgColor(color.headerBgColor)"
/>
</div> </div>
<div class="theme-item"> <div class="theme-item">
<el-button :color="color.headerTextColor">文字颜色</el-button> <el-button :color="color.headerTextColor">文字颜色</el-button>
<div class="theme-color">{{ color.headerTextColor }}</div> <div class="theme-color">{{ color.headerTextColor }}</div>
<el-color-picker v-model="color.headerTextColor" <el-color-picker
@change="themeStore.setHeaderTextColor(color.headerTextColor)" /> v-model="color.headerTextColor"
@change="themeStore.setHeaderTextColor(color.headerTextColor)"
/>
</div> </div>
</div> </div>
<div class="flex-center"> <div class="flex-center">
@ -64,14 +76,18 @@
<div class="theme-item"> <div class="theme-item">
<el-button :color="sidebar.bgColor">背景颜色</el-button> <el-button :color="sidebar.bgColor">背景颜色</el-button>
<div class="theme-color">{{ sidebar.bgColor }}</div> <div class="theme-color">{{ sidebar.bgColor }}</div>
<el-color-picker v-model="sidebarColor.bgColor" <el-color-picker
@change="sidebar.setBgColor(sidebarColor.bgColor)" /> v-model="sidebarColor.bgColor"
@change="sidebar.setBgColor(sidebarColor.bgColor)"
/>
</div> </div>
<div class="theme-item"> <div class="theme-item">
<el-button :color="sidebar.textColor">文字颜色</el-button> <el-button :color="sidebar.textColor">文字颜色</el-button>
<div class="theme-color">{{ sidebar.textColor }}</div> <div class="theme-color">{{ sidebar.textColor }}</div>
<el-color-picker v-model="sidebarColor.textColor" <el-color-picker
@change="sidebar.setTextColor(sidebarColor.textColor)" /> v-model="sidebarColor.textColor"
@change="sidebar.setTextColor(sidebarColor.textColor)"
/>
</div> </div>
</div> </div>
<div class="flex-center"> <div class="flex-center">
@ -80,108 +96,107 @@
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useSidebarStore } from '@/store/sidebar'; import { useSidebarStore } from "@/store/sidebar";
import { useThemeStore } from '@/store/theme' import { useThemeStore } from "@/store/theme";
import { reactive } from 'vue'; import { reactive } from "vue";
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const sidebar = useSidebarStore(); const sidebar = useSidebarStore();
const color = reactive({ const color = reactive({
primary: localStorage.getItem('theme-primary') || '#409eff', primary: localStorage.getItem("theme-primary") || "#409eff",
success: localStorage.getItem('theme-success') || '#67c23a', success: localStorage.getItem("theme-success") || "#67c23a",
warning: localStorage.getItem('theme-warning') || '#e6a23c', warning: localStorage.getItem("theme-warning") || "#e6a23c",
danger: localStorage.getItem('theme-danger') || '#f56c6c', danger: localStorage.getItem("theme-danger") || "#f56c6c",
info: localStorage.getItem('theme-info') || '#909399', info: localStorage.getItem("theme-info") || "#909399",
headerBgColor: themeStore.headerBgColor, headerBgColor: themeStore.headerBgColor,
headerTextColor: themeStore.headerTextColor, headerTextColor: themeStore.headerTextColor,
}) });
const sidebarColor = reactive({ const sidebarColor = reactive({
bgColor: sidebar.bgColor, bgColor: sidebar.bgColor,
textColor: sidebar.textColor textColor: sidebar.textColor,
}) });
const themes = [ const themes = [
{ {
name: 'primary', name: "primary",
color: themeStore.primary || color.primary color: themeStore.primary || color.primary,
}, },
{ {
name: 'success', name: "success",
color: themeStore.success || color.success color: themeStore.success || color.success,
}, },
{ {
name: 'warning', name: "warning",
color: themeStore.warning || color.warning color: themeStore.warning || color.warning,
}, },
{ {
name: 'danger', name: "danger",
color: themeStore.danger || color.danger color: themeStore.danger || color.danger,
}, },
{ {
name: 'info', name: "info",
color: themeStore.info || color.info color: themeStore.info || color.info,
} },
] ];
const changeColor = (name: string) => { const changeColor = (name: string) => {
themeStore.setPropertyColor(color[name], name) themeStore.setPropertyColor(color[name], name);
} };
const resetTheme = () => { const resetTheme = () => {
themeStore.resetTheme() themeStore.resetTheme();
} };
const resetHeader = () => { const resetHeader = () => {
localStorage.removeItem('header-bg-color') localStorage.removeItem("header-bg-color");
localStorage.removeItem('header-text-color') localStorage.removeItem("header-text-color");
location.reload() location.reload();
} };
const resetSidebar = () => { const resetSidebar = () => {
localStorage.removeItem('sidebar-bg-color') localStorage.removeItem("sidebar-bg-color");
localStorage.removeItem('sidebar-text-color') localStorage.removeItem("sidebar-text-color");
location.reload() location.reload();
} };
const system = [ const system = [
{ {
name: '默认', name: "默认",
color: '#242f42' color: "#242f42",
}, },
{ {
name: '健康', name: "健康",
color: '#1ABC9C' color: "#1ABC9C",
}, },
{ {
name: '优雅', name: "优雅",
color: '#722ed1' color: "#722ed1",
}, },
{ {
name: '热情', name: "热情",
color: '#f44336' color: "#f44336",
}, },
{ {
name: '宁静', name: "宁静",
color: '#00bcd4' color: "#00bcd4",
} },
] ];
const setSystemTheme = (data: any) => { const setSystemTheme = (data: any) => {
if (data.name === '默认') { if (data.name === "默认") {
resetSystemTheme() resetSystemTheme();
} else { } else {
themeStore.setHeaderBgColor(data.color) themeStore.setHeaderBgColor(data.color);
themeStore.setHeaderTextColor('#fff') themeStore.setHeaderTextColor("#fff");
sidebar.setBgColor('#fff') sidebar.setBgColor("#fff");
sidebar.setTextColor('#5b6e88') sidebar.setTextColor("#5b6e88");
themeStore.setPropertyColor(data.color, 'primary') themeStore.setPropertyColor(data.color, "primary");
}
} }
};
const resetSystemTheme = () => { const resetSystemTheme = () => {
resetTheme(); resetTheme();
resetHeader(); resetHeader();
resetSidebar(); resetSidebar();
} };
</script> </script>
<style scoped> <style scoped>

View File

@ -1,7 +1,11 @@
<template> <template>
<div> <div>
<div class="user-container"> <div class="user-container">
<el-card class="user-profile" shadow="hover" :body-style="{ padding: '0px' }"> <el-card
class="user-profile"
shadow="hover"
:body-style="{ padding: '0px' }"
>
<div class="user-profile-bg"></div> <div class="user-profile-bg"></div>
<div class="user-avatar-wrap"> <div class="user-avatar-wrap">
<el-avatar class="user-avatar" :size="120" :src="avatarImg" /> <el-avatar class="user-avatar" :size="120" :src="avatarImg" />
@ -11,11 +15,15 @@
<div class="info-desc"> <div class="info-desc">
<span>@lin-xin</span> <span>@lin-xin</span>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
<el-link href="https://lin-xin.gitee.io" target="_blank">lin-xin.gitee.io</el-link> <el-link href="https://lin-xin.gitee.io" target="_blank"
>lin-xin.gitee.io</el-link
>
</div> </div>
<div class="info-desc">FE Developer</div> <div class="info-desc">FE Developer</div>
<div class="info-icon"> <div class="info-icon">
<a href="https://github.com/lin-xin" target="_blank"> <i class="el-icon-lx-github-fill"></i></a> <a href="https://github.com/lin-xin" target="_blank">
<i class="el-icon-lx-github-fill"></i
></a>
<i class="el-icon-lx-qq-fill"></i> <i class="el-icon-lx-qq-fill"></i>
<i class="el-icon-lx-facebook-fill"></i> <i class="el-icon-lx-facebook-fill"></i>
<i class="el-icon-lx-twitter-fill"></i> <i class="el-icon-lx-twitter-fill"></i>
@ -36,7 +44,11 @@
<el-card <el-card
class="user-content" class="user-content"
shadow="hover" shadow="hover"
:body-style="{ padding: '20px 50px', height: '100%', boxSizing: 'border-box' }" :body-style="{
padding: '20px 50px',
height: '100%',
boxSizing: 'border-box',
}"
> >
<el-tabs tab-position="left" v-model="activeName"> <el-tabs tab-position="left" v-model="activeName">
<el-tab-pane name="label1" label="消息通知" class="user-tabpane"> <el-tab-pane name="label1" label="消息通知" class="user-tabpane">
@ -51,25 +63,30 @@
:centerBox="true" :centerBox="true"
:full="true" :full="true"
mode="contain" mode="contain"
> />
</vueCropper>
</div> </div>
<el-button class="crop-demo-btn" type="primary" <el-button class="crop-demo-btn" type="primary"
>选择图片 >选择图片
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" /> <input
class="crop-input"
type="file"
name="image"
accept="image/*"
@change="setImage"
/>
</el-button> </el-button>
<el-button type="success" @click="saveAvatar"></el-button> <el-button type="success" @click="saveAvatar"></el-button>
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="label3" label="修改密码" class="user-tabpane"> <el-tab-pane name="label3" label="修改密码" class="user-tabpane">
<el-form class="w500" label-position="top"> <el-form class="w500" label-position="top">
<el-form-item label="旧密码:"> <el-form-item label="旧密码:">
<el-input type="password" v-model="form.old"></el-input> <el-input type="password" v-model="form.old" />
</el-form-item> </el-form-item>
<el-form-item label="新密码:"> <el-form-item label="新密码:">
<el-input type="password" v-model="form.new"></el-input> <el-input type="password" v-model="form.new" />
</el-form-item> </el-form-item>
<el-form-item label="确认新密码:"> <el-form-item label="确认新密码:">
<el-input type="password" v-model="form.new1"></el-input> <el-input type="password" v-model="form.new1" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="onSubmit"></el-button> <el-button type="primary" @click="onSubmit"></el-button>
@ -79,7 +96,9 @@
<el-tab-pane name="label4" label="赞赏作者" class="user-tabpane"> <el-tab-pane name="label4" label="赞赏作者" class="user-tabpane">
<div class="plugins-tips"> <div class="plugins-tips">
如果该框架 如果该框架
<el-link href="https://github.com/lin-xin/vue-manage-system" target="_blank" <el-link
href="https://github.com/lin-xin/vue-manage-system"
target="_blank"
>vue-manage-system</el-link >vue-manage-system</el-link
> >
对你有帮助那就请作者喝杯饮料吧<el-icon> 对你有帮助那就请作者喝杯饮料吧<el-icon>
@ -98,30 +117,30 @@
</template> </template>
<script setup lang="ts" name="ucenter"> <script setup lang="ts" name="ucenter">
import { reactive, ref } from 'vue'; import { reactive, ref } from "vue";
import { VueCropper } from 'vue-cropper'; import { VueCropper } from "vue-cropper";
import 'vue-cropper/dist/index.css'; import "vue-cropper/dist/index.css";
import avatar from '@/assets/img/img.jpg'; import avatar from "@/assets/img/img.jpg";
import TabsComp from '../element/tabs.vue'; import TabsComp from "../element/tabs.vue";
const name = localStorage.getItem('vuems_name'); const name = localStorage.getItem("vuems_name");
const form = reactive({ const form = reactive({
new1: '', new1: "",
new: '', new: "",
old: '', old: "",
}); });
const onSubmit = () => {}; const onSubmit = () => {};
const activeName = ref('label1'); const activeName = ref("label1");
const avatarImg = ref(avatar); const avatarImg = ref(avatar);
const imgSrc = ref(avatar); const imgSrc = ref(avatar);
const cropImg = ref(''); const cropImg = ref("");
const cropper: any = ref(); const cropper: any = ref();
const setImage = (e: any) => { const setImage = (e: any) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (!file.type.includes('image/')) { if (!file.type.includes("image/")) {
return; return;
} }
const reader = new FileReader(); const reader = new FileReader();
@ -153,7 +172,7 @@ const saveAvatar = () => {
.user-profile-bg { .user-profile-bg {
width: 100%; width: 100%;
height: 200px; height: 200px;
background-image: url('../../assets/img/ucenter-bg.jpg'); background-image: url("../../assets/img/ucenter-bg.jpg");
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;

View File

@ -1,33 +1,64 @@
<template> <template>
<div> <div>
<div class="container"> <div class="container">
<TableCustom :columns="columns" :tableData="menuData" row-key="index" :has-pagination="false" <TableCustom
:viewFunc="handleView" :delFunc="handleDelete" :editFunc="handleEdit"> :columns="columns"
:tableData="menuData"
row-key="index"
:has-pagination="false"
:viewFunc="handleView"
:delFunc="handleDelete"
:editFunc="handleEdit"
>
<template #toolbarBtn> <template #toolbarBtn>
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button> <el-button
type="warning"
:icon="CirclePlusFilled"
@click="visible = true"
>新增</el-button
>
</template> </template>
<template #icon="{ rows }"> <template #icon="{ rows }">
<el-icon> <el-icon>
<component :is="rows.icon"></component> <component :is="rows.icon" />
</el-icon> </el-icon>
</template> </template>
</TableCustom> </TableCustom>
</div> </div>
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close <el-dialog
:close-on-click-modal="false" @close="closeDialog"> :title="isEdit ? '编辑' : '新增'"
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData"> v-model="visible"
width="700px"
destroy-on-close
:close-on-click-modal="false"
@close="closeDialog"
>
<TableEdit
:form-data="rowData"
:options="options"
:edit="isEdit"
:update="updateData"
>
<template #parent> <template #parent>
<el-cascader v-model="rowData.pid" :options="cascaderOptions" :props="{ checkStrictly: true }" <el-cascader
clearable /> v-model="rowData.pid"
:options="cascaderOptions"
:props="{ checkStrictly: true }"
clearable
/>
</template> </template>
</TableEdit> </TableEdit>
</el-dialog> </el-dialog>
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close> <el-dialog
title="查看详情"
v-model="visible1"
width="700px"
destroy-on-close
>
<TableDetail :data="viewData"> <TableDetail :data="viewData">
<template #icon="{ rows }"> <template #icon="{ rows }">
<el-icon> <el-icon>
<component :is="rows.icon"></component> <component :is="rows.icon" />
</el-icon> </el-icon>
</template> </template>
</TableDetail> </TableDetail>
@ -36,51 +67,50 @@
</template> </template>
<script setup lang="ts" name="system-menu"> <script setup lang="ts" name="system-menu">
import { ref } from 'vue'; import { ref } from "vue";
import { ElMessage } from 'element-plus'; import { ElMessage } from "element-plus";
import { CirclePlusFilled } from '@element-plus/icons-vue'; import { CirclePlusFilled } from "@element-plus/icons-vue";
import { Menus } from '@/types/menu'; import { Menus } from "@/types/menu";
import TableCustom from '@/components/table-custom.vue'; import TableCustom from "@/components/table-custom.vue";
import TableDetail from '@/components/table-detail.vue'; import TableDetail from "@/components/table-detail.vue";
import { FormOption } from '@/types/form-option'; import { FormOption } from "@/types/form-option";
import { menuData } from '@/components/menu'; import { menuData } from "@/components/menu";
// //
let columns = ref([ let columns = ref([
{ prop: 'title', label: '菜单名称', align: 'left' }, { prop: "title", label: "菜单名称", align: "left" },
{ prop: 'icon', label: '图标' }, { prop: "icon", label: "图标" },
{ prop: 'index', label: '路由路径' }, { prop: "index", label: "路由路径" },
{ prop: 'permiss', label: '权限标识' }, { prop: "permiss", label: "权限标识" },
{ prop: 'operator', label: '操作', width: 250 }, { prop: "operator", label: "操作", width: 250 },
]) ]);
const getOptions = (data: any) => { const getOptions = (data: any) => {
return data.map(item => { return data.map((item) => {
const a: any = { const a: any = {
label: item.title, label: item.title,
value: item.id, value: item.id,
} };
if (item.children) { if (item.children) {
a.children = getOptions(item.children) a.children = getOptions(item.children);
}
return a
})
} }
return a;
});
};
const cascaderOptions = ref(getOptions(menuData)); const cascaderOptions = ref(getOptions(menuData));
// / // /
let options = ref<FormOption>({ let options = ref<FormOption>({
labelWidth: '100px', labelWidth: "100px",
span: 12, span: 12,
list: [ list: [
{ type: 'input', label: '菜单名称', prop: 'title', required: true }, { type: "input", label: "菜单名称", prop: "title", required: true },
{ type: 'input', label: '路由路径', prop: 'index', required: true }, { type: "input", label: "路由路径", prop: "index", required: true },
{ type: 'input', label: '图标', prop: 'icon' }, { type: "input", label: "图标", prop: "icon" },
{ type: 'input', label: '权限标识', prop: 'permiss' }, { type: "input", label: "权限标识", prop: "permiss" },
{ type: 'parent', label: '父菜单', prop: 'parent' }, { type: "parent", label: "父菜单", prop: "parent" },
] ],
}) });
const visible = ref(false); const visible = ref(false);
const isEdit = ref(false); const isEdit = ref(false);
const rowData = ref<any>({}); const rowData = ref<any>({});
@ -102,43 +132,43 @@ const closeDialog = () => {
const visible1 = ref(false); const visible1 = ref(false);
const viewData = ref({ const viewData = ref({
row: {}, row: {},
list: [] list: [],
}); });
const handleView = (row: Menus) => { const handleView = (row: Menus) => {
viewData.value.row = { ...row } viewData.value.row = { ...row };
viewData.value.list = [ viewData.value.list = [
{ {
prop: 'id', prop: "id",
label: '菜单ID', label: "菜单ID",
}, },
{ {
prop: 'pid', prop: "pid",
label: '父菜单ID', label: "父菜单ID",
}, },
{ {
prop: 'title', prop: "title",
label: '菜单名称', label: "菜单名称",
}, },
{ {
prop: 'index', prop: "index",
label: '路由路径', label: "路由路径",
}, },
{ {
prop: 'permiss', prop: "permiss",
label: '权限标识', label: "权限标识",
}, },
{ {
prop: 'icon', prop: "icon",
label: '图标', label: "图标",
}, },
] ];
visible1.value = true; visible1.value = true;
}; };
// //
const handleDelete = (row: Menus) => { const handleDelete = (row: Menus) => {
ElMessage.success('删除成功'); ElMessage.success("删除成功");
} };
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -14,9 +14,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from "vue";
import { ElTree } from 'element-plus'; import { ElTree } from "element-plus";
import { menuData } from '@/components/menu'; import { menuData } from "@/components/menu";
const props = defineProps({ const props = defineProps({
permissOptions: { permissOptions: {
@ -58,7 +58,10 @@ const getTreeData = (data) => {
const data = getTreeData(menuData); const data = getTreeData(menuData);
const checkData = (data: string[]) => { const checkData = (data: string[]) => {
return data.filter((item) => { return data.filter((item) => {
return !menuObj.value[item] || data.toString().includes(menuObj.value[item].toString()); return (
!menuObj.value[item] ||
data.toString().includes(menuObj.value[item].toString())
);
}); });
}; };
// //
@ -68,7 +71,10 @@ const checkedKeys = ref<string[]>(checkData(props.permissOptions.permiss));
const tree = ref<InstanceType<typeof ElTree>>(); const tree = ref<InstanceType<typeof ElTree>>();
const onSubmit = () => { const onSubmit = () => {
// //
const keys = [...tree.value!.getCheckedKeys(false), ...tree.value!.getHalfCheckedKeys()] as number[]; const keys = [
...tree.value!.getCheckedKeys(false),
...tree.value!.getHalfCheckedKeys(),
] as number[];
console.log(keys); console.log(keys);
}; };
</script> </script>

View File

@ -2,26 +2,59 @@
<div> <div>
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" /> <TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
<div class="container"> <div class="container">
<TableCustom
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView" :columns="columns"
:delFunc="handleDelete" :page-change="changePage" :editFunc="handleEdit"> :tableData="tableData"
:total="page.total"
:viewFunc="handleView"
:delFunc="handleDelete"
:page-change="changePage"
:editFunc="handleEdit"
>
<template #toolbarBtn> <template #toolbarBtn>
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button> <el-button
type="warning"
:icon="CirclePlusFilled"
@click="visible = true"
>新增</el-button
>
</template> </template>
<template #status="{ rows }"> <template #status="{ rows }">
<el-tag type="success" v-if="rows.status"></el-tag> <el-tag type="success" v-if="rows.status"></el-tag>
<el-tag type="danger" v-else></el-tag> <el-tag type="danger" v-else></el-tag>
</template> </template>
<template #permissions="{ rows }"> <template #permissions="{ rows }">
<el-button type="primary" size="small" plain @click="handlePermission(rows)"></el-button> <el-button
type="primary"
size="small"
plain
@click="handlePermission(rows)"
>管理</el-button
>
</template> </template>
</TableCustom> </TableCustom>
</div> </div>
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close <el-dialog
:close-on-click-modal="false" @close="closeDialog"> :title="isEdit ? '编辑' : '新增'"
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" /> v-model="visible"
width="700px"
destroy-on-close
:close-on-click-modal="false"
@close="closeDialog"
>
<TableEdit
:form-data="rowData"
:options="options"
:edit="isEdit"
:update="updateData"
/>
</el-dialog> </el-dialog>
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close> <el-dialog
title="查看详情"
v-model="visible1"
width="700px"
destroy-on-close
>
<TableDetail :data="viewData"> <TableDetail :data="viewData">
<template #status="{ rows }"> <template #status="{ rows }">
<el-tag type="success" v-if="rows.status"></el-tag> <el-tag type="success" v-if="rows.status"></el-tag>
@ -29,51 +62,56 @@
</template> </template>
</TableDetail> </TableDetail>
</el-dialog> </el-dialog>
<el-dialog title="权限管理" v-model="visible2" width="500px" destroy-on-close> <el-dialog
title="权限管理"
v-model="visible2"
width="500px"
destroy-on-close
>
<RolePermission :permiss-options="permissOptions" /> <RolePermission :permiss-options="permissOptions" />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="system-role"> <script setup lang="ts" name="system-role">
import { ref, reactive } from 'vue'; import { ref, reactive } from "vue";
import { ElMessage } from 'element-plus'; import { ElMessage } from "element-plus";
import { Role } from '@/types/role'; import { Role } from "@/types/role";
import { fetchRoleData } from '@/api'; import { fetchRoleData } from "@/api";
import TableCustom from '@/components/table-custom.vue'; import TableCustom from "@/components/table-custom.vue";
import TableDetail from '@/components/table-detail.vue'; import TableDetail from "@/components/table-detail.vue";
import RolePermission from './role-permission.vue' import RolePermission from "./role-permission.vue";
import { CirclePlusFilled } from '@element-plus/icons-vue'; import { CirclePlusFilled } from "@element-plus/icons-vue";
import { FormOption, FormOptionList } from '@/types/form-option'; import { FormOption, FormOptionList } from "@/types/form-option";
// //
const query = reactive({ const query = reactive({
name: '', name: "",
}); });
const searchOpt = ref<FormOptionList[]>([ const searchOpt = ref<FormOptionList[]>([
{ type: 'input', label: '角色名称:', prop: 'name' } { type: "input", label: "角色名称:", prop: "name" },
]) ]);
const handleSearch = () => { const handleSearch = () => {
changePage(1); changePage(1);
}; };
// //
let columns = ref([ let columns = ref([
{ type: 'index', label: '序号', width: 55, align: 'center' }, { type: "index", label: "序号", width: 55, align: "center" },
{ prop: 'name', label: '角色名称' }, { prop: "name", label: "角色名称" },
{ prop: 'key', label: '角色标识' }, { prop: "key", label: "角色标识" },
{ prop: 'status', label: '状态' }, { prop: "status", label: "状态" },
{ prop: 'permissions', label: '权限管理' }, { prop: "permissions", label: "权限管理" },
{ prop: 'operator', label: '操作', width: 250 }, { prop: "operator", label: "操作", width: 250 },
]) ]);
const page = reactive({ const page = reactive({
index: 1, index: 1,
size: 10, size: 10,
total: 0, total: 0,
}) });
const tableData = ref<Role[]>([]); const tableData = ref<Role[]>([]);
const getData = async () => { const getData = async () => {
const res = await fetchRoleData() const res = await fetchRoleData();
tableData.value = res.data.list; tableData.value = res.data.list;
page.total = res.data.pageTotal; page.total = res.data.pageTotal;
}; };
@ -85,14 +123,21 @@ const changePage = (val: number) => {
// / // /
const options = ref<FormOption>({ const options = ref<FormOption>({
labelWidth: '100px', labelWidth: "100px",
span: 24, span: 24,
list: [ list: [
{ type: 'input', label: '角色名称', prop: 'name', required: true }, { type: "input", label: "角色名称", prop: "name", required: true },
{ type: 'input', label: '角色标识', prop: 'key', required: true }, { type: "input", label: "角色标识", prop: "key", required: true },
{ type: 'switch', label: '状态', prop: 'status', required: false, activeText: '启用', inactiveText: '禁用' }, {
] type: "switch",
}) label: "状态",
prop: "status",
required: false,
activeText: "启用",
inactiveText: "禁用",
},
],
});
const visible = ref(false); const visible = ref(false);
const isEdit = ref(false); const isEdit = ref(false);
const rowData = ref({}); const rowData = ref({});
@ -116,47 +161,46 @@ const visible1 = ref(false);
const viewData = ref({ const viewData = ref({
row: {}, row: {},
list: [], list: [],
column: 1 column: 1,
}); });
const handleView = (row: Role) => { const handleView = (row: Role) => {
viewData.value.row = { ...row } viewData.value.row = { ...row };
viewData.value.list = [ viewData.value.list = [
{ {
prop: 'id', prop: "id",
label: '角色ID', label: "角色ID",
}, },
{ {
prop: 'name', prop: "name",
label: '角色名称', label: "角色名称",
}, },
{ {
prop: 'key', prop: "key",
label: '角色标识', label: "角色标识",
}, },
{ {
prop: 'status', prop: "status",
label: '角色状态', label: "角色状态",
}, },
] ];
visible1.value = true; visible1.value = true;
}; };
// //
const handleDelete = (row: Role) => { const handleDelete = (row: Role) => {
ElMessage.success('删除成功'); ElMessage.success("删除成功");
} };
// //
const visible2 = ref(false); const visible2 = ref(false);
const permissOptions = ref({}) const permissOptions = ref({});
const handlePermission = (row: Role) => { const handlePermission = (row: Role) => {
visible2.value = true; visible2.value = true;
permissOptions.value = { permissOptions.value = {
id: row.id, id: row.id,
permiss: row.permiss permiss: row.permiss,
};
}; };
}
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -2,62 +2,89 @@
<div> <div>
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" /> <TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
<div class="container"> <div class="container">
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView" <TableCustom
:delFunc="handleDelete" :page-change="changePage" :editFunc="handleEdit"> :columns="columns"
:tableData="tableData"
:total="page.total"
:viewFunc="handleView"
:delFunc="handleDelete"
:page-change="changePage"
:editFunc="handleEdit"
>
<template #toolbarBtn> <template #toolbarBtn>
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button> <el-button
type="warning"
:icon="CirclePlusFilled"
@click="visible = true"
>新增</el-button
>
</template> </template>
</TableCustom> </TableCustom>
</div> </div>
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close <el-dialog
:close-on-click-modal="false" @close="closeDialog"> :title="isEdit ? '编辑' : '新增'"
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" /> v-model="visible"
width="700px"
destroy-on-close
:close-on-click-modal="false"
@close="closeDialog"
>
<TableEdit
:form-data="rowData"
:options="options"
:edit="isEdit"
:update="updateData"
/>
</el-dialog> </el-dialog>
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close> <el-dialog
<TableDetail :data="viewData"></TableDetail> title="查看详情"
v-model="visible1"
width="700px"
destroy-on-close
>
<TableDetail :data="viewData" />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts" name="system-user"> <script setup lang="ts" name="system-user">
import { ref, reactive } from 'vue'; import { ref, reactive } from "vue";
import { ElMessage } from 'element-plus'; import { ElMessage } from "element-plus";
import { CirclePlusFilled } from '@element-plus/icons-vue'; import { CirclePlusFilled } from "@element-plus/icons-vue";
import { User } from '@/types/user'; import { User } from "@/types/user";
import { fetchUserData } from '@/api'; import { fetchUserData } from "@/api";
import TableCustom from '@/components/table-custom.vue'; import TableCustom from "@/components/table-custom.vue";
import TableDetail from '@/components/table-detail.vue'; import TableDetail from "@/components/table-detail.vue";
import TableSearch from '@/components/table-search.vue'; import TableSearch from "@/components/table-search.vue";
import { FormOption, FormOptionList } from '@/types/form-option'; import { FormOption, FormOptionList } from "@/types/form-option";
// //
const query = reactive({ const query = reactive({
name: '', name: "",
}); });
const searchOpt = ref<FormOptionList[]>([ const searchOpt = ref<FormOptionList[]>([
{ type: 'input', label: '用户名:', prop: 'name' } { type: "input", label: "用户名:", prop: "name" },
]) ]);
const handleSearch = () => { const handleSearch = () => {
changePage(1); changePage(1);
}; };
// //
let columns = ref([ let columns = ref([
{ type: 'index', label: '序号', width: 55, align: 'center' }, { type: "index", label: "序号", width: 55, align: "center" },
{ prop: 'name', label: '用户名' }, { prop: "name", label: "用户名" },
{ prop: 'phone', label: '手机号' }, { prop: "phone", label: "手机号" },
{ prop: 'role', label: '角色' }, { prop: "role", label: "角色" },
{ prop: 'operator', label: '操作', width: 250 }, { prop: "operator", label: "操作", width: 250 },
]) ]);
const page = reactive({ const page = reactive({
index: 1, index: 1,
size: 10, size: 10,
total: 0, total: 0,
}) });
const tableData = ref<User[]>([]); const tableData = ref<User[]>([]);
const getData = async () => { const getData = async () => {
const res = await fetchUserData() const res = await fetchUserData();
tableData.value = res.data.list; tableData.value = res.data.list;
page.total = res.data.pageTotal; page.total = res.data.pageTotal;
}; };
@ -70,16 +97,16 @@ const changePage = (val: number) => {
// / // /
let options = ref<FormOption>({ let options = ref<FormOption>({
labelWidth: '100px', labelWidth: "100px",
span: 12, span: 12,
list: [ list: [
{ type: 'input', label: '用户名', prop: 'name', required: true }, { type: "input", label: "用户名", prop: "name", required: true },
{ type: 'input', label: '手机号', prop: 'phone', required: true }, { type: "input", label: "手机号", prop: "phone", required: true },
{ type: 'input', label: '密码', prop: 'password', required: true }, { type: "input", label: "密码", prop: "password", required: true },
{ type: 'input', label: '邮箱', prop: 'email', required: true }, { type: "input", label: "邮箱", prop: "email", required: true },
{ type: 'input', label: '角色', prop: 'role', required: true }, { type: "input", label: "角色", prop: "role", required: true },
] ],
}) });
const visible = ref(false); const visible = ref(false);
const isEdit = ref(false); const isEdit = ref(false);
const rowData = ref({}); const rowData = ref({});
@ -102,47 +129,47 @@ const closeDialog = () => {
const visible1 = ref(false); const visible1 = ref(false);
const viewData = ref({ const viewData = ref({
row: {}, row: {},
list: [] list: [],
}); });
const handleView = (row: User) => { const handleView = (row: User) => {
viewData.value.row = { ...row } viewData.value.row = { ...row };
viewData.value.list = [ viewData.value.list = [
{ {
prop: 'id', prop: "id",
label: '用户ID', label: "用户ID",
}, },
{ {
prop: 'name', prop: "name",
label: '用户名', label: "用户名",
}, },
{ {
prop: 'password', prop: "password",
label: '密码', label: "密码",
}, },
{ {
prop: 'email', prop: "email",
label: '邮箱', label: "邮箱",
}, },
{ {
prop: 'phone', prop: "phone",
label: '电话', label: "电话",
}, },
{ {
prop: 'role', prop: "role",
label: '角色', label: "角色",
}, },
{ {
prop: 'date', prop: "date",
label: '注册日期', label: "注册日期",
}, },
] ];
visible1.value = true; visible1.value = true;
}; };
// //
const handleDelete = (row: User) => { const handleDelete = (row: User) => {
ElMessage.success('删除成功'); ElMessage.success("删除成功");
} };
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -2,40 +2,70 @@
<div> <div>
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" /> <TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
<div class="container"> <div class="container">
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView" <TableCustom
:delFunc="handleDelete" :editFunc="handleEdit" :refresh="getData" :currentPage="page.index" :columns="columns"
:changePage="changePage"> :tableData="tableData"
:total="page.total"
:viewFunc="handleView"
:delFunc="handleDelete"
:editFunc="handleEdit"
:refresh="getData"
:currentPage="page.index"
:changePage="changePage"
>
<template #toolbarBtn> <template #toolbarBtn>
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button> <el-button
</template> type="warning"
<template #money="{ rows }"> :icon="CirclePlusFilled"
{{ rows.money }} @click="visible = true"
>新增</el-button
>
</template> </template>
<template #money="{ rows }"> {{ rows.money }} </template>
<template #thumb="{ rows }"> <template #thumb="{ rows }">
<el-image class="table-td-thumb" :src="rows.thumb" :z-index="10" :preview-src-list="[rows.thumb]" <el-image
preview-teleported> class="table-td-thumb"
</el-image> :src="rows.thumb"
:z-index="10"
:preview-src-list="[rows.thumb]"
preview-teleported
/>
</template> </template>
<template #state="{ rows }"> <template #state="{ rows }">
<el-tag :type="rows.state ? 'success' : 'danger'"> <el-tag :type="rows.state ? 'success' : 'danger'">
{{ rows.state ? '正常' : '异常' }} {{ rows.state ? "正常" : "异常" }}
</el-tag> </el-tag>
</template> </template>
</TableCustom> </TableCustom>
</div> </div>
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close <el-dialog
:close-on-click-modal="false" @close="closeDialog"> :title="isEdit ? '编辑' : '新增'"
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData"> v-model="visible"
width="700px"
destroy-on-close
:close-on-click-modal="false"
@close="closeDialog"
>
<TableEdit
:form-data="rowData"
:options="options"
:edit="isEdit"
:update="updateData"
>
<template #thumb="{ rows }"> <template #thumb="{ rows }">
<img class="table-td-thumb" :src="rows.thumb"></img> <img class="table-td-thumb" :src="rows.thumb" />
</template> </template>
</TableEdit> </TableEdit>
</el-dialog> </el-dialog>
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close> <el-dialog
title="查看详情"
v-model="visible1"
width="700px"
destroy-on-close
>
<TableDetail :data="viewData"> <TableDetail :data="viewData">
<template #thumb="{ rows }"> <template #thumb="{ rows }">
<el-image :src="rows.thumb"></el-image> <el-image :src="rows.thumb" />
</template> </template>
</TableDetail> </TableDetail>
</el-dialog> </el-dialog>
@ -43,45 +73,45 @@
</template> </template>
<script setup lang="ts" name="basetable"> <script setup lang="ts" name="basetable">
import { ref, reactive } from 'vue'; import { ref, reactive } from "vue";
import { ElMessage, } from 'element-plus'; import { ElMessage } from "element-plus";
import { CirclePlusFilled } from '@element-plus/icons-vue'; import { CirclePlusFilled } from "@element-plus/icons-vue";
import { fetchData } from '@/api/index'; import { fetchData } from "@/api/index";
import TableCustom from '@/components/table-custom.vue'; import TableCustom from "@/components/table-custom.vue";
import TableDetail from '@/components/table-detail.vue'; import TableDetail from "@/components/table-detail.vue";
import TableSearch from '@/components/table-search.vue'; import TableSearch from "@/components/table-search.vue";
import { TableItem } from '@/types/table'; import { TableItem } from "@/types/table";
import { FormOption, FormOptionList } from '@/types/form-option'; import { FormOption, FormOptionList } from "@/types/form-option";
// //
const query = reactive({ const query = reactive({
name: '', name: "",
}); });
const searchOpt = ref<FormOptionList[]>([ const searchOpt = ref<FormOptionList[]>([
{ type: 'input', label: '用户名:', prop: 'name' } { type: "input", label: "用户名:", prop: "name" },
]) ]);
const handleSearch = () => { const handleSearch = () => {
changePage(1); changePage(1);
}; };
// //
let columns = ref([ let columns = ref([
{ type: 'selection' }, { type: "selection" },
{ type: 'index', label: '序号', width: 55, align: 'center' }, { type: "index", label: "序号", width: 55, align: "center" },
{ prop: 'name', label: '用户名' }, { prop: "name", label: "用户名" },
{ prop: 'money', label: '账户余额' }, { prop: "money", label: "账户余额" },
{ prop: 'thumb', label: '头像' }, { prop: "thumb", label: "头像" },
{ prop: 'state', label: '账户状态' }, { prop: "state", label: "账户状态" },
{ prop: 'operator', label: '操作', width: 250 }, { prop: "operator", label: "操作", width: 250 },
]) ]);
const page = reactive({ const page = reactive({
index: 1, index: 1,
size: 10, size: 10,
total: 200, total: 200,
}) });
const tableData = ref<TableItem[]>([]); const tableData = ref<TableItem[]>([]);
const getData = async () => { const getData = async () => {
const res = await fetchData() const res = await fetchData();
tableData.value = res.data.list; tableData.value = res.data.list;
}; };
getData(); getData();
@ -93,15 +123,22 @@ const changePage = (val: number) => {
// / // /
let options = ref<FormOption>({ let options = ref<FormOption>({
labelWidth: '100px', labelWidth: "100px",
span: 24, span: 24,
list: [ list: [
{ type: 'input', label: '用户名', prop: 'name', required: true }, { type: "input", label: "用户名", prop: "name", required: true },
{ type: 'number', label: '账户余额', prop: 'money', required: true }, { type: "number", label: "账户余额", prop: "money", required: true },
{ type: 'switch', activeText: '正常', inactiveText: '异常', label: '账户状态', prop: 'state', required: true }, {
{ type: 'upload', label: '头像', prop: 'thumb', required: true }, type: "switch",
] activeText: "正常",
}) inactiveText: "异常",
label: "账户状态",
prop: "state",
required: true,
},
{ type: "upload", label: "头像", prop: "thumb", required: true },
],
});
const visible = ref(false); const visible = ref(false);
const isEdit = ref(false); const isEdit = ref(false);
const rowData = ref({}); const rowData = ref({});
@ -124,39 +161,39 @@ const closeDialog = () => {
const visible1 = ref(false); const visible1 = ref(false);
const viewData = ref({ const viewData = ref({
row: {}, row: {},
list: [] list: [],
}); });
const handleView = (row: TableItem) => { const handleView = (row: TableItem) => {
viewData.value.row = { ...row } viewData.value.row = { ...row };
viewData.value.list = [ viewData.value.list = [
{ {
prop: 'id', prop: "id",
label: '用户ID', label: "用户ID",
}, },
{ {
prop: 'name', prop: "name",
label: '用户名', label: "用户名",
}, },
{ {
prop: 'money', prop: "money",
label: '账户余额', label: "账户余额",
}, },
{ {
prop: 'state', prop: "state",
label: '账户状态', label: "账户状态",
}, },
{ {
prop: 'thumb', prop: "thumb",
label: '头像', label: "头像",
}, },
] ];
visible1.value = true; visible1.value = true;
}; };
// //
const handleDelete = (row: TableItem) => { const handleDelete = (row: TableItem) => {
ElMessage.success('删除成功'); ElMessage.success("删除成功");
} };
</script> </script>
<style scoped> <style scoped>

View File

@ -4,21 +4,26 @@
<div class="handle-box"> <div class="handle-box">
<el-button type="primary" @click="exportXlsx">Excel</el-button> <el-button type="primary" @click="exportXlsx">Excel</el-button>
</div> </div>
<el-table :data="tableData" border class="table" header-cell-class-name="table-header"> <el-table
<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column> :data="tableData"
<el-table-column prop="name" label="姓名"></el-table-column> border
<el-table-column prop="sno" label="学号"></el-table-column> class="table"
<el-table-column prop="class" label="班级"></el-table-column> header-cell-class-name="table-header"
<el-table-column prop="age" label="年龄"></el-table-column> >
<el-table-column prop="sex" label="性别"></el-table-column> <el-table-column prop="id" label="ID" width="55" align="center" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="sno" label="学号" />
<el-table-column prop="class" label="班级" />
<el-table-column prop="age" label="年龄" />
<el-table-column prop="sex" label="性别" />
</el-table> </el-table>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts" name="export"> <script setup lang="ts" name="export">
import { ref } from 'vue'; import { ref } from "vue";
import * as XLSX from 'xlsx'; import * as XLSX from "xlsx";
interface TableItem { interface TableItem {
id: number; id: number;
@ -35,25 +40,25 @@ const getData = () => {
tableData.value = [ tableData.value = [
{ {
id: 1, id: 1,
name: '小明', name: "小明",
sno: 'S001', sno: "S001",
class: '一班', class: "一班",
age: '10', age: "10",
sex: '男', sex: "男",
}, },
{ {
id: 2, id: 2,
name: '小红', name: "小红",
sno: 'S002', sno: "S002",
class: '一班', class: "一班",
age: '9', age: "9",
sex: '女', sex: "女",
}, },
]; ];
}; };
getData(); getData();
const list = [['序号', '姓名', '学号', '班级', '年龄', '性别']]; const list = [["序号", "姓名", "学号", "班级", "年龄", "性别"]];
const exportXlsx = () => { const exportXlsx = () => {
tableData.value.map((item: any, i: number) => { tableData.value.map((item: any, i: number) => {
const arr: any[] = [i + 1]; const arr: any[] = [i + 1];
@ -62,7 +67,7 @@ const exportXlsx = () => {
}); });
let WorkSheet = XLSX.utils.aoa_to_sheet(list); let WorkSheet = XLSX.utils.aoa_to_sheet(list);
let new_workbook = XLSX.utils.book_new(); let new_workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页'); XLSX.utils.book_append_sheet(new_workbook, WorkSheet, "第一页");
XLSX.writeFile(new_workbook, `表格.xlsx`); XLSX.writeFile(new_workbook, `表格.xlsx`);
}; };
</script> </script>

View File

@ -2,28 +2,39 @@
<div> <div>
<div class="container"> <div class="container">
<div class="handle-box"> <div class="handle-box">
<el-upload action="#" :limit="1" accept=".xlsx, .xls" :show-file-list="false" <el-upload
:before-upload="beforeUpload" :http-request="handleMany"> action="#"
:limit="1"
accept=".xlsx, .xls"
:show-file-list="false"
:before-upload="beforeUpload"
:http-request="handleMany"
>
<el-button class="mr10" type="success">批量导入</el-button> <el-button class="mr10" type="success">批量导入</el-button>
</el-upload> </el-upload>
<el-link href="/template.xlsx" target="_blank">下载模板</el-link> <el-link href="/template.xlsx" target="_blank">下载模板</el-link>
</div> </div>
<el-table :data="tableData" border class="table" header-cell-class-name="table-header"> <el-table
<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column> :data="tableData"
<el-table-column prop="name" label="姓名"></el-table-column> border
<el-table-column prop="sno" label="学号"></el-table-column> class="table"
<el-table-column prop="class" label="班级"></el-table-column> header-cell-class-name="table-header"
<el-table-column prop="age" label="年龄"></el-table-column> >
<el-table-column prop="sex" label="性别"></el-table-column> <el-table-column prop="id" label="ID" width="55" align="center" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="sno" label="学号" />
<el-table-column prop="class" label="班级" />
<el-table-column prop="age" label="年龄" />
<el-table-column prop="sex" label="性别" />
</el-table> </el-table>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts" name="import"> <script setup lang="ts" name="import">
import { UploadProps } from 'element-plus'; import { UploadProps } from "element-plus";
import { ref, reactive } from 'vue'; import { ref, reactive } from "vue";
import * as XLSX from 'xlsx'; import * as XLSX from "xlsx";
interface TableItem { interface TableItem {
id: number; id: number;
@ -40,26 +51,26 @@ const getData = () => {
tableData.value = [ tableData.value = [
{ {
id: 1, id: 1,
name: '小明', name: "小明",
sno: 'S001', sno: "S001",
class: '一班', class: "一班",
age: '10', age: "10",
sex: '男', sex: "男",
}, },
{ {
id: 2, id: 2,
name: '小红', name: "小红",
sno: 'S002', sno: "S002",
class: '一班', class: "一班",
age: '9', age: "9",
sex: '女', sex: "女",
}, },
]; ];
}; };
getData(); getData();
const importList = ref<any>([]); const importList = ref<any>([]);
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => { const beforeUpload: UploadProps["beforeUpload"] = async (rawFile) => {
importList.value = await analysisExcel(rawFile); importList.value = await analysisExcel(rawFile);
return true; return true;
}; };
@ -69,7 +80,7 @@ const analysisExcel = (file: any) => {
reader.onload = function (e: any) { reader.onload = function (e: any) {
const data = e.target.result; const data = e.target.result;
let datajson = XLSX.read(data, { let datajson = XLSX.read(data, {
type: 'binary', type: "binary",
}); });
const sheetName = datajson.SheetNames[0]; const sheetName = datajson.SheetNames[0];
@ -85,11 +96,11 @@ const handleMany = async () => {
const list = importList.value.map((item: any, index: number) => { const list = importList.value.map((item: any, index: number) => {
return { return {
id: index, id: index,
name: item['姓名'], name: item["姓名"],
sno: item['学号'], sno: item["学号"],
class: item['班级'], class: item["班级"],
age: item['年龄'], age: item["年龄"],
sex: item['性别'], sex: item["性别"],
}; };
}); });
tableData.value.push(...list); tableData.value.push(...list);

View File

@ -1,28 +1,38 @@
<template> <template>
<div class="container"> <div class="container">
<TableCustom :columns="columns" :tableData="tableData" :hasToolbar="false" :hasPagination="false"> <TableCustom
:columns="columns"
:tableData="tableData"
:hasToolbar="false"
:hasPagination="false"
>
<template #name="{ rows }"> <template #name="{ rows }">
<el-input v-if="rows.editing" v-model="rows.name"></el-input> <el-input v-if="rows.editing" v-model="rows.name" />
<span v-else>{{ rows.name }}</span> <span v-else>{{ rows.name }}</span>
</template> </template>
<template #password="{ rows }"> <template #password="{ rows }">
<el-input v-if="rows.editing" v-model="rows.password"></el-input> <el-input v-if="rows.editing" v-model="rows.password" />
<span v-else>{{ rows.password }}</span> <span v-else>{{ rows.password }}</span>
</template> </template>
<template #email="{ rows }"> <template #email="{ rows }">
<el-input v-if="rows.editing" v-model="rows.email"></el-input> <el-input v-if="rows.editing" v-model="rows.email" />
<span v-else>{{ rows.email }}</span> <span v-else>{{ rows.email }}</span>
</template> </template>
<template #role="{ rows }"> <template #role="{ rows }">
<el-select v-if="rows.editing" v-model="rows.role"> <el-select v-if="rows.editing" v-model="rows.role">
<el-option label="管理员" value="管理员"></el-option> <el-option label="管理员" value="管理员" />
<el-option label="普通用户" value="普通用户"></el-option> <el-option label="普通用户" value="普通用户" />
</el-select> </el-select>
<span v-else>{{ rows.role }}</span> <span v-else>{{ rows.role }}</span>
</template> </template>
<template #operator="{ rows, index }"> <template #operator="{ rows, index }">
<template v-if="!rows.editing"> <template v-if="!rows.editing">
<el-button type="primary" size="small" :icon="Edit" @click="handleEdit(rows)"> <el-button
type="primary"
size="small"
:icon="Edit"
@click="handleEdit(rows)"
>
编辑 编辑
</el-button> </el-button>
<el-button type="danger" size="small" :icon="Delete" @click=""> <el-button type="danger" size="small" :icon="Delete" @click="">
@ -30,10 +40,20 @@
</el-button> </el-button>
</template> </template>
<template v-else> <template v-else>
<el-button type="success" size="small" :icon="Select" @click="rows.editing = false"> <el-button
type="success"
size="small"
:icon="Select"
@click="rows.editing = false"
>
保存 保存
</el-button> </el-button>
<el-button type="default" size="small" :icon="CloseBold" @click="handleCancel(rows, index)"> <el-button
type="default"
size="small"
:icon="CloseBold"
@click="handleCancel(rows, index)"
>
取消 取消
</el-button> </el-button>
</template> </template>
@ -43,19 +63,19 @@
</template> </template>
<script setup lang="ts" name="table-editor"> <script setup lang="ts" name="table-editor">
import { ref } from 'vue'; import { ref } from "vue";
import { Delete, Edit, CloseBold, Select } from '@element-plus/icons-vue'; import { Delete, Edit, CloseBold, Select } from "@element-plus/icons-vue";
import TableCustom from '@/components/table-custom.vue'; import TableCustom from "@/components/table-custom.vue";
import { fetchUserData } from '@/api/index'; import { fetchUserData } from "@/api/index";
let columns = ref([ let columns = ref([
{ type: 'index', label: '序号', width: 55, align: 'center' }, { type: "index", label: "序号", width: 55, align: "center" },
{ prop: 'name', label: '用户名' }, { prop: "name", label: "用户名" },
{ prop: 'password', label: '密码' }, { prop: "password", label: "密码" },
{ prop: 'email', label: '邮箱' }, { prop: "email", label: "邮箱" },
{ prop: 'role', label: '角色' }, { prop: "role", label: "角色" },
{ prop: 'operator', label: '操作', width: 180 }, { prop: "operator", label: "操作", width: 180 },
]) ]);
const tableData = ref([]); const tableData = ref([]);
const getData = async () => { const getData = async () => {
const res = await fetchUserData(); const res = await fetchUserData();
@ -63,7 +83,7 @@ const getData = async () => {
}; };
getData(); getData();
const rowData = ref({}) const rowData = ref({});
const handleEdit = (row) => { const handleEdit = (row) => {
rowData.value = { ...row }; rowData.value = { ...row };

12
src/vite-env.d.ts vendored
View File

@ -1,10 +1,10 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.vue' { declare module "*.vue" {
import type { DefineComponent } from 'vue' import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any> const component: DefineComponent<{}, {}, any>;
export default component export default component;
} }
declare module 'vue-schart'; declare module "vue-schart";
declare module 'nprogress' declare module "nprogress";

View File

@ -17,6 +17,5 @@
"@/*": ["src/*"] "@/*": ["src/*"]
} }
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"], "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
"references": [{ "path": "./tsconfig.node.json" }]
} }

View File

@ -1,9 +0,0 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -1,31 +1,53 @@
import { defineConfig } from 'vite'; import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
import vue from '@vitejs/plugin-vue'; import vue from "@vitejs/plugin-vue";
import VueSetupExtend from 'vite-plugin-vue-setup-extend'; import VueSetupExtend from "vite-plugin-vue-setup-extend";
import AutoImport from 'unplugin-auto-import/vite'; import AutoImport from "unplugin-auto-import/vite";
import Components from 'unplugin-vue-components/vite'; import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig({
base: './', export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
const env = loadEnv(mode, process.cwd());
return {
base: "./",
plugins: [ plugins: [
vue(), vue(),
VueSetupExtend(), VueSetupExtend(),
AutoImport({ AutoImport({
resolvers: [ElementPlusResolver()] resolvers: [ElementPlusResolver()],
}), }),
Components({ Components({
resolvers: [ElementPlusResolver()] resolvers: [ElementPlusResolver()],
}) }),
], ],
optimizeDeps: { optimizeDeps: {
include: ['schart.js'] include: ["schart.js"],
}, },
resolve: { resolve: {
alias: { alias: {
'@': '/src', "@": "/src",
'~': '/src/assets' "~": "/src/assets",
} },
}, },
define: { define: {
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "true", __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "true",
}, },
server: {
// 允许IP访问
host: "0.0.0.0",
// 应用端口 (默认:3000)
port: Number(env.VITE_APP_PORT),
// 运行是否自动打开浏览器
open: true,
proxy: {
/** 代理前缀为 /dev-api 的请求 */
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
// 接口地址
target: env.VITE_APP_API_URL,
rewrite: (path) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
},
},
},
};
}); });

2178
yarn.lock

File diff suppressed because it is too large Load Diff