增加配置格式化

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'
export {}
declare global {
}
export {};
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']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
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']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
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']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu']
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']
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']
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']
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']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
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']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']

View File

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

5525
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,73 @@
{
"name": "vue-manage-system",
"version": "5.5.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"serve": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "*",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.3",
"countup.js": "^2.8.0",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
"md-editor-v3": "^2.11.2",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"vue": "^3.4.5",
"vue-cropper": "1.1.1",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue-schart": "^2.0.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.0.0",
"@vue/compiler-sfc": "^3.1.2",
"typescript": "^4.6.4",
"unplugin-auto-import": "^0.11.2",
"unplugin-vue-components": "^0.22.4",
"vite": "^3.0.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^0.38.4"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
{
"name": "vue-manage-system",
"version": "5.5.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"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": {
"@element-plus/icons-vue": "*",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.3",
"countup.js": "^2.8.0",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
"md-editor-v3": "^2.11.2",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"vue": "^3.4.5",
"vue-cropper": "1.1.1",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue-schart": "^2.0.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/node": "^20.12.11",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@vitejs/plugin-vue": "^3.0.0",
"@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",
"unplugin-auto-import": "^0.11.2",
"unplugin-vue-components": "^0.22.4",
"vite": "^3.0.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^0.38.4"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

View File

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

View File

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

View File

@ -1,39 +1,40 @@
<template>
<span ref="countRef"></span>
<span ref="countRef"></span>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { CountUp } from 'countup.js';
import { onMounted, ref, watch } from "vue";
import { CountUp } from "countup.js";
const props = defineProps({
end: {
type: Number,
required: true,
},
options: {
type: Object,
default: () => ({}),
required: false,
},
end: {
type: Number,
required: true,
},
options: {
type: Object,
default: () => ({}),
required: false,
},
});
const countRef = ref<any>(null);
let countUp: any;
onMounted(() => {
countUp = new CountUp(countRef.value, props.end, props.options);
if (countUp.error) {
console.error(countUp.error);
return;
}
countUp.start();
countUp = new CountUp(countRef.value, props.end, props.options);
if (countUp.error) {
console.error(countUp.error);
return;
}
countUp.start();
});
watch(() => props.end, (newVal) => {
watch(
() => props.end,
(newVal) => {
if (countUp) {
countUp.update(newVal);
countUp.update(newVal);
}
});
</script>
}
);
</script>

View File

@ -1,204 +1,212 @@
<template>
<div class="header">
<!-- 折叠按钮 -->
<div class="header-left">
<img class="logo" src="../assets/img/logo.svg" alt="" />
<div class="web-title">后台管理系统</div>
<div class="collapse-btn" @click="collapseChage">
<el-icon v-if="sidebar.collapse">
<Expand />
</el-icon>
<el-icon v-else>
<Fold />
</el-icon>
</div>
</div>
<div class="header-right">
<div class="header-user-con">
<div class="btn-icon" @click="router.push('/theme')">
<el-tooltip effect="dark" content="设置主题" placement="bottom">
<i class="el-icon-lx-skin"></i>
</el-tooltip>
</div>
<div class="btn-icon" @click="router.push('/ucenter')">
<el-tooltip
effect="dark"
:content="message ? `有${message}条未读消息` : `消息中心`"
placement="bottom"
>
<i class="el-icon-lx-notice"></i>
</el-tooltip>
<span class="btn-bell-badge" v-if="message"></span>
</div>
<div class="btn-icon" @click="setFullScreen">
<el-tooltip effect="dark" content="全屏" placement="bottom">
<i class="el-icon-lx-full"></i>
</el-tooltip>
</div>
<!-- 用户头像 -->
<el-avatar class="user-avator" :size="30" :src="imgurl" />
<!-- 用户名下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
{{ username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
<el-dropdown-item>项目仓库</el-dropdown-item>
</a>
<a href="https://lin-xin.gitee.io/example/vuems-doc/" target="_blank">
<el-dropdown-item>官方文档</el-dropdown-item>
</a>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div class="header">
<!-- 折叠按钮 -->
<div class="header-left">
<img class="logo" src="../assets/img/logo.svg" alt="" />
<div class="web-title">后台管理系统</div>
<div class="collapse-btn" @click="collapseChage">
<el-icon v-if="sidebar.collapse">
<Expand />
</el-icon>
<el-icon v-else>
<Fold />
</el-icon>
</div>
</div>
<div class="header-right">
<div class="header-user-con">
<div class="btn-icon" @click="router.push('/theme')">
<el-tooltip effect="dark" content="设置主题" placement="bottom">
<i class="el-icon-lx-skin"></i>
</el-tooltip>
</div>
<div class="btn-icon" @click="router.push('/ucenter')">
<el-tooltip
effect="dark"
:content="message ? `有${message}条未读消息` : `消息中心`"
placement="bottom"
>
<i class="el-icon-lx-notice"></i>
</el-tooltip>
<span class="btn-bell-badge" v-if="message"></span>
</div>
<div class="btn-icon" @click="setFullScreen">
<el-tooltip effect="dark" content="全屏" placement="bottom">
<i class="el-icon-lx-full"></i>
</el-tooltip>
</div>
<!-- 用户头像 -->
<el-avatar class="user-avator" :size="30" :src="imgurl" />
<!-- 用户名下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
{{ username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<a
href="https://github.com/lin-xin/vue-manage-system"
target="_blank"
>
<el-dropdown-item>项目仓库</el-dropdown-item>
</a>
<a
href="https://lin-xin.gitee.io/example/vuems-doc/"
target="_blank"
>
<el-dropdown-item>官方文档</el-dropdown-item>
</a>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="loginout"
>退出登录</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { useSidebarStore } from '../store/sidebar';
import { useRouter } from 'vue-router';
import imgurl from '../assets/img/img.jpg';
import { onMounted } from "vue";
import { useSidebarStore } from "../store/sidebar";
import { useRouter } from "vue-router";
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 sidebar = useSidebarStore();
//
const collapseChage = () => {
sidebar.handleCollapse();
sidebar.handleCollapse();
};
onMounted(() => {
if (document.body.clientWidth < 1500) {
collapseChage();
}
if (document.body.clientWidth < 1500) {
collapseChage();
}
});
//
const router = useRouter();
const handleCommand = (command: string) => {
if (command == 'loginout') {
localStorage.removeItem('vuems_name');
router.push('/login');
} else if (command == 'user') {
router.push('/ucenter');
}
if (command == "loginout") {
localStorage.removeItem("vuems_name");
router.push("/login");
} else if (command == "user") {
router.push("/ucenter");
}
};
const setFullScreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.body.requestFullscreen.call(document.body);
}
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.body.requestFullscreen.call(document.body);
}
};
</script>
<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: 100%;
height: 70px;
color: var(--header-text-color);
background-color: var(--header-bg-color);
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: 100%;
height: 70px;
color: var(--header-text-color);
background-color: var(--header-bg-color);
border-bottom: 1px solid #ddd;
}
.header-left {
display: flex;
align-items: center;
padding-left: 20px;
height: 100%;
display: flex;
align-items: center;
padding-left: 20px;
height: 100%;
}
.logo {
width: 35px;
width: 35px;
}
.web-title {
margin: 0 40px 0 10px;
font-size: 22px;
margin: 0 40px 0 10px;
font-size: 22px;
}
.collapse-btn {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 10px;
cursor: pointer;
opacity: 0.8;
font-size: 22px;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 10px;
cursor: pointer;
opacity: 0.8;
font-size: 22px;
}
.collapse-btn:hover {
opacity: 1;
opacity: 1;
}
.header-right {
float: right;
padding-right: 50px;
float: right;
padding-right: 50px;
}
.header-user-con {
display: flex;
height: 70px;
align-items: center;
display: flex;
height: 70px;
align-items: center;
}
.btn-fullscreen {
transform: rotate(45deg);
margin-right: 5px;
font-size: 24px;
transform: rotate(45deg);
margin-right: 5px;
font-size: 24px;
}
.btn-icon {
position: relative;
width: 30px;
height: 30px;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
margin: 0 5px;
font-size: 20px;
position: relative;
width: 30px;
height: 30px;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
margin: 0 5px;
font-size: 20px;
}
.btn-bell-badge {
position: absolute;
right: 4px;
top: 0px;
width: 8px;
height: 8px;
border-radius: 4px;
background: #f56c6c;
color: var(--header-text-color);
position: absolute;
right: 4px;
top: 0px;
width: 8px;
height: 8px;
border-radius: 4px;
background: #f56c6c;
color: var(--header-text-color);
}
.user-avator {
margin: 0 10px 0 20px;
margin: 0 10px 0 20px;
}
.el-dropdown-link {
color: var(--header-text-color);
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
cursor: pointer;
display: flex;
align-items: center;
}
.el-dropdown-menu__item {
text-align: center;
text-align: center;
}
</style>

View File

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

View File

@ -1,66 +1,74 @@
<template>
<div class="sidebar">
<el-menu
class="sidebar-el-menu"
:default-active="onRoutes"
:collapse="sidebar.collapse"
:background-color="sidebar.bgColor"
:text-color="sidebar.textColor"
router
>
<template v-for="item in menuData">
<template v-if="item.children">
<el-sub-menu :index="item.index" :key="item.index" v-permiss="item.id">
<template #title>
<el-icon>
<component :is="item.icon"></component>
</el-icon>
<span>{{ item.title }}</span>
</template>
<template v-for="subItem in item.children">
<el-sub-menu
v-if="subItem.children"
:index="subItem.index"
:key="subItem.index"
v-permiss="item.id"
>
<template #title>{{ subItem.title }}</template>
<el-menu-item
v-for="(threeItem, i) in subItem.children"
:key="i"
:index="threeItem.index"
>
{{ threeItem.title }}
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="subItem.index" v-permiss="item.id">
{{ subItem.title }}
</el-menu-item>
</template>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item :index="item.index" :key="item.index" v-permiss="item.id">
<el-icon>
<component :is="item.icon"></component>
</el-icon>
<template #title>{{ item.title }}</template>
</el-menu-item>
</template>
<div class="sidebar">
<el-menu
class="sidebar-el-menu"
:default-active="onRoutes"
:collapse="sidebar.collapse"
:background-color="sidebar.bgColor"
:text-color="sidebar.textColor"
router
>
<template v-for="item in menuData">
<template v-if="item.children">
<el-sub-menu
:index="item.index"
:key="item.index"
v-permiss="item.id"
>
<template #title>
<el-icon>
<component :is="item.icon" />
</el-icon>
<span>{{ item.title }}</span>
</template>
</el-menu>
</div>
<template v-for="subItem in item.children">
<el-sub-menu
v-if="subItem.children"
:index="subItem.index"
:key="subItem.index"
v-permiss="item.id"
>
<template #title>{{ subItem.title }}</template>
<el-menu-item
v-for="(threeItem, i) in subItem.children"
:key="i"
:index="threeItem.index"
>
{{ threeItem.title }}
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="subItem.index" v-permiss="item.id">
{{ subItem.title }}
</el-menu-item>
</template>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item
:index="item.index"
:key="item.index"
v-permiss="item.id"
>
<el-icon>
<component :is="item.icon" />
</el-icon>
<template #title>{{ item.title }}</template>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useSidebarStore } from '../store/sidebar';
import { useRoute } from 'vue-router';
import { menuData } from '@/components/menu';
import { computed } from "vue";
import { useSidebarStore } from "../store/sidebar";
import { useRoute } from "vue-router";
import { menuData } from "@/components/menu";
const route = useRoute();
const onRoutes = computed(() => {
return route.path;
return route.path;
});
const sidebar = useSidebarStore();
@ -68,23 +76,23 @@ const sidebar = useSidebarStore();
<style scoped>
.sidebar {
display: block;
position: absolute;
left: 0;
top: 70px;
bottom: 0;
overflow-y: scroll;
display: block;
position: absolute;
left: 0;
top: 70px;
bottom: 0;
overflow-y: scroll;
}
.sidebar::-webkit-scrollbar {
width: 0;
width: 0;
}
.sidebar-el-menu:not(.el-menu--collapse) {
width: 250px;
width: 250px;
}
.sidebar-el-menu {
min-height: 100%;
min-height: 100%;
}
</style>

View File

@ -1,211 +1,250 @@
<template>
<div>
<div class="table-toolbar" v-if="hasToolbar">
<div class="table-toolbar-left">
<slot name="toolbarBtn"></slot>
</div>
<div class="table-toolbar-right flex-center">
<template v-if="multipleSelection.length > 0">
<el-tooltip effect="dark" content="删除选中" placement="top">
<el-icon class="columns-setting-icon" @click="delSelection(multipleSelection)">
<Delete />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
</template>
<el-tooltip effect="dark" content="刷新" placement="top">
<el-icon class="columns-setting-icon" @click="refresh">
<Refresh />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="列设置" placement="top">
<el-dropdown :hide-on-click="false" size="small" trigger="click">
<el-icon class="columns-setting-icon">
<Setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="c in columns">
<el-checkbox v-model="c.visible" :label="c.label" />
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
</div>
</div>
<el-table 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">
<el-table-column v-if="item.visible" :prop="item.prop" :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) }}
</template>
<template #default="{ row, column, $index }" v-if="!item.type">
<slot :name="item.prop" :rows="row" :index="$index">
<template v-if="item.prop == 'operator'">
<el-button type="warning" size="small" :icon="View" @click="viewFunc(row)">
查看
</el-button>
<el-button type="primary" size="small" :icon="Edit" @click="editFunc(row)">
编辑
</el-button>
<el-button type="danger" size="small" :icon="Delete" @click="handleDelete(row)">
删除
</el-button>
</template>
<span v-else-if="item.formatter">
{{ item.formatter(row[item.prop]) }}
</span>
<span v-else>
{{ row[item.prop] }}
</span>
</slot>
</template>
</el-table-column>
<div>
<div class="table-toolbar" v-if="hasToolbar">
<div class="table-toolbar-left">
<slot name="toolbarBtn"></slot>
</div>
<div class="table-toolbar-right flex-center">
<template v-if="multipleSelection.length > 0">
<el-tooltip effect="dark" content="删除选中" placement="top">
<el-icon
class="columns-setting-icon"
@click="delSelection(multipleSelection)"
>
<Delete />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
</template>
<el-tooltip effect="dark" content="刷新" placement="top">
<el-icon class="columns-setting-icon" @click="refresh">
<Refresh />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="列设置" placement="top">
<el-dropdown :hide-on-click="false" size="small" trigger="click">
<el-icon class="columns-setting-icon">
<Setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="c in columns">
<el-checkbox v-model="c.visible" :label="c.label" />
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-table>
<el-pagination v-if="hasPagination" :current-page="currentPage" :page-size="pageSize" :background="true"
:layout="layout" :total="total" @current-change="handleCurrentChange" />
</el-dropdown>
</el-tooltip>
</div>
</div>
<el-table
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">
<el-table-column
v-if="item.visible"
:prop="item.prop"
: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) }}
</template>
<template #default="{ row, column, $index }" v-if="!item.type">
<slot :name="item.prop" :rows="row" :index="$index">
<template v-if="item.prop == 'operator'">
<el-button
type="warning"
size="small"
:icon="View"
@click="viewFunc(row)"
>
查看
</el-button>
<el-button
type="primary"
size="small"
:icon="Edit"
@click="editFunc(row)"
>
编辑
</el-button>
<el-button
type="danger"
size="small"
:icon="Delete"
@click="handleDelete(row)"
>
删除
</el-button>
</template>
<span v-else-if="item.formatter">
{{ item.formatter(row[item.prop]) }}
</span>
<span v-else>
{{ row[item.prop] }}
</span>
</slot>
</template>
</el-table-column>
</template>
</el-table>
<el-pagination
v-if="hasPagination"
:current-page="currentPage"
:page-size="pageSize"
:background="true"
:layout="layout"
:total="total"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup lang="ts">
import { toRefs, PropType, ref } from 'vue'
import { Delete, Edit, View, Refresh } from '@element-plus/icons-vue';
import { ElMessageBox } from 'element-plus';
import { toRefs, PropType, ref } from "vue";
import { Delete, Edit, View, Refresh } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
const props = defineProps({
//
tableData: {
type: Array,
default: []
},
columns: {
type: Array as PropType<any[]>,
default: []
},
rowKey: {
type: String,
default: 'id'
},
hasToolbar: {
type: Boolean,
default: true
},
//
hasPagination: {
type: Boolean,
default: true
},
total: {
type: Number,
default: 0
},
currentPage: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
//
tableData: {
type: Array,
default: [],
},
columns: {
type: Array as PropType<any[]>,
default: [],
},
rowKey: {
type: String,
default: "id",
},
hasToolbar: {
type: Boolean,
default: true,
},
//
hasPagination: {
type: Boolean,
default: true,
},
total: {
type: Number,
default: 0,
},
currentPage: {
type: Number,
default: 1,
},
pageSize: {
type: Number,
default: 10,
},
layout: {
type: String,
default: 'total, prev, pager, next'
},
delFunc: {
type: Function,
default: () => { }
},
viewFunc: {
type: Function,
default: () => { }
},
editFunc: {
type: Function,
default: () => { }
},
delSelection: {
type: Function,
default: () => { }
},
refresh: {
type: Function,
default: () => { }
},
changePage: {
type: Function,
default: () => { }
}
})
layout: {
type: String,
default: "total, prev, pager, next",
},
delFunc: {
type: Function,
default: () => {},
},
viewFunc: {
type: Function,
default: () => {},
},
editFunc: {
type: Function,
default: () => {},
},
delSelection: {
type: Function,
default: () => {},
},
refresh: {
type: Function,
default: () => {},
},
changePage: {
type: Function,
default: () => {},
},
});
let {
tableData,
columns,
rowKey,
hasToolbar,
hasPagination,
total,
currentPage,
pageSize,
layout,
} = toRefs(props)
tableData,
columns,
rowKey,
hasToolbar,
hasPagination,
total,
currentPage,
pageSize,
layout,
} = toRefs(props);
columns.value.forEach((item) => {
if (item.visible === undefined) {
item.visible = true
}
})
if (item.visible === undefined) {
item.visible = true;
}
});
//
const multipleSelection = ref([])
const multipleSelection = ref([]);
const handleSelectionChange = (selection: any[]) => {
multipleSelection.value = selection
}
multipleSelection.value = selection;
};
//
const handleCurrentChange = (val: number) => {
props.changePage(val)
}
props.changePage(val);
};
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
type: 'warning'
ElMessageBox.confirm("确定要删除吗?", "提示", {
type: "warning",
})
.then(async () => {
props.delFunc(row);
})
.then(async () => {
props.delFunc(row);
})
.catch(() => { });
.catch(() => {});
};
const getIndex = (index: number) => {
return index + 1 + (currentPage.value - 1) * pageSize.value
}
return index + 1 + (currentPage.value - 1) * pageSize.value;
};
</script>
<style scoped>
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 10px;
}
.columns-setting-icon {
display: block;
font-size: 18px;
cursor: pointer;
color: #676767;
display: block;
font-size: 18px;
cursor: pointer;
color: #676767;
}
</style>
<style>
.table-header .cell {
color: #333;
color: #333;
}
</style>
</style>

View File

@ -1,21 +1,20 @@
<template>
<el-descriptions :title="title" :column="column" border>
<el-descriptions-item v-for="item in list" :span="item.span">
<template #label> {{ item.label }} </template>
<slot :name="item.prop" :rows="row">
{{ item.value || row[item.prop] }}
</slot>
</el-descriptions-item>
</el-descriptions>
<el-descriptions :title="title" :column="column" border>
<el-descriptions-item v-for="item in list" :span="item.span">
<template #label> {{ item.label }} </template>
<slot :name="item.prop" :rows="row">
{{ item.value || row[item.prop] }}
</slot>
</el-descriptions-item>
</el-descriptions>
</template>
<script lang="ts" setup>
const props = defineProps({
data: {
type: Object,
required: true,
}
data: {
type: Object,
required: true,
},
});
const { row, title, column = 2, list } = props.data;
</script>

View File

@ -1,111 +1,152 @@
<template>
<el-form ref="formRef" :model="form" :rules="rules" :label-width="options.labelWidth">
<el-row>
<el-col :span="options.span" v-for="item in options.list">
<el-form-item :label="item.label" :prop="item.prop">
<!-- 文本框数字框下拉框日期框开关上传 -->
<el-input v-if="item.type === 'input'" v-model="form[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable></el-input>
<el-input-number v-else-if="item.type === 'number'" v-model="form[item.prop]"
:disabled="item.disabled" controls-position="right"></el-input-number>
<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-option>
</el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="form[item.prop]"
:value-format="item.format"></el-date-picker>
<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-switch>
<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" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
<slot :name="item.prop" v-else>
<el-form
ref="formRef"
:model="form"
:rules="rules"
:label-width="options.labelWidth"
>
<el-row>
<el-col :span="options.span" v-for="item in options.list">
<el-form-item :label="item.label" :prop="item.prop">
<!-- 文本框数字框下拉框日期框开关上传 -->
<el-input
v-if="item.type === 'input'"
v-model="form[item.prop]"
:disabled="item.disabled"
:placeholder="item.placeholder"
clearable
/>
<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-date-picker
v-else-if="item.type === 'date'"
type="date"
v-model="form[item.prop]"
:value-format="item.format"
/>
<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" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
<slot :name="item.prop" v-else> </slot>
</el-form-item>
</el-col>
</el-row>
</slot>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="saveEdit(formRef)"> </el-button>
</el-form-item>
</el-form>
<el-form-item>
<el-button type="primary" @click="saveEdit(formRef)"> </el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { FormOption } from '@/types/form-option';
import { FormInstance, FormRules, UploadProps } from 'element-plus';
import { PropType, ref } from 'vue';
import { FormOption } from "@/types/form-option";
import { FormInstance, FormRules, UploadProps } from "element-plus";
import { PropType, ref } from "vue";
const { options, formData, edit, update } = defineProps({
options: {
type: Object as PropType<FormOption>,
required: true
},
formData: {
type: Object,
required: true
},
edit: {
type: Boolean,
required: false
},
update: {
type: Function,
required: true
}
options: {
type: Object as PropType<FormOption>,
required: true,
},
formData: {
type: Object,
required: true,
},
edit: {
type: Boolean,
required: false,
},
update: {
type: Function,
required: true,
},
});
const form = ref({ ...(edit ? formData : {}) });
const rules: FormRules = options.list.map(item => {
if (item.required) {
return { [item.prop]: [{ required: true, message: `${item.label}不能为空`, trigger: 'blur' }] };
}
return {};
}).reduce((acc, cur) => ({ ...acc, ...cur }), {});
const rules: FormRules = options.list
.map((item) => {
if (item.required) {
return {
[item.prop]: [
{ required: true, message: `${item.label}不能为空`, trigger: "blur" },
],
};
}
return {};
})
.reduce((acc, cur) => ({ ...acc, ...cur }), {});
const formRef = ref<FormInstance>();
const saveEdit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(valid => {
if (!valid) return false;
update(form.value);
});
if (!formEl) return;
formEl.validate((valid) => {
if (!valid) return false;
update(form.value);
});
};
const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {
form.value.thumb = URL.createObjectURL(uploadFile.raw!);
const handleAvatarSuccess: UploadProps["onSuccess"] = (
response,
uploadFile
) => {
form.value.thumb = URL.createObjectURL(uploadFile.raw!);
};
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>

View File

@ -1,60 +1,86 @@
<template>
<div class="search-container">
<el-form ref="searchRef" :model="query" :inline="true">
<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"
:placeholder="item.placeholder" clearable></el-input>
<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-option>
</el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="query[item.prop]"
:value-format="item.format"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="search"></el-button>
<el-button :icon="Refresh" @click="resetForm(searchRef)"></el-button>
</el-form-item>
</el-form>
</div>
<div class="search-container">
<el-form ref="searchRef" :model="query" :inline="true">
<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"
: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-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-button type="primary" :icon="Search" @click="search"
>搜索</el-button
>
<el-button :icon="Refresh" @click="resetForm(searchRef)"
>重置</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { Search, Refresh } from '@element-plus/icons-vue';
import { PropType, ref } from 'vue';
import { FormOptionList } from '@/types/form-option';
import { FormInstance } from "element-plus";
import { Search, Refresh } from "@element-plus/icons-vue";
import { PropType, ref } from "vue";
import { FormOptionList } from "@/types/form-option";
const props = defineProps({
query: {
type: Object,
required: true
},
options: {
type: Array as PropType<Array<FormOptionList>>,
required: true
},
search: {
type: Function,
default: () => { }
}
query: {
type: Object,
required: true,
},
options: {
type: Array as PropType<Array<FormOptionList>>,
required: true,
},
search: {
type: Function,
default: () => {},
},
});
const searchRef = ref<FormInstance>();
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
props.search();
}
if (!formEl) return;
formEl.resetFields();
props.search();
};
</script>
<style scoped>
.search-container {
padding: 20px 30px 0;
background-color: #fff;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px
padding: 20px 30px 0;
background-color: #fff;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>

View File

@ -1,38 +1,45 @@
<template>
<div class="tabs-container">
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
<el-tab-pane
v-for="item in tabs.list"
:key="item.path"
:label="item.title"
:name="item.path"
@click="setTags(item)"
></el-tab-pane>
</el-tabs>
<div class="Tabs-close-box">
<el-dropdown @command="handleTags">
<el-button size="small" type="primary" plain>
标签选项
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu size="small">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="tabs-container">
<el-tabs
v-model="activePath"
class="tabs"
type="card"
closable
@tab-click="clickTabls"
@tab-remove="closeTabs"
>
<el-tab-pane
v-for="item in tabs.list"
:key="item.path"
:label="item.title"
:name="item.path"
@click="setTags(item)"
/>
</el-tabs>
<div class="Tabs-close-box">
<el-dropdown @command="handleTags">
<el-button size="small" type="primary" plain>
标签选项
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu size="small">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useTabsStore } from '../store/tabs';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
import { ref, watch } from "vue";
import { useTabsStore } from "../store/tabs";
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
@ -40,109 +47,109 @@ const activePath = ref(route.fullPath);
const tabs = useTabsStore();
//
const setTags = (route: any) => {
const isExist = tabs.list.some((item) => {
return item.path === route.fullPath;
const isExist = tabs.list.some((item) => {
return item.path === route.fullPath;
});
if (!isExist) {
tabs.setTabsItem({
name: route.name,
title: route.meta.title,
path: route.fullPath,
});
if (!isExist) {
tabs.setTabsItem({
name: route.name,
title: route.meta.title,
path: route.fullPath,
});
}
}
};
setTags(route);
onBeforeRouteUpdate((to) => {
setTags(to);
setTags(to);
});
//
const closeAll = () => {
tabs.clearTabs();
router.push('/');
tabs.clearTabs();
router.push("/");
};
//
const closeOther = () => {
const curItem = tabs.list.filter((item) => {
return item.path === route.fullPath;
});
tabs.closeTabsOther(curItem);
const curItem = tabs.list.filter((item) => {
return item.path === route.fullPath;
});
tabs.closeTabsOther(curItem);
};
const handleTags = (command: string) => {
switch (command) {
case 'current':
//
tabs.closeCurrentTag({
$router: router,
$route: route,
});
break;
case 'all':
closeAll();
break;
switch (command) {
case "current":
//
tabs.closeCurrentTag({
$router: router,
$route: route,
});
break;
case "all":
closeAll();
break;
case 'other':
closeOther();
break;
}
case "other":
closeOther();
break;
}
};
const clickTabls = (item: any) => {
router.push(item.props.name);
router.push(item.props.name);
};
const closeTabs = (path: string) => {
const index = tabs.list.findIndex((item) => item.path === path);
tabs.delTabsItem(index);
const item = tabs.list[index] || tabs.list[index - 1];
router.push(item ? item.path : '/');
const index = tabs.list.findIndex((item) => item.path === path);
tabs.delTabsItem(index);
const item = tabs.list[index] || tabs.list[index - 1];
router.push(item ? item.path : "/");
};
watch(
() => route.fullPath,
(newVal, oldVal) => {
activePath.value = newVal;
}
() => route.fullPath,
(newVal, oldVal) => {
activePath.value = newVal;
}
);
</script>
<style scss>
.tabs-container {
position: relative;
overflow: hidden;
background: #fff;
padding: 2px 120px 0 0;
position: relative;
overflow: hidden;
background: #fff;
padding: 2px 120px 0 0;
}
.tabs {
.el-tabs__header {
margin-bottom: 0;
}
.el-tabs__header {
margin-bottom: 0;
}
.el-tabs__nav {
height: 28px;
}
.el-tabs__nav {
height: 28px;
}
.el-tabs__nav-next,
.el-tabs__nav-prev {
line-height: 32px;
}
.el-tabs__nav-next,
.el-tabs__nav-prev {
line-height: 32px;
}
&.el-tabs {
--el-tabs-header-height: 28px;
}
&.el-tabs {
--el-tabs-header-height: 28px;
}
}
.Tabs-close-box {
position: absolute;
right: 0;
top: 0;
box-sizing: border-box;
padding-top: 1px;
text-align: center;
width: 110px;
height: 30px;
background: #fff;
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
position: absolute;
right: 0;
top: 0;
box-sizing: border-box;
padding-top: 1px;
text-align: center;
width: 110px;
height: 30px;
background: #fff;
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,53 +1,53 @@
import { defineStore } from 'pinia';
import { defineStore } from "pinia";
interface ListItem {
name: string;
path: string;
title: string;
name: string;
path: string;
title: string;
}
export const useTabsStore = defineStore('tabs', {
state: () => {
return {
list: <ListItem[]>[]
};
},
getters: {
show: state => {
return state.list.length > 0;
},
nameList: state => {
return state.list.map(item => item.name);
}
},
actions: {
delTabsItem(index: number) {
this.list.splice(index, 1);
},
setTabsItem(data: ListItem) {
this.list.push(data);
},
clearTabs() {
this.list = [];
},
closeTabsOther(data: ListItem[]) {
this.list = data;
},
closeCurrentTag(data: any) {
for (let i = 0, len = this.list.length; i < len; i++) {
const item = this.list[i];
if (item.path === data.$route.fullPath) {
if (i < len - 1) {
data.$router.push(this.list[i + 1].path);
} else if (i > 0) {
data.$router.push(this.list[i - 1].path);
} else {
data.$router.push('/');
}
this.list.splice(i, 1);
break;
}
}
}
}
export const useTabsStore = defineStore("tabs", {
state: () => {
return {
list: <ListItem[]>[],
};
},
getters: {
show: (state) => {
return state.list.length > 0;
},
nameList: (state) => {
return state.list.map((item) => item.name);
},
},
actions: {
delTabsItem(index: number) {
this.list.splice(index, 1);
},
setTabsItem(data: ListItem) {
this.list.push(data);
},
clearTabs() {
this.list = [];
},
closeTabsOther(data: ListItem[]) {
this.list = data;
},
closeCurrentTag(data: any) {
for (let i = 0, len = this.list.length; i < len; i++) {
const item = this.list[i];
if (item.path === data.$route.fullPath) {
if (i < len - 1) {
data.$router.push(this.list[i + 1].path);
} else if (i > 0) {
data.$router.push(this.list[i - 1].path);
} else {
data.$router.push("/");
}
this.list.splice(i, 1);
break;
}
}
},
},
});

View File

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

View File

@ -1,21 +1,20 @@
export interface FormOption {
list: FormOptionList[];
labelWidth?: number | string;
span?: number;
list: FormOptionList[];
labelWidth?: number | string;
span?: number;
}
export interface FormOptionList {
prop: string;
label: string;
type: string;
placeholder?: string;
disabled?: boolean;
opts?: any[];
format?: string;
activeValue?: any;
inactiveValue?: any;
activeText?: string;
inactiveText?: string;
required?: boolean;
}
prop: string;
label: string;
type: string;
placeholder?: string;
disabled?: boolean;
opts?: any[];
format?: string;
activeValue?: any;
inactiveValue?: any;
activeText?: string;
inactiveText?: string;
required?: boolean;
}

View File

@ -1,9 +1,9 @@
export interface Menus {
id: string;
pid?: string;
icon?: string;
index: string;
title: string;
permiss?: string;
children?: Menus[];
}
id: string;
pid?: string;
icon?: string;
index: string;
title: string;
permiss?: string;
children?: Menus[];
}

View File

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

View File

@ -1,9 +1,9 @@
export interface TableItem {
id: number;
name: string;
thumb: string;
money: number;
state: string;
date: string;
address: string;
}
id: number;
name: string;
thumb: string;
money: number;
state: string;
date: string;
address: string;
}

View File

@ -1,16 +1,15 @@
export interface User {
id: number;
name: string;
password: string;
email: string;
phone: string;
role: string;
date: string;
id: number;
name: string;
password: string;
email: string;
phone: string;
role: string;
date: string;
}
export interface Register {
username: string;
password: string;
email: string;
}
username: string;
password: string;
email: 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) => {
dom.style.setProperty(prop, val);
export const setProperty = (
prop: string,
val: any,
dom = document.documentElement
) => {
dom.style.setProperty(prop, val);
};
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
let color = '#';
for (let i = 0; i <= 2; i++) {
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 c = Math.round(c1 * weight + c2 * (1 - weight));
color += c.toString(16).padStart(2, '0');
}
return color;
export const mix = (
color1: string,
color2: string,
weight: number = 0.5
): string => {
let color = "#";
for (let i = 0; i <= 2; i++) {
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 c = Math.round(c1 * weight + c2 * (1 - weight));
color += c.toString(16).padStart(2, "0");
}
return color;
};

View File

@ -1,31 +1,37 @@
import axios, { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import axios, {
AxiosInstance,
AxiosError,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
const service: AxiosInstance = axios.create({
timeout: 5000
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 5000,
});
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
return config;
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
(config: InternalAxiosRequestConfig) => {
return config;
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
service.interceptors.response.use(
(response: AxiosResponse) => {
if (response.status === 200) {
return response;
} else {
Promise.reject();
}
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
(response: AxiosResponse) => {
if (response.status === 200) {
return response;
} else {
Promise.reject();
}
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
export default service;

View File

@ -1,87 +1,96 @@
<template>
<div class="container">
<div class="plugins-tips">
vue-echartsApache ECharts Vue.js 组件 访问地址
<a href="https://github.com/ecomfe/vue-echarts" target="_blank">vue-echarts</a>
</div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">柱状图</div>
</template>
<v-chart class="schart" :option="barOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">折线图</div>
</template>
<v-chart class="schart" :option="lineOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">饼状图</div>
</template>
<v-chart class="schart" :option="pieOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">环形图</div>
</template>
<v-chart class="schart" :option="ringOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">词云图</div>
</template>
<v-chart class="schart" :option="wordOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">地图</div>
</template>
<v-chart class="schart" :option="mapOptions" />
</el-card>
<div class="container">
<div class="plugins-tips">
vue-echartsApache ECharts Vue.js 组件 访问地址
<a href="https://github.com/ecomfe/vue-echarts" target="_blank"
>vue-echarts</a
>
</div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">柱状图</div>
</template>
<v-chart class="schart" :option="barOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">折线图</div>
</template>
<v-chart class="schart" :option="lineOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">饼状图</div>
</template>
<v-chart class="schart" :option="pieOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">环形图</div>
</template>
<v-chart class="schart" :option="ringOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">词云图</div>
</template>
<v-chart class="schart" :option="wordOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">地图</div>
</template>
<v-chart class="schart" :option="mapOptions" />
</el-card>
</div>
</template>
<script setup lang="ts" name="echarts">
import { registerMap, use } from 'echarts/core';
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
import { registerMap, use } from "echarts/core";
import { BarChart, LineChart, PieChart, MapChart } from "echarts/charts";
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import VChart from 'vue-echarts';
import 'echarts-wordcloud';
import { barOptions, lineOptions, pieOptions, ringOptions, wordOptions, mapOptions } from './options';
import chinaMap from '@/utils/china';
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import VChart from "vue-echarts";
import "echarts-wordcloud";
import {
barOptions,
lineOptions,
pieOptions,
ringOptions,
wordOptions,
mapOptions,
} from "./options";
import chinaMap from "@/utils/china";
use([
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
MapChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
MapChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
]);
registerMap('china', chinaMap);
registerMap("china", chinaMap);
</script>
<style scoped>
.schart {
width: 100%;
height: 400px;
width: 100%;
height: 400px;
}
.content-title {
font-weight: 400;
font-size: 22px;
color: #1f2f3d;
font-weight: 400;
font-size: 22px;
color: #1f2f3d;
}
</style>

View File

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

View File

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

View File

@ -1,357 +1,363 @@
<template>
<div>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<div>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="18">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">订单动态</p>
<p class="card-header-desc">最近一周订单状态包括订单成交量和订单退货量</p>
</div>
<v-chart class="chart" :option="dashOpt1" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">品类分布</p>
<p class="card-header-desc">最近一个月销售商品的品类情况</p>
</div>
<v-chart class="chart" :option="dashOpt2" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">时间线</p>
<p class="card-header-desc">最新的销售动态和活动信息</p>
</div>
<el-timeline>
<el-timeline-item v-for="(activity, index) in activities" :key="index" :color="activity.color">
<div class="timeline-item">
<div>
<p>{{ activity.content }}</p>
<p class="timeline-desc">{{ activity.description }}</p>
</div>
<div class="timeline-time">{{ activity.timestamp }}</div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
</el-col>
<el-col :span="10">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">渠道统计</p>
<p class="card-header-desc">最近一个月的订单来源统计</p>
</div>
<v-chart class="map-chart" :option="mapOptions" />
</el-card>
</el-col>
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">排行榜</p>
<p class="card-header-desc">销售商品的热门榜单Top5</p>
</div>
<div>
<div class="rank-item" v-for="(rank, index) in ranks">
<div class="rank-item-avatar">{{ index + 1 }}</div>
<div class="rank-item-content">
<div class="rank-item-top">
<div class="rank-item-title">{{ rank.title }}</div>
<div class="rank-item-desc">销量{{ rank.value }}</div>
</div>
<el-progress
:show-text="false"
striped
:stroke-width="10"
:percentage="rank.percent"
:color="rank.color"
/>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<el-row :gutter="20" class="mgb20">
<el-col :span="18">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">订单动态</p>
<p class="card-header-desc">
最近一周订单状态包括订单成交量和订单退货量
</p>
</div>
<v-chart class="chart" :option="dashOpt1" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">品类分布</p>
<p class="card-header-desc">最近一个月销售商品的品类情况</p>
</div>
<v-chart class="chart" :option="dashOpt2" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">时间线</p>
<p class="card-header-desc">最新的销售动态和活动信息</p>
</div>
<el-timeline>
<el-timeline-item
v-for="(activity, index) in activities"
:key="index"
:color="activity.color"
>
<div class="timeline-item">
<div>
<p>{{ activity.content }}</p>
<p class="timeline-desc">{{ activity.description }}</p>
</div>
<div class="timeline-time">{{ activity.timestamp }}</div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
</el-col>
<el-col :span="10">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">渠道统计</p>
<p class="card-header-desc">最近一个月的订单来源统计</p>
</div>
<v-chart class="map-chart" :option="mapOptions" />
</el-card>
</el-col>
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">排行榜</p>
<p class="card-header-desc">销售商品的热门榜单Top5</p>
</div>
<div>
<div class="rank-item" v-for="(rank, index) in ranks">
<div class="rank-item-avatar">{{ index + 1 }}</div>
<div class="rank-item-content">
<div class="rank-item-top">
<div class="rank-item-title">{{ rank.title }}</div>
<div class="rank-item-desc">销量{{ rank.value }}</div>
</div>
<el-progress
:show-text="false"
striped
:stroke-width="10"
:percentage="rank.percent"
:color="rank.color"
/>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts" name="dashboard">
import countup from '@/components/countup.vue';
import { use, registerMap } from 'echarts/core';
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
import countup from "@/components/countup.vue";
import { use, registerMap } from "echarts/core";
import { BarChart, LineChart, PieChart, MapChart } from "echarts/charts";
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import VChart from 'vue-echarts';
import { dashOpt1, dashOpt2, mapOptions } from './chart/options';
import chinaMap from '@/utils/china';
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import VChart from "vue-echarts";
import { dashOpt1, dashOpt2, mapOptions } from "./chart/options";
import chinaMap from "@/utils/china";
use([
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
MapChart,
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
MapChart,
]);
registerMap('china', chinaMap);
registerMap("china", chinaMap);
const activities = [
{
content: '收藏商品',
description: 'xxx收藏了你的商品就是不买',
timestamp: '30分钟前',
color: '#00bcd4',
},
{
content: '用户评价',
description: 'xxx给了某某商品一个差评吐血啊',
timestamp: '55分钟前',
color: '#1ABC9C',
},
{
content: '订单提交',
description: 'xxx提交了订单快去收钱吧',
timestamp: '1小时前',
color: '#3f51b5',
},
{
content: '退款申请',
description: 'xxx申请了仅退款又要亏钱了',
timestamp: '15小时前',
color: '#f44336',
},
{
content: '商品上架',
description: '运营专员瞒着你上架了一辆飞机',
timestamp: '1天前',
color: '#009688',
},
{
content: "收藏商品",
description: "xxx收藏了你的商品就是不买",
timestamp: "30分钟前",
color: "#00bcd4",
},
{
content: "用户评价",
description: "xxx给了某某商品一个差评吐血啊",
timestamp: "55分钟前",
color: "#1ABC9C",
},
{
content: "订单提交",
description: "xxx提交了订单快去收钱吧",
timestamp: "1小时前",
color: "#3f51b5",
},
{
content: "退款申请",
description: "xxx申请了仅退款又要亏钱了",
timestamp: "15小时前",
color: "#f44336",
},
{
content: "商品上架",
description: "运营专员瞒着你上架了一辆飞机",
timestamp: "1天前",
color: "#009688",
},
];
const ranks = [
{
title: '手机',
value: 10000,
percent: 80,
color: '#f25e43',
},
{
title: '电脑',
value: 8000,
percent: 70,
color: '#00bcd4',
},
{
title: '相机',
value: 6000,
percent: 60,
color: '#64d572',
},
{
title: '衣服',
value: 5000,
percent: 55,
color: '#e9a745',
},
{
title: '书籍',
value: 4000,
percent: 50,
color: '#009688',
},
{
title: "手机",
value: 10000,
percent: 80,
color: "#f25e43",
},
{
title: "电脑",
value: 8000,
percent: 70,
color: "#00bcd4",
},
{
title: "相机",
value: 6000,
percent: 60,
color: "#64d572",
},
{
title: "衣服",
value: 5000,
percent: 55,
color: "#e9a745",
},
{
title: "书籍",
value: 4000,
percent: 50,
color: "#009688",
},
];
</script>
<style>
.card-body {
display: flex;
align-items: center;
height: 100px;
padding: 0;
display: flex;
align-items: center;
height: 100px;
padding: 0;
}
</style>
<style scoped>
.card-content {
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
}
.card-num {
font-size: 30px;
font-size: 30px;
}
.card-icon {
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
}
.bg1 {
background: #2d8cf0;
background: #2d8cf0;
}
.bg2 {
background: #64d572;
background: #64d572;
}
.bg3 {
background: #f25e43;
background: #f25e43;
}
.bg4 {
background: #e9a745;
background: #e9a745;
}
.color1 {
color: #2d8cf0;
color: #2d8cf0;
}
.color2 {
color: #64d572;
color: #64d572;
}
.color3 {
color: #f25e43;
color: #f25e43;
}
.color4 {
color: #e9a745;
color: #e9a745;
}
.chart {
width: 100%;
height: 400px;
width: 100%;
height: 400px;
}
.card-header {
padding-left: 10px;
margin-bottom: 20px;
padding-left: 10px;
margin-bottom: 20px;
}
.card-header-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
.card-header-desc {
font-size: 14px;
color: #999;
font-size: 14px;
color: #999;
}
.timeline-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #000;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #000;
}
.timeline-time,
.timeline-desc {
font-size: 12px;
color: #787878;
font-size: 12px;
color: #787878;
}
.rank-item {
display: flex;
align-items: center;
margin-bottom: 20px;
display: flex;
align-items: center;
margin-bottom: 20px;
}
.rank-item-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #f2f2f2;
text-align: center;
line-height: 40px;
margin-right: 10px;
width: 40px;
height: 40px;
border-radius: 50%;
background: #f2f2f2;
text-align: center;
line-height: 40px;
margin-right: 10px;
}
.rank-item-content {
flex: 1;
flex: 1;
}
.rank-item-top {
display: flex;
justify-content: space-between;
align-items: center;
color: #343434;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
color: #343434;
margin-bottom: 10px;
}
.rank-item-desc {
font-size: 14px;
color: #999;
font-size: 14px;
color: #999;
}
.map-chart {
width: 100%;
height: 350px;
width: 100%;
height: 350px;
}
</style>

View File

@ -1,21 +1,23 @@
<template>
<div class="container">
<el-calendar v-model="value">
<template #date-cell="{ data }">
<div>{{ data.date.getDate() }}</div>
<div class="notes-container" v-if="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>
<div class="note-title">{{ note.title }}</div>
</div>
</div>
</template>
</el-calendar>
</div>
<div class="container">
<el-calendar v-model="value">
<template #date-cell="{ data }">
<div>{{ data.date.getDate() }}</div>
<div class="notes-container" v-if="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>
<div class="note-title">{{ note.title }}</div>
</div>
</div>
</template>
</el-calendar>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ref } from "vue";
const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
@ -25,58 +27,58 @@ const todayDate = today.toISOString().slice(0, 10);
const yesterdayDate = yesterday.toISOString().slice(0, 10);
const notes: any = {
[todayDate]: [
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
],
[yesterdayDate]: [{ title: '参加会议', status: 0 }],
[todayDate]: [
{ title: "吃饭", status: 1 },
{ title: "睡觉", status: 0 },
{ title: "吃饭", status: 1 },
{ title: "睡觉", status: 0 },
{ title: "吃饭", status: 1 },
{ title: "睡觉", status: 0 },
],
[yesterdayDate]: [{ title: "参加会议", status: 0 }],
};
</script>
<style scoped>
.notes-container {
height: 60px;
overflow-y: auto;
height: 60px;
overflow-y: auto;
}
.notes-container::-webkit-scrollbar {
width: 0;
width: 0;
}
.notes {
display: flex;
align-items: center;
width: 100%;
font-size: 12px;
display: flex;
align-items: center;
width: 100%;
font-size: 12px;
}
.notes:hover {
background-color: #eee;
background-color: #eee;
}
.note-title {
flex: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
flex: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.notes span {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.text-success {
background-color: #5cb85c;
background-color: #5cb85c;
}
.text-danger {
background-color: #d9534f;
background-color: #d9534f;
}
</style>

View File

@ -1,66 +1,66 @@
<template>
<div>
<el-card class="mgb20">
<template #header>基础用法</template>
<el-carousel height="400px">
<el-carousel-item v-for="item in 4" :key="item">
<h3>{{ item }}</h3>
</el-carousel-item>
</el-carousel>
</el-card>
<div>
<el-card class="mgb20">
<template #header>基础用法</template>
<el-carousel height="400px">
<el-carousel-item v-for="item in 4" :key="item">
<h3>{{ item }}</h3>
</el-carousel-item>
</el-carousel>
</el-card>
<el-row :gutter="20">
<el-col :span="12">
<el-card class="mgb20">
<template #header>轮播图</template>
<el-carousel height="300px">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="mgb20">
<template #header>卡片模式</template>
<el-carousel height="300px" type="card">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
</el-row>
</div>
<el-row :gutter="20">
<el-col :span="12">
<el-card class="mgb20">
<template #header>轮播图</template>
<el-carousel height="300px">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="mgb20">
<template #header>卡片模式</template>
<el-carousel height="300px" type="card">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
const imgs = [
'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/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/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/2024/02/21/08/06/coast-8587004_640.jpg",
"https://cdn.pixabay.com/photo/2023/07/29/10/21/grasshopper-8156626_640.jpg",
];
</script>
<style scoped>
.el-carousel__item h3 {
color: #475669;
line-height: 400px;
margin: 0;
text-align: center;
color: #475669;
line-height: 400px;
margin: 0;
text-align: center;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
background-color: #d3dce6;
}
.carousel-img {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
}
</style>

View File

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

View File

@ -1,340 +1,355 @@
<template>
<div>
<div>
<el-card class="mgb20" shadow="hover">
<template #header>基础用法</template>
<el-row>
<el-col :span="6" style="text-align: center">
<el-statistic title="Daily active users" :value="268500" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic :value="138">
<template #title>
<div style="display: inline-flex; align-items: center">
Ratio of men to women
</div>
</template>
<template #suffix>/100</template>
</el-statistic>
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic title="数字滚动" :value="outputValue" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-countdown title="倒计时" :value="value" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>CountUp.js</template>
<div class="plugins-tips">
countup.js用于快速创建以更有趣的方式显示数字数据的动画 访问地址
<a href="https://github.com/inorganik/countUp.js" target="_blank"
>countUp.js</a
>
</div>
<el-row>
<el-col :span="8" style="text-align: center">
<p>基础用法</p>
<countup class="countup" :end="6666" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>具体配置</p>
<countup class="countup" :end="8888.5" :options="options" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>更新数值</p>
<countup class="countup" :end="value1" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="never">
<template #header>统计卡片</template>
<el-card class="mgb20" shadow="hover">
<template #header>基础用法</template>
<el-row>
<el-col :span="6" style="text-align: center">
<el-statistic title="Daily active users" :value="268500" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic :value="138">
<template #title>
<div style="display: inline-flex; align-items: center">
Ratio of men to women
</div>
</template>
<template #suffix>/100</template>
</el-statistic>
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic title="数字滚动" :value="outputValue" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-countdown title="倒计时" :value="value" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>CountUp.js</template>
<div class="plugins-tips">
countup.js用于快速创建以更有趣的方式显示数字数据的动画 访问地址
<a href="https://github.com/inorganik/countUp.js" target="_blank">countUp.js</a>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color1">
<User />
</el-icon>
<div class="card-content text-right">
<el-statistic title="日活跃用户量" :value="268500" />
</div>
<el-row>
<el-col :span="8" style="text-align: center">
<p>基础用法</p>
<countup class="countup" :end="6666" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>具体配置</p>
<countup class="countup" :end="8888.5" :options="options" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>更新数值</p>
<countup class="countup" :end="value1" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="never">
<template #header>统计卡片</template>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color1">
<User />
</el-icon>
<div class="card-content text-right">
<el-statistic title="日活跃用户量" :value="268500" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
<div class="card-content text-right">
<el-statistic title="系统消息" :value="16800" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color3">
<Goods />
</el-icon>
<div class="card-content text-right">
<el-statistic title="商品数量" :value="8888" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
<div class="card-content text-right">
<el-statistic title="今日订单量" :value="56888" />
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#2d8cf0' }" title="日活跃用户量" :value="268500" />
</div>
<el-icon class="card-icon color1">
<User />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#64d572' }" title="系统消息" :value="16800" />
</div>
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#f25e43' }" title="商品数量" :value="8888" />
</div>
<el-icon class="card-icon color3">
<Goods />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#e9a745' }" title="今日订单量" :value="56888" />
</div>
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg1">
<el-icon class="card-icon ">
<User />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg2">
<el-icon class="card-icon">
<ChatDotRound />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg3">
<el-icon class="card-icon">
<Goods />
</el-icon>
<div class="card-content color0">
<countup class="card-num " :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg4">
<el-icon class="card-icon">
<ShoppingCartFull />
</el-icon>
<div class="card-content color0">
<countup class="card-num " :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
<div class="card-content text-right">
<el-statistic title="系统消息" :value="16800" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color3">
<Goods />
</el-icon>
<div class="card-content text-right">
<el-statistic title="商品数量" :value="8888" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
<div class="card-content text-right">
<el-statistic title="今日订单量" :value="56888" />
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic
:value-style="{ color: '#2d8cf0' }"
title="日活跃用户量"
:value="268500"
/>
</div>
<el-icon class="card-icon color1">
<User />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic
:value-style="{ color: '#64d572' }"
title="系统消息"
:value="16800"
/>
</div>
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic
:value-style="{ color: '#f25e43' }"
title="商品数量"
:value="8888"
/>
</div>
<el-icon class="card-icon color3">
<Goods />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic
:value-style="{ color: '#e9a745' }"
title="今日订单量"
:value="56888"
/>
</div>
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg1">
<el-icon class="card-icon">
<User />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg2">
<el-icon class="card-icon">
<ChatDotRound />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg3">
<el-icon class="card-icon">
<Goods />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg4">
<el-icon class="card-icon">
<ShoppingCartFull />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useTransition } from '@vueuse/core'
import countup from '@/components/countup.vue';
import { ref } from "vue";
import { useTransition } from "@vueuse/core";
import countup from "@/components/countup.vue";
const source = ref(0)
const source = ref(0);
const outputValue = useTransition(source, {
duration: 1500,
})
source.value = 172000
duration: 1500,
});
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);
setTimeout(() => {
value1.value = 8000;
value1.value = 8000;
}, 5000);
const options = {
startVal: 1000,
decimalPlaces: 2,
duration: 5,
useGrouping: false,
prefix: '$',
separator: ',',
decimal: '.',
suffix: '',
}
startVal: 1000,
decimalPlaces: 2,
duration: 5,
useGrouping: false,
prefix: "$",
separator: ",",
decimal: ".",
suffix: "",
};
</script>
<style>
.card-body {
display: flex;
align-items: center;
height: 100px;
padding: 0;
display: flex;
align-items: center;
height: 100px;
padding: 0;
}
.bg1 {
background: #2d8cf0;
background: #2d8cf0;
}
.bg2 {
background: #64d572;
background: #64d572;
}
.bg3 {
background: #f25e43;
background: #f25e43;
}
.bg4 {
background: #e9a745;
background: #e9a745;
}
</style>
<style scoped>
.countup {
font-size: 24px;
font-size: 24px;
}
.card-content {
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
}
.card-num {
font-size: 30px;
font-size: 30px;
}
.card-icon {
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
}
.color0 {
color: #fff;
color: #fff;
}
.color1 {
color: #2d8cf0;
color: #2d8cf0;
}
.color2 {
color: #64d572;
color: #64d572;
}
.color3 {
color: #f25e43;
color: #f25e43;
}
.color4 {
color: #e9a745;
color: #e9a745;
}
.text-right {
text-align: right;
text-align: right;
}
.text-left {
text-align: left;
text-align: left;
}
</style>
</style>

View File

@ -1,61 +1,72 @@
<template>
<div class="container">
<div class="step-div" v-if="step === 0">
<p>输入注册时的邮箱我们会发送验证码到您的邮箱</p>
<el-input placeholder="请输入邮箱"></el-input>
<el-button class="step-btn" type="primary" @click="step++"></el-button>
</div>
<div class="step-div" v-else-if="step === 1">
<p>验证码已发送至您的邮箱请输入验证码</p>
<el-input placeholder="请输入验证码"></el-input>
<el-button class="step-btn" type="primary" @click="step++"></el-button>
</div>
<div class="step-div" v-else-if="step === 2">
<p>请输入6位以上密码</p>
<el-input placeholder="请输入新密码"></el-input>
<el-button class="step-btn" type="primary" @click="step++"></el-button>
</div>
<div v-else>
<el-result icon="success" title="保存成功" sub-title="退"></el-result>
</div>
<el-steps class="step-style" :active="step" align-center finish-status="success">
<el-step title="Step 1" description="填写邮箱" />
<el-step title="Step 2" description="填写验证码" />
<el-step title="Step 3" description="修改密码" />
</el-steps>
<el-steps class="step-style" :active="step" finish-status="success" simple>
<el-step title="填写邮箱" />
<el-step title="填写验证码" />
<el-step title="修改密码" />
</el-steps>
<div class="container">
<div class="step-div" v-if="step === 0">
<p>输入注册时的邮箱我们会发送验证码到您的邮箱</p>
<el-input placeholder="请输入邮箱" />
<el-button class="step-btn" type="primary" @click="step++"
>下一步</el-button
>
</div>
<div class="step-div" v-else-if="step === 1">
<p>验证码已发送至您的邮箱请输入验证码</p>
<el-input placeholder="请输入验证码" />
<el-button class="step-btn" type="primary" @click="step++"
>下一步</el-button
>
</div>
<div class="step-div" v-else-if="step === 2">
<p>请输入6位以上密码</p>
<el-input placeholder="请输入新密码" />
<el-button class="step-btn" type="primary" @click="step++"
>保存</el-button
>
</div>
<div v-else>
<el-result icon="success" title="保存成功" sub-title="退" />
</div>
<el-steps
class="step-style"
:active="step"
align-center
finish-status="success"
>
<el-step title="Step 1" description="填写邮箱" />
<el-step title="Step 2" description="填写验证码" />
<el-step title="Step 3" description="修改密码" />
</el-steps>
<el-steps class="step-style" :active="step" finish-status="success" simple>
<el-step title="填写邮箱" />
<el-step title="填写验证码" />
<el-step title="修改密码" />
</el-steps>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const step = ref(0)
import { ref } from "vue";
const step = ref(0);
</script>
<style scoped>
.step-div {
max-width: 500px;
margin: 0 auto;
max-width: 500px;
margin: 0 auto;
}
.step-div p {
margin-bottom: 20px;
color: #787878;
margin-bottom: 20px;
color: #787878;
}
.step-btn {
display: block;
width: 100%;
margin: 20px 0;
display: block;
width: 100%;
margin: 20px 0;
}
.step-style {
max-width: 800px;
margin: 40px auto;
max-width: 800px;
margin: 40px auto;
}
</style>
</style>

View File

@ -1,116 +1,129 @@
<template>
<el-tabs v-model="message" type="card">
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
<el-table :data="state.unread" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRead(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="primary">全部标为已读</el-button>
</div>
</el-tab-pane>
<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
<template v-if="message === 'second'">
<el-table :data="state.read" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDel(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">删除全部</el-button>
</div>
</template>
</el-tab-pane>
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
<template v-if="message === 'third'">
<el-table :data="state.recycle" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRestore(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">清空回收站</el-button>
</div>
</template>
</el-tab-pane>
</el-tabs>
<el-tabs v-model="message" type="card">
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
<el-table :data="state.unread" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180" />
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRead(scope.$index)"
>标为已读</el-button
>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="primary">全部标为已读</el-button>
</div>
</el-tab-pane>
<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
<template v-if="message === 'second'">
<el-table :data="state.read" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180" />
<el-table-column width="120">
<template #default="scope">
<el-button
type="danger"
size="small"
@click="handleDel(scope.$index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">删除全部</el-button>
</div>
</template>
</el-tab-pane>
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
<template v-if="message === 'third'">
<el-table
:data="state.recycle"
:show-header="false"
style="width: 100%"
>
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180" />
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRestore(scope.$index)"
>还原</el-button
>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">清空回收站</el-button>
</div>
</template>
</el-tab-pane>
</el-tabs>
</template>
<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({
unread: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
},
{
date: '2018-04-19 21:00:00',
title: '今晚12点整发大红包先到先得'
}
],
read: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}
],
recycle: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}
]
unread: [
{
date: "2018-04-19 20:00:00",
title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
},
{
date: "2018-04-19 21:00:00",
title: "今晚12点整发大红包先到先得",
},
],
read: [
{
date: "2018-04-19 20:00:00",
title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
},
],
recycle: [
{
date: "2018-04-19 20:00:00",
title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护",
},
],
});
const handleRead = (index: number) => {
const item = state.unread.splice(index, 1);
state.read = item.concat(state.read);
const item = state.unread.splice(index, 1);
state.read = item.concat(state.read);
};
const handleDel = (index: number) => {
const item = state.read.splice(index, 1);
state.recycle = item.concat(state.recycle);
const item = state.read.splice(index, 1);
state.recycle = item.concat(state.recycle);
};
const handleRestore = (index: number) => {
const item = state.recycle.splice(index, 1);
state.read = item.concat(state.read);
const item = state.recycle.splice(index, 1);
state.read = item.concat(state.read);
};
</script>
<style>
.message-title {
cursor: pointer;
color: var(--el-color-primary);
cursor: pointer;
color: var(--el-color-primary);
}
.handle-row {
margin-top: 30px;
margin-top: 30px;
}
</style>

View File

@ -1,33 +1,45 @@
<template>
<div class="container">
<el-button type="primary" @click="open = true">开始引导</el-button>
<div class="container">
<el-button type="primary" @click="open = true">开始引导</el-button>
<el-divider />
<el-divider />
<el-space>
<el-button ref="ref1">上传</el-button>
<el-button ref="ref2" type="primary">保存</el-button>
<el-button ref="ref3" :icon="MoreFilled" />
</el-space>
<el-space>
<el-button ref="ref1">上传</el-button>
<el-button ref="ref2" type="primary">保存</el-button>
<el-button ref="ref3" :icon="MoreFilled" />
</el-space>
<el-tour v-model="open">
<el-tour-step :target="ref1?.$el" title="上传文件">
<img style="width: 120px" src="../../assets/img/img.jpg" alt="tour.png" />
<div>点击这里选择文件</div>
</el-tour-step>
<el-tour-step :target="ref2?.$el" title="保存" description="点击进行上传" />
<el-tour-step :target="ref3?.$el" title="更多操作" description="点击查看更多操作" />
</el-tour>
</div>
<el-tour v-model="open">
<el-tour-step :target="ref1?.$el" title="上传文件">
<img
style="width: 120px"
src="../../assets/img/img.jpg"
alt="tour.png"
/>
<div>点击这里选择文件</div>
</el-tour-step>
<el-tour-step
:target="ref2?.$el"
title="保存"
description="点击进行上传"
/>
<el-tour-step
:target="ref3?.$el"
title="更多操作"
description="点击查看更多操作"
/>
</el-tour>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { MoreFilled } from '@element-plus/icons-vue'
import { ref } from "vue";
import { MoreFilled } from "@element-plus/icons-vue";
const ref1 = ref()
const ref2 = ref()
const ref3 = ref()
const ref1 = ref();
const ref2 = ref();
const ref3 = ref();
const open = ref(false)
</script>
const open = ref(false);
</script>

View File

@ -1,44 +1,55 @@
<template>
<div class="container">
<div class="content-title">支持拖拽</div>
<div class="plugins-tips">
Element Plus自带上传组件 访问地址
<a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
</div>
<el-upload 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>
<div class="el-upload__text">
将文件拖到此处
<em>点击上传</em>
</div>
</el-upload>
<div class="content-title">支持裁剪</div>
<div class="plugins-tips">
vue-cropper一个简单的vue图片裁剪插件 访问地址
<a href="https://github.com/xyxiao001/vue-cropper" target="_blank">vue-cropper</a> 示例请查看
<router-link to="/ucenter">个人中心-我的头像</router-link>
</div>
<div class="container">
<div class="content-title">支持拖拽</div>
<div class="plugins-tips">
Element Plus自带上传组件 访问地址
<a
href="https://element-plus.org/zh-CN/component/upload.html"
target="_blank"
>Element Plus Upload</a
>
</div>
<el-upload
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>
<div class="el-upload__text">
将文件拖到此处
<em>点击上传</em>
</div>
</el-upload>
<div class="content-title">支持裁剪</div>
<div class="plugins-tips">
vue-cropper一个简单的vue图片裁剪插件 访问地址
<a href="https://github.com/xyxiao001/vue-cropper" target="_blank"
>vue-cropper</a
> 示例请查看
<router-link to="/ucenter">个人中心-我的头像</router-link>
</div>
</div>
</template>
<script setup lang="ts">
const handle = (rawFile: any) => {
console.log(rawFile);
console.log(rawFile);
};
</script>
<style scoped>
.content-title {
font-weight: 400;
line-height: 50px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
font-weight: 400;
line-height: 50px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
}
.upload-demo {
width: 360px;
width: 360px;
}
</style>

View File

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

View File

@ -1,27 +1,27 @@
<template>
<div class="wrapper">
<v-header />
<v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<v-tabs></v-tabs>
<div class="content">
<router-view v-slot="{ Component }">
<transition name="move" mode="out-in">
<keep-alive :include="tabs.nameList">
<component :is="Component"></component>
</keep-alive>
</transition>
</router-view>
</div>
</div>
<div class="wrapper">
<v-header />
<v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<v-tabs />
<div class="content">
<router-view v-slot="{ Component }">
<transition name="move" mode="out-in">
<keep-alive :include="tabs.nameList">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useSidebarStore } from '@/store/sidebar';
import { useTabsStore } from '@/store/tabs';
import vHeader from '@/components/header.vue';
import vSidebar from '@/components/sidebar.vue';
import vTabs from '@/components/tabs.vue';
import { useSidebarStore } from "@/store/sidebar";
import { useTabsStore } from "@/store/tabs";
import vHeader from "@/components/header.vue";
import vSidebar from "@/components/sidebar.vue";
import vTabs from "@/components/tabs.vue";
const sidebar = useSidebarStore();
const tabs = useTabsStore();
@ -29,35 +29,35 @@ const tabs = useTabsStore();
<style>
.wrapper {
height: 100vh;
overflow: hidden;
height: 100vh;
overflow: hidden;
}
.content-box {
position: absolute;
left: 250px;
right: 0;
top: 70px;
bottom: 0;
padding-bottom: 30px;
-webkit-transition: left 0.3s ease-in-out;
transition: left 0.3s ease-in-out;
background: #eef0fc;
overflow: hidden;
position: absolute;
left: 250px;
right: 0;
top: 70px;
bottom: 0;
padding-bottom: 30px;
-webkit-transition: left 0.3s ease-in-out;
transition: left 0.3s ease-in-out;
background: #eef0fc;
overflow: hidden;
}
.content {
width: auto;
height: 100%;
padding: 20px;
overflow-y: scroll;
box-sizing: border-box;
width: auto;
height: 100%;
padding: 20px;
overflow-y: scroll;
box-sizing: border-box;
}
.content::-webkit-scrollbar {
width: 0;
width: 0;
}
.content-collapse {
left: 65px;
left: 65px;
}
</style>

View File

@ -1,67 +1,69 @@
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">403</div>
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
<div class="error-page">
<div class="error-box">
<div class="error-code">403</div>
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"
>返回上一页</el-button
>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="403">
import { useRouter } from 'vue-router';
import { useRouter } from "vue-router";
const router = useRouter();
const goBack = () => {
router.go(-2);
router.go(-2);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center;
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
margin-left: 100px;
}
</style>

View File

@ -1,67 +1,69 @@
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">404</div>
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"></el-button>
</div>
</div>
<div class="error-page">
<div class="error-box">
<div class="error-code">404</div>
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack"
>返回上一页</el-button
>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="404">
import { useRouter } from 'vue-router';
import { useRouter } from "vue-router";
const router = useRouter();
const goBack = () => {
router.go(-1);
router.go(-1);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center;
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
margin-left: 100px;
}
</style>

View File

@ -1,54 +1,58 @@
<template>
<div class="container">
<div class="plugins-tips">
wangEditor轻量级 web 富文本编辑器配置方便使用简单 访问地址
<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
</div>
<div style="border: 1px solid #ccc; margin-bottom: 10px">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" />
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
@onCreated="handleCreated"
/>
</div>
<el-button type="primary" @click="syncHTML"></el-button>
<div class="container">
<div class="plugins-tips">
wangEditor轻量级 web 富文本编辑器配置方便使用简单 访问地址
<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
</div>
<div style="border: 1px solid #ccc; margin-bottom: 10px">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
/>
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
@on-created="handleCreated"
/>
</div>
<el-button type="primary" @click="syncHTML"></el-button>
</div>
</template>
<script setup lang="ts" name="editor">
import '@wangeditor/editor/dist/css/style.css'; // css
import { onBeforeUnmount, ref, reactive, shallowRef, onMounted } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import "@wangeditor/editor/dist/css/style.css"; // css
import { onBeforeUnmount, ref, reactive, shallowRef, onMounted } from "vue";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
// shallowRef
const editorRef = shallowRef();
// HTML
const valueHtml = ref('<p>hello</p>');
const valueHtml = ref("<p>hello</p>");
// ajax
onMounted(() => {
setTimeout(() => {
valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>';
}, 1500);
setTimeout(() => {
valueHtml.value = "<p>模拟 Ajax 异步设置内容</p>";
}, 1500);
});
const toolbarConfig = {};
const editorConfig = { placeholder: '请输入内容...' };
const editorConfig = { placeholder: "请输入内容..." };
//
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = (editor: any) => {
editorRef.value = editor; // editor
editorRef.value = editor; // editor
};
const syncHTML = () => {
console.log(valueHtml.value);
console.log(valueHtml.value);
};
</script>

View File

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

View File

@ -1,102 +1,117 @@
<template>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">后台管理系统</div>
</div>
<el-form :model="param" :rules="rules" ref="login" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(login)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<div class="pwd-tips">
<el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" />
<el-link type="primary" @click="$router.push('/reset-pwd')"></el-link>
</div>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(login)"></el-button>
<p class="login-tips">Tips : 用户名和密码随便填</p>
<p class="login-text">
没有账号<el-link type="primary" @click="$router.push('/register')"></el-link>
</p>
</el-form>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">后台管理系统</div>
</div>
<el-form :model="param" :rules="rules" ref="login" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(login)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<div class="pwd-tips">
<el-checkbox
class="pwd-checkbox"
v-model="checked"
label="记住密码"
/>
<el-link type="primary" @click="$router.push('/reset-pwd')"
>忘记密码</el-link
>
</div>
<el-button
class="login-btn"
type="primary"
size="large"
@click="submitForm(login)"
>登录</el-button
>
<p class="login-tips">Tips : 用户名和密码随便填</p>
<p class="login-text">
没有账号<el-link type="primary" @click="$router.push('/register')"
>立即注册</el-link
>
</p>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useTabsStore } from '@/store/tabs';
import { usePermissStore } from '@/store/permiss';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import { ref, reactive } from "vue";
import { useTabsStore } from "@/store/tabs";
import { usePermissStore } from "@/store/permiss";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
interface LoginInfo {
username: string;
password: string;
username: string;
password: string;
}
const lgStr = localStorage.getItem('login-param');
const lgStr = localStorage.getItem("login-param");
const defParam = lgStr ? JSON.parse(lgStr) : null;
const checked = ref(lgStr ? true : false);
const router = useRouter();
const param = reactive<LoginInfo>({
username: defParam ? defParam.username : '',
password: defParam ? defParam.password : '',
username: defParam ? defParam.username : "",
password: defParam ? defParam.password : "",
});
const rules: FormRules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
username: [
{
required: true,
message: "请输入用户名",
trigger: "blur",
},
],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
};
const permiss = usePermissStore();
const login = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success('登录成功');
localStorage.setItem('vuems_name', param.username);
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
permiss.handleSet(keys);
router.push('/');
if (checked.value) {
localStorage.setItem('login-param', JSON.stringify(param));
} else {
localStorage.removeItem('login-param');
}
} else {
ElMessage.error('登录失败');
return false;
}
});
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success("登录成功");
localStorage.setItem("vuems_name", param.username);
const keys =
permiss.defaultList[param.username == "admin" ? "admin" : "user"];
permiss.handleSet(keys);
router.push("/");
if (checked.value) {
localStorage.setItem("login-param", JSON.stringify(param));
} else {
localStorage.removeItem("login-param");
}
} else {
ElMessage.error("登录失败");
return false;
}
});
};
const tabs = useTabsStore();
@ -105,67 +120,67 @@ tabs.clearTabs();
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}
.login-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.logo {
width: 35px;
width: 35px;
}
.login-title {
font-size: 22px;
color: #333;
font-weight: bold;
font-size: 22px;
color: #333;
font-weight: bold;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.pwd-tips {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin: -10px 0 10px;
color: #787878;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin: -10px 0 10px;
color: #787878;
}
.pwd-checkbox {
height: auto;
height: auto;
}
.login-btn {
display: block;
width: 100%;
display: block;
width: 100%;
}
.login-tips {
font-size: 12px;
color: #999;
font-size: 12px;
color: #999;
}
.login-text {
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
}
</style>

View File

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

View File

@ -1,135 +1,143 @@
<template>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">后台管理系统</div>
</div>
<el-form :model="param" :rules="rules" ref="register" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="email">
<el-input v-model="param.email" placeholder="邮箱">
<template #prepend>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(register)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)"></el-button>
<p class="login-text">
已有账号<el-link type="primary" @click="$router.push('/login')"></el-link>
</p>
</el-form>
</div>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">后台管理系统</div>
</div>
<el-form :model="param" :rules="rules" ref="register" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="email">
<el-input v-model="param.email" placeholder="邮箱">
<template #prepend>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(register)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-button
class="login-btn"
type="primary"
size="large"
@click="submitForm(register)"
>注册</el-button
>
<p class="login-text">
已有账号<el-link type="primary" @click="$router.push('/login')"
>立即登录</el-link
>
</p>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { Register } from '@/types/user';
import { ref, reactive } from "vue";
import { useRouter } from "vue-router";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { Register } from "@/types/user";
const router = useRouter();
const param = reactive<Register>({
username: '',
password: '',
email: '',
username: "",
password: "",
email: "",
});
const rules: FormRules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
username: [
{
required: true,
message: "请输入用户名",
trigger: "blur",
},
],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
email: [{ required: true, message: "请输入邮箱", trigger: "blur" }],
};
const register = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success('注册成功,请登录');
router.push('/login');
} else {
return false;
}
});
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success("注册成功,请登录");
router.push("/login");
} else {
return false;
}
});
};
</script>
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}
.login-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.logo {
width: 35px;
width: 35px;
}
.login-title {
font-size: 22px;
color: #333;
font-weight: bold;
font-size: 22px;
color: #333;
font-weight: bold;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.login-btn {
display: block;
width: 100%;
display: block;
width: 100%;
}
.login-text {
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
}
</style>

View File

@ -1,102 +1,110 @@
<template>
<div class="login-bg">
<div class="login-container">
<div class="reset-title">重置密码</div>
<p class="reset-text">输入你的邮箱我们将发送重置密码邮件</p>
<el-form :model="param" :rules="rules" ref="register" size="large">
<el-form-item prop="email">
<el-input v-model="param.email" placeholder="邮箱">
<template #prepend>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)"
>发送邮件</el-button
>
<p class="login-text"><el-link type="primary" @click="$router.push('/login')"></el-link></p>
</el-form>
</div>
<div class="login-bg">
<div class="login-container">
<div class="reset-title">重置密码</div>
<p class="reset-text">输入你的邮箱我们将发送重置密码邮件</p>
<el-form :model="param" :rules="rules" ref="register" size="large">
<el-form-item prop="email">
<el-input v-model="param.email" placeholder="邮箱">
<template #prepend>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-button
class="login-btn"
type="primary"
size="large"
@click="submitForm(register)"
>发送邮件</el-button
>
<p class="login-text">
<el-link type="primary" @click="$router.push('/login')"
>返回登录</el-link
>
</p>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { ref } from "vue";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
const param = ref({
email: '',
email: "",
});
const rules: FormRules = {
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: '请输入正确的邮箱格式',
trigger: 'blur',
},
],
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "请输入正确的邮箱格式",
trigger: "blur",
},
],
};
const register = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success('邮件已发送,请注意查收');
} else {
return false;
}
});
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success("邮件已发送,请注意查收");
} else {
return false;
}
});
};
</script>
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}
.reset-title {
text-align: center;
font-size: 22px;
color: #333;
font-weight: bold;
margin-bottom: 10px;
text-align: center;
font-size: 22px;
color: #333;
font-weight: bold;
margin-bottom: 10px;
}
.reset-text {
text-align: center;
font-size: 14px;
color: #787878;
margin-bottom: 40px;
text-align: center;
font-size: 14px;
color: #787878;
margin-bottom: 40px;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.login-btn {
display: block;
width: 100%;
display: block;
width: 100%;
}
.login-text {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
font-size: 14px;
color: #333;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
font-size: 14px;
color: #333;
}
</style>

View File

@ -1,205 +1,220 @@
<template>
<div>
<div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">系统主题</div>
</template>
<div class="theme-list mgb20">
<div
class="theme-item"
@click="setSystemTheme(item)"
v-for="item in system"
:style="{ backgroundColor: item.color, color: '#fff' }"
>
{{ item.name }}
</div>
</div>
<div class="flex-center">
<el-button @click="resetSystemTheme"></el-button>
</div>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">Element-Plus主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item" v-for="theme in themes">
<el-button :type="theme.name">{{ theme.name }}</el-button>
<div class="theme-color">{{ theme.color }}</div>
<el-color-picker
v-model="color[theme.name]"
@change="changeColor(theme.name)"
/>
</div>
</div>
<div class="flex-center">
<el-button @click="resetTheme"></el-button>
</div>
</el-card>
<el-row :gutter="50">
<el-col :span="12">
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">系统主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item" @click="setSystemTheme(item)" v-for="item in system"
:style="{ backgroundColor: item.color, color: '#fff' }">{{ item.name }}
</div>
<template #header>
<div class="content-title">头部主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item">
<el-button :color="color.headerBgColor">背景颜色</el-button>
<div class="theme-color">{{ color.headerBgColor }}</div>
<el-color-picker
v-model="color.headerBgColor"
@change="themeStore.setHeaderBgColor(color.headerBgColor)"
/>
</div>
<div class="flex-center">
<el-button @click="resetSystemTheme"></el-button>
<div class="theme-item">
<el-button :color="color.headerTextColor">文字颜色</el-button>
<div class="theme-color">{{ color.headerTextColor }}</div>
<el-color-picker
v-model="color.headerTextColor"
@change="themeStore.setHeaderTextColor(color.headerTextColor)"
/>
</div>
</div>
<div class="flex-center">
<el-button @click="resetHeader"></el-button>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">Element-Plus主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item" v-for="theme in themes">
<el-button :type="theme.name">{{ theme.name }}</el-button>
<div class="theme-color">{{ theme.color }}</div>
<el-color-picker v-model="color[theme.name]" @change="changeColor(theme.name)" />
</div>
<template #header>
<div class="content-title">菜单主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item">
<el-button :color="sidebar.bgColor">背景颜色</el-button>
<div class="theme-color">{{ sidebar.bgColor }}</div>
<el-color-picker
v-model="sidebarColor.bgColor"
@change="sidebar.setBgColor(sidebarColor.bgColor)"
/>
</div>
<div class="flex-center">
<el-button @click="resetTheme"></el-button>
<div class="theme-item">
<el-button :color="sidebar.textColor">文字颜色</el-button>
<div class="theme-color">{{ sidebar.textColor }}</div>
<el-color-picker
v-model="sidebarColor.textColor"
@change="sidebar.setTextColor(sidebarColor.textColor)"
/>
</div>
</div>
<div class="flex-center">
<el-button @click="resetSidebar"></el-button>
</div>
</el-card>
<el-row :gutter="50">
<el-col :span="12">
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">头部主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item">
<el-button :color="color.headerBgColor">背景颜色</el-button>
<div class="theme-color">{{ color.headerBgColor }}</div>
<el-color-picker v-model="color.headerBgColor"
@change="themeStore.setHeaderBgColor(color.headerBgColor)" />
</div>
<div class="theme-item">
<el-button :color="color.headerTextColor">文字颜色</el-button>
<div class="theme-color">{{ color.headerTextColor }}</div>
<el-color-picker v-model="color.headerTextColor"
@change="themeStore.setHeaderTextColor(color.headerTextColor)" />
</div>
</div>
<div class="flex-center">
<el-button @click="resetHeader"></el-button>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">菜单主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item">
<el-button :color="sidebar.bgColor">背景颜色</el-button>
<div class="theme-color">{{ sidebar.bgColor }}</div>
<el-color-picker v-model="sidebarColor.bgColor"
@change="sidebar.setBgColor(sidebarColor.bgColor)" />
</div>
<div class="theme-item">
<el-button :color="sidebar.textColor">文字颜色</el-button>
<div class="theme-color">{{ sidebar.textColor }}</div>
<el-color-picker v-model="sidebarColor.textColor"
@change="sidebar.setTextColor(sidebarColor.textColor)" />
</div>
</div>
<div class="flex-center">
<el-button @click="resetSidebar"></el-button>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { useSidebarStore } from '@/store/sidebar';
import { useThemeStore } from '@/store/theme'
import { reactive } from 'vue';
import { useSidebarStore } from "@/store/sidebar";
import { useThemeStore } from "@/store/theme";
import { reactive } from "vue";
const themeStore = useThemeStore();
const sidebar = useSidebarStore();
const color = reactive({
primary: localStorage.getItem('theme-primary') || '#409eff',
success: localStorage.getItem('theme-success') || '#67c23a',
warning: localStorage.getItem('theme-warning') || '#e6a23c',
danger: localStorage.getItem('theme-danger') || '#f56c6c',
info: localStorage.getItem('theme-info') || '#909399',
headerBgColor: themeStore.headerBgColor,
headerTextColor: themeStore.headerTextColor,
})
primary: localStorage.getItem("theme-primary") || "#409eff",
success: localStorage.getItem("theme-success") || "#67c23a",
warning: localStorage.getItem("theme-warning") || "#e6a23c",
danger: localStorage.getItem("theme-danger") || "#f56c6c",
info: localStorage.getItem("theme-info") || "#909399",
headerBgColor: themeStore.headerBgColor,
headerTextColor: themeStore.headerTextColor,
});
const sidebarColor = reactive({
bgColor: sidebar.bgColor,
textColor: sidebar.textColor
})
bgColor: sidebar.bgColor,
textColor: sidebar.textColor,
});
const themes = [
{
name: 'primary',
color: themeStore.primary || color.primary
},
{
name: 'success',
color: themeStore.success || color.success
},
{
name: 'warning',
color: themeStore.warning || color.warning
},
{
name: 'danger',
color: themeStore.danger || color.danger
},
{
name: 'info',
color: themeStore.info || color.info
}
]
{
name: "primary",
color: themeStore.primary || color.primary,
},
{
name: "success",
color: themeStore.success || color.success,
},
{
name: "warning",
color: themeStore.warning || color.warning,
},
{
name: "danger",
color: themeStore.danger || color.danger,
},
{
name: "info",
color: themeStore.info || color.info,
},
];
const changeColor = (name: string) => {
themeStore.setPropertyColor(color[name], name)
}
themeStore.setPropertyColor(color[name], name);
};
const resetTheme = () => {
themeStore.resetTheme()
}
themeStore.resetTheme();
};
const resetHeader = () => {
localStorage.removeItem('header-bg-color')
localStorage.removeItem('header-text-color')
location.reload()
}
localStorage.removeItem("header-bg-color");
localStorage.removeItem("header-text-color");
location.reload();
};
const resetSidebar = () => {
localStorage.removeItem('sidebar-bg-color')
localStorage.removeItem('sidebar-text-color')
location.reload()
}
localStorage.removeItem("sidebar-bg-color");
localStorage.removeItem("sidebar-text-color");
location.reload();
};
const system = [
{
name: '默认',
color: '#242f42'
},
{
name: '健康',
color: '#1ABC9C'
},
{
name: '优雅',
color: '#722ed1'
},
{
name: '热情',
color: '#f44336'
},
{
name: '宁静',
color: '#00bcd4'
}
]
{
name: "默认",
color: "#242f42",
},
{
name: "健康",
color: "#1ABC9C",
},
{
name: "优雅",
color: "#722ed1",
},
{
name: "热情",
color: "#f44336",
},
{
name: "宁静",
color: "#00bcd4",
},
];
const setSystemTheme = (data: any) => {
if (data.name === '默认') {
resetSystemTheme()
} else {
themeStore.setHeaderBgColor(data.color)
themeStore.setHeaderTextColor('#fff')
sidebar.setBgColor('#fff')
sidebar.setTextColor('#5b6e88')
themeStore.setPropertyColor(data.color, 'primary')
}
}
if (data.name === "默认") {
resetSystemTheme();
} else {
themeStore.setHeaderBgColor(data.color);
themeStore.setHeaderTextColor("#fff");
sidebar.setBgColor("#fff");
sidebar.setTextColor("#5b6e88");
themeStore.setPropertyColor(data.color, "primary");
}
};
const resetSystemTheme = () => {
resetTheme();
resetHeader();
resetSidebar();
}
resetTheme();
resetHeader();
resetSidebar();
};
</script>
<style scoped>
.theme-list {
display: flex;
justify-content: center;
display: flex;
justify-content: center;
}
.theme-item {
margin-right: 20px;
padding: 30px;
border: 1px solid #dcdfe6;
border-radius: 4px;
text-align: center;
margin-right: 20px;
padding: 30px;
border: 1px solid #dcdfe6;
border-radius: 4px;
text-align: center;
}
.theme-color {
color: #787878;
margin: 20px 0;
color: #787878;
margin: 20px 0;
}
</style>

View File

@ -1,270 +1,289 @@
<template>
<div>
<div class="user-container">
<el-card class="user-profile" shadow="hover" :body-style="{ padding: '0px' }">
<div class="user-profile-bg"></div>
<div class="user-avatar-wrap">
<el-avatar class="user-avatar" :size="120" :src="avatarImg" />
</div>
<div class="user-info">
<div class="info-name">{{ name }}</div>
<div class="info-desc">
<span>@lin-xin</span>
<el-divider direction="vertical" />
<el-link href="https://lin-xin.gitee.io" target="_blank">lin-xin.gitee.io</el-link>
</div>
<div class="info-desc">FE Developer</div>
<div class="info-icon">
<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-facebook-fill"></i>
<i class="el-icon-lx-twitter-fill"></i>
</div>
</div>
<div class="user-footer">
<div class="user-footer-item">
<el-statistic title="Follower" :value="1800" />
</div>
<div class="user-footer-item">
<el-statistic title="Following" :value="666" />
</div>
<div class="user-footer-item">
<el-statistic title="Total Post" :value="888" />
</div>
</div>
</el-card>
<el-card
class="user-content"
shadow="hover"
:body-style="{ padding: '20px 50px', height: '100%', boxSizing: 'border-box' }"
>
<el-tabs tab-position="left" v-model="activeName">
<el-tab-pane name="label1" label="消息通知" class="user-tabpane">
<TabsComp />
</el-tab-pane>
<el-tab-pane name="label2" label="我的头像" class="user-tabpane">
<div class="crop-wrap" v-if="activeName === 'label2'">
<vueCropper
ref="cropper"
:img="imgSrc"
:autoCrop="true"
:centerBox="true"
:full="true"
mode="contain"
>
</vueCropper>
</div>
<el-button class="crop-demo-btn" type="primary"
>选择图片
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
</el-button>
<el-button type="success" @click="saveAvatar"></el-button>
</el-tab-pane>
<el-tab-pane name="label3" label="修改密码" class="user-tabpane">
<el-form class="w500" label-position="top">
<el-form-item label="旧密码:">
<el-input type="password" v-model="form.old"></el-input>
</el-form-item>
<el-form-item label="新密码:">
<el-input type="password" v-model="form.new"></el-input>
</el-form-item>
<el-form-item label="确认新密码:">
<el-input type="password" v-model="form.new1"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit"></el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane name="label4" label="赞赏作者" class="user-tabpane">
<div class="plugins-tips">
如果该框架
<el-link href="https://github.com/lin-xin/vue-manage-system" target="_blank"
>vue-manage-system</el-link
>
对你有帮助那就请作者喝杯饮料吧<el-icon>
<ColdDrink />
</el-icon>
加微信号 linxin_20 探讨问题
</div>
<div>
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
<div>
<div class="user-container">
<el-card
class="user-profile"
shadow="hover"
:body-style="{ padding: '0px' }"
>
<div class="user-profile-bg"></div>
<div class="user-avatar-wrap">
<el-avatar class="user-avatar" :size="120" :src="avatarImg" />
</div>
<div class="user-info">
<div class="info-name">{{ name }}</div>
<div class="info-desc">
<span>@lin-xin</span>
<el-divider direction="vertical" />
<el-link href="https://lin-xin.gitee.io" target="_blank"
>lin-xin.gitee.io</el-link
>
</div>
<div class="info-desc">FE Developer</div>
<div class="info-icon">
<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-facebook-fill"></i>
<i class="el-icon-lx-twitter-fill"></i>
</div>
</div>
<div class="user-footer">
<div class="user-footer-item">
<el-statistic title="Follower" :value="1800" />
</div>
<div class="user-footer-item">
<el-statistic title="Following" :value="666" />
</div>
<div class="user-footer-item">
<el-statistic title="Total Post" :value="888" />
</div>
</div>
</el-card>
<el-card
class="user-content"
shadow="hover"
:body-style="{
padding: '20px 50px',
height: '100%',
boxSizing: 'border-box',
}"
>
<el-tabs tab-position="left" v-model="activeName">
<el-tab-pane name="label1" label="消息通知" class="user-tabpane">
<TabsComp />
</el-tab-pane>
<el-tab-pane name="label2" label="我的头像" class="user-tabpane">
<div class="crop-wrap" v-if="activeName === 'label2'">
<vueCropper
ref="cropper"
:img="imgSrc"
:autoCrop="true"
:centerBox="true"
:full="true"
mode="contain"
/>
</div>
<el-button class="crop-demo-btn" type="primary"
>选择图片
<input
class="crop-input"
type="file"
name="image"
accept="image/*"
@change="setImage"
/>
</el-button>
<el-button type="success" @click="saveAvatar"></el-button>
</el-tab-pane>
<el-tab-pane name="label3" label="修改密码" class="user-tabpane">
<el-form class="w500" label-position="top">
<el-form-item label="旧密码:">
<el-input type="password" v-model="form.old" />
</el-form-item>
<el-form-item label="新密码:">
<el-input type="password" v-model="form.new" />
</el-form-item>
<el-form-item label="确认新密码:">
<el-input type="password" v-model="form.new1" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit"></el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane name="label4" label="赞赏作者" class="user-tabpane">
<div class="plugins-tips">
如果该框架
<el-link
href="https://github.com/lin-xin/vue-manage-system"
target="_blank"
>vue-manage-system</el-link
>
对你有帮助那就请作者喝杯饮料吧<el-icon>
<ColdDrink />
</el-icon>
加微信号 linxin_20 探讨问题
</div>
<div>
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="ucenter">
import { reactive, ref } from 'vue';
import { VueCropper } from 'vue-cropper';
import 'vue-cropper/dist/index.css';
import avatar from '@/assets/img/img.jpg';
import TabsComp from '../element/tabs.vue';
import { reactive, ref } from "vue";
import { VueCropper } from "vue-cropper";
import "vue-cropper/dist/index.css";
import avatar from "@/assets/img/img.jpg";
import TabsComp from "../element/tabs.vue";
const name = localStorage.getItem('vuems_name');
const name = localStorage.getItem("vuems_name");
const form = reactive({
new1: '',
new: '',
old: '',
new1: "",
new: "",
old: "",
});
const onSubmit = () => {};
const activeName = ref('label1');
const activeName = ref("label1");
const avatarImg = ref(avatar);
const imgSrc = ref(avatar);
const cropImg = ref('');
const cropImg = ref("");
const cropper: any = ref();
const setImage = (e: any) => {
const file = e.target.files[0];
if (!file.type.includes('image/')) {
return;
}
const reader = new FileReader();
reader.onload = (event: any) => {
imgSrc.value = event.target.result;
cropper.value && cropper.value.replace(event.target.result);
};
reader.readAsDataURL(file);
const file = e.target.files[0];
if (!file.type.includes("image/")) {
return;
}
const reader = new FileReader();
reader.onload = (event: any) => {
imgSrc.value = event.target.result;
cropper.value && cropper.value.replace(event.target.result);
};
reader.readAsDataURL(file);
};
const cropImage = () => {
cropImg.value = cropper.value?.getCroppedCanvas().toDataURL();
cropImg.value = cropper.value?.getCroppedCanvas().toDataURL();
};
const saveAvatar = () => {
avatarImg.value = cropImg.value;
avatarImg.value = cropImg.value;
};
</script>
<style scoped>
.user-container {
display: flex;
display: flex;
}
.user-profile {
position: relative;
position: relative;
}
.user-profile-bg {
width: 100%;
height: 200px;
background-image: url('../../assets/img/ucenter-bg.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 100%;
height: 200px;
background-image: url("../../assets/img/ucenter-bg.jpg");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.user-profile {
width: 500px;
margin-right: 20px;
flex: 0 0 auto;
align-self: flex-start;
width: 500px;
margin-right: 20px;
flex: 0 0 auto;
align-self: flex-start;
}
.user-avatar-wrap {
position: absolute;
top: 135px;
width: 100%;
text-align: center;
position: absolute;
top: 135px;
width: 100%;
text-align: center;
}
.user-avatar {
border: 5px solid #fff;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 7px 12px 0 rgba(62, 57, 107, 0.16);
border: 5px solid #fff;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 7px 12px 0 rgba(62, 57, 107, 0.16);
}
.user-info {
text-align: center;
padding: 80px 0 30px;
text-align: center;
padding: 80px 0 30px;
}
.info-name {
margin: 0 0 20px;
font-size: 22px;
font-weight: 500;
color: #373a3c;
margin: 0 0 20px;
font-size: 22px;
font-weight: 500;
color: #373a3c;
}
.info-desc {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 5px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 5px;
}
.info-desc,
.info-desc a {
font-size: 18px;
color: #55595c;
font-size: 18px;
color: #55595c;
}
.info-icon {
margin-top: 10px;
margin-top: 10px;
}
.info-icon i {
font-size: 30px;
margin: 0 10px;
color: #343434;
font-size: 30px;
margin: 0 10px;
color: #343434;
}
.user-content {
flex: 1;
flex: 1;
}
.user-tabpane {
padding: 10px 20px;
padding: 10px 20px;
}
.crop-wrap {
width: 600px;
height: 350px;
margin-bottom: 20px;
width: 600px;
height: 350px;
margin-bottom: 20px;
}
.crop-demo-btn {
position: relative;
position: relative;
}
.crop-input {
position: absolute;
width: 100px;
height: 40px;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
position: absolute;
width: 100px;
height: 40px;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
}
.w500 {
width: 500px;
width: 500px;
}
.user-footer {
display: flex;
border-top: 1px solid rgba(83, 70, 134, 0.1);
display: flex;
border-top: 1px solid rgba(83, 70, 134, 0.1);
}
.user-footer-item {
padding: 20px 0;
width: 33.3333333333%;
text-align: center;
padding: 20px 0;
width: 33.3333333333%;
text-align: center;
}
.user-footer > div + div {
border-left: 1px solid rgba(83, 70, 134, 0.1);
border-left: 1px solid rgba(83, 70, 134, 0.1);
}
</style>
<style>
.el-tabs.el-tabs--left {
height: 100%;
height: 100%;
}
</style>

View File

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

View File

@ -1,28 +1,28 @@
<template>
<div>
<el-tree
class="mgb10"
ref="tree"
:data="data"
node-key="id"
default-expand-all
show-checkbox
:default-checked-keys="checkedKeys"
/>
<el-button type="primary" @click="onSubmit"></el-button>
</div>
<div>
<el-tree
class="mgb10"
ref="tree"
:data="data"
node-key="id"
default-expand-all
show-checkbox
:default-checked-keys="checkedKeys"
/>
<el-button type="primary" @click="onSubmit"></el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElTree } from 'element-plus';
import { menuData } from '@/components/menu';
import { ref } from "vue";
import { ElTree } from "element-plus";
import { menuData } from "@/components/menu";
const props = defineProps({
permissOptions: {
type: Object,
required: true,
},
permissOptions: {
type: Object,
required: true,
},
});
const menuObj = ref({});
@ -43,23 +43,26 @@ const menuObj = ref({});
// });
const getTreeData = (data) => {
return data.map((item) => {
const obj: any = {
id: item.id,
label: item.title,
};
if (item.children) {
menuObj.value[item.id] = item.children.map((sub) => sub.id);
obj.children = getTreeData(item.children);
}
return obj;
});
return data.map((item) => {
const obj: any = {
id: item.id,
label: item.title,
};
if (item.children) {
menuObj.value[item.id] = item.children.map((sub) => sub.id);
obj.children = getTreeData(item.children);
}
return obj;
});
};
const data = getTreeData(menuData);
const checkData = (data: string[]) => {
return data.filter((item) => {
return !menuObj.value[item] || data.toString().includes(menuObj.value[item].toString());
});
return data.filter((item) => {
return (
!menuObj.value[item] ||
data.toString().includes(menuObj.value[item].toString())
);
});
};
//
const checkedKeys = ref<string[]>(checkData(props.permissOptions.permiss));
@ -67,9 +70,12 @@ const checkedKeys = ref<string[]>(checkData(props.permissOptions.permiss));
//
const tree = ref<InstanceType<typeof ElTree>>();
const onSubmit = () => {
//
const keys = [...tree.value!.getCheckedKeys(false), ...tree.value!.getHalfCheckedKeys()] as number[];
console.log(keys);
//
const keys = [
...tree.value!.getCheckedKeys(false),
...tree.value!.getHalfCheckedKeys(),
] as number[];
console.log(keys);
};
</script>

View File

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

View File

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

View File

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

View File

@ -1,98 +1,103 @@
<template>
<div>
<div class="container">
<div class="handle-box">
<el-button type="primary" @click="exportXlsx">Excel</el-button>
</div>
<el-table :data="tableData" border class="table" header-cell-class-name="table-header">
<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
<el-table-column prop="name" label="姓名"></el-table-column>
<el-table-column prop="sno" label="学号"></el-table-column>
<el-table-column prop="class" label="班级"></el-table-column>
<el-table-column prop="age" label="年龄"></el-table-column>
<el-table-column prop="sex" label="性别"></el-table-column>
</el-table>
</div>
<div>
<div class="container">
<div class="handle-box">
<el-button type="primary" @click="exportXlsx">Excel</el-button>
</div>
<el-table
:data="tableData"
border
class="table"
header-cell-class-name="table-header"
>
<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>
</div>
</div>
</template>
<script setup lang="ts" name="export">
import { ref } from 'vue';
import * as XLSX from 'xlsx';
import { ref } from "vue";
import * as XLSX from "xlsx";
interface TableItem {
id: number;
name: string;
sno: string;
class: string;
age: string;
sex: string;
id: number;
name: string;
sno: string;
class: string;
age: string;
sex: string;
}
const tableData = ref<TableItem[]>([]);
//
const getData = () => {
tableData.value = [
{
id: 1,
name: '小明',
sno: 'S001',
class: '一班',
age: '10',
sex: '男',
},
{
id: 2,
name: '小红',
sno: 'S002',
class: '一班',
age: '9',
sex: '女',
},
];
tableData.value = [
{
id: 1,
name: "小明",
sno: "S001",
class: "一班",
age: "10",
sex: "男",
},
{
id: 2,
name: "小红",
sno: "S002",
class: "一班",
age: "9",
sex: "女",
},
];
};
getData();
const list = [['序号', '姓名', '学号', '班级', '年龄', '性别']];
const list = [["序号", "姓名", "学号", "班级", "年龄", "性别"]];
const exportXlsx = () => {
tableData.value.map((item: any, i: number) => {
const arr: any[] = [i + 1];
arr.push(...[item.name, item.sno, item.class, item.age, item.sex]);
list.push(arr);
});
let WorkSheet = XLSX.utils.aoa_to_sheet(list);
let new_workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页');
XLSX.writeFile(new_workbook, `表格.xlsx`);
tableData.value.map((item: any, i: number) => {
const arr: any[] = [i + 1];
arr.push(...[item.name, item.sno, item.class, item.age, item.sex]);
list.push(arr);
});
let WorkSheet = XLSX.utils.aoa_to_sheet(list);
let new_workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(new_workbook, WorkSheet, "第一页");
XLSX.writeFile(new_workbook, `表格.xlsx`);
};
</script>
<style scoped>
.handle-box {
margin-bottom: 20px;
margin-bottom: 20px;
}
.handle-select {
width: 120px;
width: 120px;
}
.handle-input {
width: 300px;
width: 300px;
}
.table {
width: 100%;
font-size: 14px;
width: 100%;
font-size: 14px;
}
.red {
color: #f56c6c;
color: #f56c6c;
}
.table-td-thumb {
display: block;
margin: auto;
width: 40px;
height: 40px;
display: block;
margin: auto;
width: 40px;
height: 40px;
}
</style>

View File

@ -1,109 +1,120 @@
<template>
<div>
<div class="container">
<div class="handle-box">
<el-upload 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-upload>
<el-link href="/template.xlsx" target="_blank">下载模板</el-link>
</div>
<el-table :data="tableData" border class="table" header-cell-class-name="table-header">
<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
<el-table-column prop="name" label="姓名"></el-table-column>
<el-table-column prop="sno" label="学号"></el-table-column>
<el-table-column prop="class" label="班级"></el-table-column>
<el-table-column prop="age" label="年龄"></el-table-column>
<el-table-column prop="sex" label="性别"></el-table-column>
</el-table>
</div>
<div>
<div class="container">
<div class="handle-box">
<el-upload
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-upload>
<el-link href="/template.xlsx" target="_blank">下载模板</el-link>
</div>
<el-table
:data="tableData"
border
class="table"
header-cell-class-name="table-header"
>
<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>
</div>
</div>
</template>
<script setup lang="ts" name="import">
import { UploadProps } from 'element-plus';
import { ref, reactive } from 'vue';
import * as XLSX from 'xlsx';
import { UploadProps } from "element-plus";
import { ref, reactive } from "vue";
import * as XLSX from "xlsx";
interface TableItem {
id: number;
name: string;
sno: string;
class: string;
age: string;
sex: string;
id: number;
name: string;
sno: string;
class: string;
age: string;
sex: string;
}
const tableData = ref<TableItem[]>([]);
//
const getData = () => {
tableData.value = [
{
id: 1,
name: '小明',
sno: 'S001',
class: '一班',
age: '10',
sex: '男',
},
{
id: 2,
name: '小红',
sno: 'S002',
class: '一班',
age: '9',
sex: '女',
},
];
tableData.value = [
{
id: 1,
name: "小明",
sno: "S001",
class: "一班",
age: "10",
sex: "男",
},
{
id: 2,
name: "小红",
sno: "S002",
class: "一班",
age: "9",
sex: "女",
},
];
};
getData();
const importList = ref<any>([]);
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
importList.value = await analysisExcel(rawFile);
return true;
const beforeUpload: UploadProps["beforeUpload"] = async (rawFile) => {
importList.value = await analysisExcel(rawFile);
return true;
};
const analysisExcel = (file: any) => {
return new Promise(function (resolve, reject) {
const reader = new FileReader();
reader.onload = function (e: any) {
const data = e.target.result;
let datajson = XLSX.read(data, {
type: 'binary',
});
return new Promise(function (resolve, reject) {
const reader = new FileReader();
reader.onload = function (e: any) {
const data = e.target.result;
let datajson = XLSX.read(data, {
type: "binary",
});
const sheetName = datajson.SheetNames[0];
const result = XLSX.utils.sheet_to_json(datajson.Sheets[sheetName]);
resolve(result);
};
reader.readAsBinaryString(file);
});
const sheetName = datajson.SheetNames[0];
const result = XLSX.utils.sheet_to_json(datajson.Sheets[sheetName]);
resolve(result);
};
reader.readAsBinaryString(file);
});
};
const handleMany = async () => {
//
const list = importList.value.map((item: any, index: number) => {
return {
id: index,
name: item['姓名'],
sno: item['学号'],
class: item['班级'],
age: item['年龄'],
sex: item['性别'],
};
});
tableData.value.push(...list);
//
const list = importList.value.map((item: any, index: number) => {
return {
id: index,
name: item["姓名"],
sno: item["学号"],
class: item["班级"],
age: item["年龄"],
sex: item["性别"],
};
});
tableData.value.push(...list);
};
</script>
<style scoped>
.handle-box {
display: flex;
margin-bottom: 20px;
display: flex;
margin-bottom: 20px;
}
.table {
width: 100%;
font-size: 14px;
width: 100%;
font-size: 14px;
}
</style>

View File

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

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

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

View File

@ -1,22 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": false,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": false,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
}

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 vue from '@vitejs/plugin-vue';
import VueSetupExtend from 'vite-plugin-vue-setup-extend';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
base: './',
plugins: [
vue(),
VueSetupExtend(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
],
optimizeDeps: {
include: ['schart.js']
},
resolve: {
alias: {
'@': '/src',
'~': '/src/assets'
}
},
define: {
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "true",
},
import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import VueSetupExtend from "vite-plugin-vue-setup-extend";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
const env = loadEnv(mode, process.cwd());
return {
base: "./",
plugins: [
vue(),
VueSetupExtend(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
optimizeDeps: {
include: ["schart.js"],
},
resolve: {
alias: {
"@": "/src",
"~": "/src/assets",
},
},
define: {
__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), ""),
},
},
},
};
});

4462
yarn.lock

File diff suppressed because it is too large Load Diff