初始项目,完成显示验证码

pull/751/head
huppygo 2025-03-24 22:22:38 +08:00
parent b232296d17
commit c6e9644882
379 changed files with 54005 additions and 0 deletions

5
.browserslistrc Normal file
View File

@ -0,0 +1,5 @@
Android 4.1
IOS 7.1
Chrome > 31
ff > 31
ie >= 11

32
.env.base Normal file
View File

@ -0,0 +1,32 @@
# 环境
VITE_NODE_ENV=development
# 接口前缀
VITE_API_BASE_PATH=http://39.104.23.25:48080/admin-api/
# 打包路径
VITE_BASE_PATH=/
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=true
# 是否开启mock
VITE_USE_MOCK=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false
# 是否启用租户功能
VITE_APP_TENANT_ENABLE=true
# 默认租户名称
VITE_APP_DEFAULT_LOGIN_TENANT=芋道源码
#开启验证码
VITE_APP_CAPTCHA_ENABLE=true

45
.env.dev Normal file
View File

@ -0,0 +1,45 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=http://39.104.23.25:48080/admin-api/
# 打包路径
VITE_BASE_PATH=/dist-dev/
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
# 输出路径
VITE_OUT_DIR=dist-dev
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false
#开启验证码
VITE_APP_CAPTCHA_ENABLE=true

44
.env.gitee Normal file
View File

@ -0,0 +1,44 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=http://39.104.23.25:48080/admin-api/
# 打包路径
VITE_BASE_PATH=/vue-element-plus-admin/
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 输出路径
VITE_OUT_DIR=dist-pro
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false
#开启验证码
VITE_APP_CAPTCHA_ENABLE=true

44
.env.pro Normal file
View File

@ -0,0 +1,44 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=http://39.104.23.25:48080/admin-api/
# 打包路径
VITE_BASE_PATH=/
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 输出路径
VITE_OUT_DIR=dist-pro
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=true
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=false
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false
#开启验证码
VITE_APP_CAPTCHA_ENABLE=true

44
.env.test Normal file
View File

@ -0,0 +1,44 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=http://39.104.23.25:48080/admin-api/
# 打包路径
VITE_BASE_PATH=/dist-test/
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
# 输出路径
VITE_OUT_DIR=dist-test
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=false
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false
#开启验证码
VITE_APP_CAPTCHA_ENABLE=true

131
.github/workflows/auto-merge.yml vendored Normal file
View File

@ -0,0 +1,131 @@
name: Automerge
on:
pull_request:
types:
- labeled
- unlabeled
- synchronize
- opened
- edited
- ready_for_review
- reopened
- unlocked
pull_request_review:
types:
- submitted
status: {}
jobs:
# 合并发布版本的 pr 到 master
auto-merge:
runs-on: ubuntu-latest
steps:
- name: Automerge
uses: 'pascalgn/automerge-action@v0.14.3'
env:
BASE_BRANCHES: 'release'
GITHUB_TOKEN: '${{ secrets.TOKEN }}'
MERGE_LABELS: ''
MERGE_FILTER_AUTHOR: 'kailong321200875'
push-to-gh-pages:
needs: [auto-merge]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: use Node.js 18
uses: actions/setup-node@v2.1.2
with:
node-version: '18.x'
- name: Set SSH Environment
env:
DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com > ~/.ssh/known_hosts
chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
git config --local user.email "321200875@qq.com"
git config --local user.name "kailong321200875"
# 发布到 github
- name: Build Github
run: |
pnpm install --no-frozen-lockfile
pnpm run build:pro
- name: Deploy Github
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}}
publish_branch: gh-pages
publish_dir: ./dist-pro
force_orphan: true
cname: element-plus-admin.cn
push-to-gh-pages-gitee:
needs: [auto-merge]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: use Node.js 18
uses: actions/setup-node@v2.1.2
with:
node-version: '18.x'
- name: Set SSH Environment
env:
DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com > ~/.ssh/known_hosts
chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
git config --local user.email "321200875@qq.com"
git config --local user.name "kailong321200875"
- name: Build Gitee
run: |
pnpm install --no-frozen-lockfile
pnpm run build:gitee
# 发布到 gitee
- name: Deploy Gitee
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}}
publish_branch: gh-pages-gitee
publish_dir: ./dist-pro
force_orphan: true
- name: Sync Github Repos To Gitee # 名字随便起
uses: Yikun/hub-mirror-action@v1.1 # 使用Yikun/hub-mirror-action
with:
src: github/kailong321200875 # 源端账户名(github)
dst: gitee/kailong110120130 # 目的端账户名(gitee)
dst_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} # SSH密钥对中的私钥
dst_token: ${{ secrets.GITEE_TOKEN }} # Gitee账户的私人令牌
account_type: user # 账户类型
clone_style: 'https' # 使用https方式进行clone也可以使用ssh
debug: true # 启用后会显示所有执行命令
force_update: true # 启用后,强制同步,即强制覆盖目的端仓库
static_list: 'vue-element-plus-admin' # 静态同步列表,在此填写需要同步的仓库名称,可填写多个
timeout: '600s' # git超时设置超时后会自动重试git操作

19
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,19 @@
on:
push:
branches:
- release
name: Release
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v3
id: release
with:
token: ${{ secrets.TOKEN }}
default-branch: release
release-type: node
package-name: standard-version
changelog-types: '[{"type": "types", "section":"Types", "hidden": false},{"type": "revert", "section":"Reverts", "hidden": false},{"type": "feat", "section": "Features", "hidden": false},{"type": "fix", "section": "Bug Fixes", "hidden": false},{"type": "improvement", "section": "Feature Improvements", "hidden": false},{"type": "docs", "section":"Docs", "hidden": false},{"type": "style", "section":"Styling", "hidden": false},{"type": "refactor", "section":"Code Refactoring", "hidden": false},{"type": "perf", "section":"Performance Improvements", "hidden": false},{"type": "test", "section":"Tests", "hidden": false},{"type": "build", "section":"Build System", "hidden": false},{"type": "ci", "section":"CI", "hidden":false}]'

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
/dist*
*-lock.*
pnpm-debug
stats.html
.idea
.vtj/histories

1
.husky/commit-msg Normal file
View File

@ -0,0 +1 @@
npx --no -- commitlint --edit $1

9
.husky/lintstagedrc.cjs Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --parser json --write'],
'package.json': ['prettier --write'],
'*.vue': ['prettier --write', 'stylelint --fix'],
'*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write'],
'*.md': ['prettier --write'],
'*.hbs': ['prettier --write']
}

2
.husky/pre-commit Normal file
View File

@ -0,0 +1,2 @@
npm run ts:check
npm run lint:lint-staged

9
.prettierignore Normal file
View File

@ -0,0 +1,9 @@
/node_modules/**
/dist/
/dist*
/public/*
/docs/*
/src/types/env.d.ts
/docs/**/*
/plop/**/*
CHANGELOG

6
.stylelintignore Normal file
View File

@ -0,0 +1,6 @@
/dist/*
/public/*
public/*
/dist*
/src/types/env.d.ts
/docs/**/*

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["vue.volar", "lokalise.i18n-ally"]
}

19
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"prettier.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"god.tsconfig": "./tsconfig.json"
}

313
.vtj/files/7pdk4mxyf.json Normal file
View File

@ -0,0 +1,313 @@
{
"name": "Users",
"locked": false,
"inject": [],
"state": {
"keyword": {
"type": "JSExpression",
"value": "''"
}
},
"lifeCycles": {},
"methods": {
"loader": {
"type": "JSFunction",
"value": "(data) => {\n return this.getData(data)\n}"
},
"search": {
"type": "JSFunction",
"value": "() => {\n this.$refs.gridRef.search()\n}"
},
"rowAction": {
"type": "JSFunction",
"value": "(row) => {\n this.$message.info('click row id: ' + row.id)\n}"
}
},
"computed": {
"columns": {
"type": "JSFunction",
"value": "() => {\n return [\n {\n type: 'checkbox',\n width: 40\n },\n {\n type: 'seq',\n width: 60,\n title: '序号'\n },\n {\n field: 'name',\n title: '名称',\n minWidth: 100\n },\n {\n field: 'label',\n title: '描述',\n minWidth: 100\n },\n {\n field: 'isDefault',\n title: '默认角色',\n minWidth: 100,\n slots: {\n default: 'isDefault'\n }\n },\n {\n field: 'order',\n title: '排序',\n minWidth: 100,\n sortable: true\n },\n {\n field: 'desc',\n title: '备注',\n minWidth: 100\n },\n {\n field: 'createdAt',\n title: '创建时间',\n minWidth: 140\n },\n {\n field: 'actions',\n title: '操作',\n width: 160,\n slots: {\n default: 'actions'\n }\n }\n ]\n}"
}
},
"watch": [],
"css": "",
"props": [],
"emits": [],
"slots": [],
"dataSources": {
"getData": {
"name": "getData",
"test": {
"type": "JSFunction",
"value": "() => this.runApi({\n /* 在这里可输入接口参数 */\n})"
},
"type": "mock",
"label": "模拟数据",
"mockTemplate": {
"type": "JSFunction",
"value": "(params) => {\n const { page = 1, pageSize = 50 } = params || {};\n const total = 1000;\n return {\n [`list|${pageSize}`]: [{\n id: '@guid',\n name: '@cname',\n label: '@ctitle',\n 'isDefault|1': '@boolean',\n 'order|+1': 1,\n desc: '@cparagraph',\n createdAt: '@date(yyyy-MM-dd hh:mm:ss)'\n }],\n total,\n page,\n limit: pageSize\n };\n}"
}
}
},
"__VTJ_BLOCK__": true,
"__VERSION__": "1736401534792",
"id": "7pdk4mxyf",
"nodes": [
{
"id": "1kqd4haeckr",
"name": "XGrid",
"from": "@vtj/ui",
"invisible": false,
"locked": false,
"children": [
{
"id": "1mp9b7mk9wj",
"name": "ElInput",
"from": "element-plus",
"invisible": false,
"locked": false,
"slot": {
"name": "toolbar__buttons",
"params": []
},
"children": [
{
"id": "2ax97lz9cox",
"name": "ElButton",
"from": "element-plus",
"invisible": false,
"locked": false,
"slot": {
"name": "append",
"params": []
},
"children": "查询",
"props": {
"type": "primary"
},
"directives": [],
"events": {
"click": {
"name": "click",
"handler": {
"type": "JSFunction",
"value": "this.search"
},
"modifiers": {}
}
}
}
],
"props": {
"style": {
"width": "300px",
"margin-right": "10px"
},
"placeholder": "请输入查询关键字"
},
"directives": [
{
"id": "l36jhkywal",
"name": "vModel",
"arg": "modelValue",
"value": {
"type": "JSExpression",
"value": "this.state.keyword"
}
}
],
"events": {}
},
{
"id": "2ammdsu07o6",
"name": "ElButton",
"from": "element-plus",
"invisible": false,
"locked": false,
"slot": {
"name": "toolbar__buttons",
"params": []
},
"children": "新增",
"props": {
"icon": "Plus",
"type": "primary"
},
"directives": [],
"events": {
"click": {
"name": "click",
"handler": {
"type": "JSFunction",
"value": "() => {\n this.$message.info('click add button')\n}"
},
"modifiers": {}
}
}
},
{
"id": "257nvaelda4",
"name": "ElButton",
"from": "element-plus",
"invisible": false,
"locked": false,
"slot": {
"name": "toolbar__buttons",
"params": []
},
"children": "批量删除",
"props": {
"icon": "Delete",
"type": "danger"
},
"directives": [],
"events": {
"click": {
"name": "click",
"handler": {
"type": "JSFunction",
"value": "() => {\n const rows = this.$refs.gridRef.getSelected();\n if (rows.length) {\n this.$message.info('delete data rows: ' + rows.length);\n } else {\n this.$message.warning('请选择需要删除的数据项');\n }\n}"
},
"modifiers": {}
}
}
},
{
"id": "o52fisb3kt",
"name": "ElTag",
"from": "element-plus",
"invisible": false,
"locked": false,
"slot": {
"name": "isDefault",
"params": []
},
"children": {
"type": "JSExpression",
"value": "this.context.scope_1kqd4haeckr.row.isDefault ? '是' : '否'"
},
"props": {
"type": {
"type": "JSExpression",
"value": "this.context.scope_1kqd4haeckr.row.isDefault ? 'success' : 'info'"
}
},
"directives": [],
"events": {}
},
{
"id": "1ft4np8i6i1",
"name": "XAction",
"from": "@vtj/ui",
"invisible": false,
"locked": false,
"slot": {
"name": "actions",
"params": []
},
"children": [],
"props": {
"mode": "icon",
"size": "default",
"type": "primary",
"label": "编辑"
},
"directives": [],
"events": {
"click": {
"name": "click",
"handler": {
"type": "JSFunction",
"value": "() => {\n this.rowAction(this.context.scope_1kqd4haeckr.row)\n}"
},
"modifiers": {}
}
}
},
{
"id": "1e8ewprm6ts",
"name": "XAction",
"from": "@vtj/ui",
"invisible": false,
"locked": false,
"slot": {
"name": "actions",
"params": []
},
"children": [],
"props": {
"mode": "icon",
"size": "default",
"type": "primary",
"label": "设置"
},
"directives": [],
"events": {
"click": {
"name": "click",
"handler": {
"type": "JSFunction",
"value": "() => {\n this.rowAction(this.context.scope_1kqd4haeckr.row)\n}"
},
"modifiers": {}
}
}
},
{
"id": "2a1cq8bhwej",
"name": "XAction",
"from": "@vtj/ui",
"invisible": false,
"locked": false,
"slot": {
"name": "actions",
"params": []
},
"children": [],
"props": {
"mode": "icon",
"size": "default",
"type": "danger",
"label": "删除"
},
"directives": [],
"events": {
"click": {
"name": "click",
"handler": {
"type": "JSFunction",
"value": "async () => {\n const ret = await this.$confirm('确定删除?', '提示', { type: 'warning' }).catch(() => false);\n if (ret) {\n this.rowAction(this.context.scope_1kqd4haeckr.row)\n }\n}"
},
"modifiers": {}
}
}
}
],
"props": {
"ref": "gridRef",
"pager": true,
"height": "auto",
"loader": {
"type": "JSExpression",
"value": "this.loader"
},
"columns": {
"type": "JSExpression",
"value": "this.columns.value"
},
"resizable": true,
"customable": true,
"style": {
"background-color": "#FFFFFF",
"padding-top": "10px",
"padding-left": "10px",
"padding-right": "10px",
"padding-bottom": "10px",
"border-radius": "4px 4px 4px 4px"
}
},
"directives": [],
"events": {}
}
]
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,179 @@
{
"__VTJ_PROJECT__": true,
"id": "vue-element-plus-admin",
"name": "ElementAdmin",
"homepage": "",
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
"dependencies": [
{
"package": "vue",
"version": "latest",
"library": "Vue",
"urls": ["@vtj/materials/deps/vue/vue.global.prod.js"],
"assetsLibrary": "VueMaterial",
"required": true,
"official": true,
"enabled": true
},
{
"package": "vue-router",
"version": "latest",
"library": "VueRouter",
"urls": ["@vtj/materials/deps/vue-router/vue-router.global.prod.js"],
"assetsLibrary": "VueRouterMaterial",
"required": true,
"official": true,
"enabled": true
},
{
"package": "@vtj/utils",
"version": "latest",
"library": "VtjUtils",
"urls": ["@vtj/materials/deps/@vtj/utils/index.umd.js"],
"required": true,
"official": true,
"enabled": true
},
{
"package": "@vtj/icons",
"version": "latest",
"library": "VtjIcons",
"urls": [
"@vtj/materials/deps/@vtj/icons/style.css",
"@vtj/materials/deps/@vtj/icons/index.umd.js"
],
"required": true,
"official": true,
"enabled": true
},
{
"package": "@vueuse/core",
"version": "latest",
"library": "VueUse",
"urls": [
"@vtj/materials/deps/@vueuse/shared/index.iife.min.js",
"@vtj/materials/deps/@vueuse/core/index.iife.min.js"
],
"required": false,
"official": true,
"enabled": true
},
{
"package": "element-plus",
"version": "latest",
"library": "ElementPlus",
"localeLibrary": "ElementPlusLocaleZhCn",
"urls": [
"@vtj/materials/deps/element-plus/dark/css-vars.css",
"@vtj/materials/deps/element-plus/index.css",
"@vtj/materials/deps/element-plus/zh-cn.js",
"@vtj/materials/deps/element-plus/index.full.min.js"
],
"assetsUrl": "@vtj/materials/assets/element/index.umd.js",
"assetsLibrary": "ElementPlusMaterial",
"required": false,
"official": true,
"enabled": true
},
{
"package": "@vtj/ui",
"version": "latest",
"library": "VtjUI",
"urls": [
"@vtj/materials/deps/vxe-table/style.min.css",
"@vtj/materials/deps/@vtj/ui/style.css",
"@vtj/materials/deps/xe-utils/xe-utils.umd.min.js",
"@vtj/materials/deps/vxe-table/index.umd.min.js",
"@vtj/materials/deps/@vtj/ui/index.umd.js"
],
"assetsUrl": "@vtj/materials/assets/ui/index.umd.js",
"assetsLibrary": "VtjUIMaterial",
"required": false,
"official": true,
"enabled": true
},
{
"package": "@vtj/charts",
"version": "latest",
"library": "VtjCharts",
"urls": [
"@vtj/materials/deps/echarts/echarts.min.js",
"@vtj/materials/deps/@vtj/charts/index.umd.js"
],
"assetsUrl": "@vtj/materials/assets/charts/index.umd.js",
"assetsLibrary": "VtjChartsMaterial",
"required": false,
"official": true,
"enabled": true
},
{
"package": "ant-design-vue",
"version": "latest",
"library": "antd",
"urls": [
"@vtj/materials/deps/ant-design-vue/reset.css",
"@vtj/materials/deps/ant-design-vue/dayjs/dayjs.min.js",
"@vtj/materials/deps/ant-design-vue/dayjs/plugin/customParseFormat.js",
"@vtj/materials/deps/ant-design-vue/dayjs/plugin/weekday.js",
"@vtj/materials/deps/ant-design-vue/dayjs/plugin/localeData.js",
"@vtj/materials/deps/ant-design-vue/dayjs/plugin/weekOfYear.js",
"@vtj/materials/deps/ant-design-vue/dayjs/plugin/weekYear.js",
"@vtj/materials/deps/ant-design-vue/dayjs/plugin/advancedFormat.js",
"@vtj/materials/deps/ant-design-vue/dayjs/plugin/quarterOfYear.js",
"@vtj/materials/deps/ant-design-vue/antd.min.js"
],
"assetsUrl": "@vtj/materials/assets/antdv/index.umd.js",
"assetsLibrary": "AntdvMaterial",
"required": false,
"official": true,
"enabled": false
},
{
"package": "vant",
"version": "latest",
"library": "vant",
"urls": ["@vtj/materials/deps/vant/index.css", "@vtj/materials/deps/vant/vant.min.js"],
"assetsUrl": "@vtj/materials/assets/vant/index.umd.js",
"assetsLibrary": "VantMaterial",
"required": false,
"official": true,
"enabled": false
}
],
"pages": [
{
"dir": true,
"name": "System",
"title": "系统管理",
"icon": "ChromeFilled",
"hidden": false,
"prue": false,
"id": "45rmet3ni",
"type": "page",
"children": [
{
"dir": false,
"name": "Users",
"title": "用户管理",
"icon": "",
"mask": true,
"hidden": false,
"raw": false,
"prue": false,
"meta": "",
"cache": false,
"id": "7pdk4mxyf",
"type": "page",
"market": {
"id": "0290db4d-f64e-4119-b62d-1e0b56193d6f"
}
}
]
}
],
"blocks": [],
"apis": [],
"meta": [],
"config": {},
"__BASE_PATH__": "/"
}

222
.vtj/vue/7pdk4mxyf.vue Normal file
View File

@ -0,0 +1,222 @@
<template>
<XGrid
ref="gridRef"
:pager="true"
height="auto"
:loader="loader"
:columns="columns"
:resizable="true"
:customable="true"
class="XGrid_1kqd4haeckr"
>
<template #toolbar__buttons="scope_1kqd4haeckr">
<ElInput
v-model:modelValue="state.keyword"
placeholder="请输入查询关键字"
class="ElInput_1mp9b7mk9wj"
>
<template #append="scope_1mp9b7mk9wj">
<ElButton type="primary" @click="search"> </ElButton>
</template></ElInput
>
<ElButton
icon="Plus"
type="primary"
@click="(...args: any[]) => click_2ammdsu07o6({ scope_1kqd4haeckr }, args)"
>
新增</ElButton
>
<ElButton
icon="Delete"
type="danger"
@click="(...args: any[]) => click_257nvaelda4({ scope_1kqd4haeckr }, args)"
>
批量删除</ElButton
>
</template>
<template #isDefault="scope_1kqd4haeckr">
<ElTag :type="scope_1kqd4haeckr.row.isDefault ? 'success' : 'info'">
{{ scope_1kqd4haeckr.row.isDefault ? '是' : '否' }}</ElTag
>
</template>
<template #actions="scope_1kqd4haeckr">
<XAction
mode="icon"
size="default"
type="primary"
label="编辑"
@click="(...args: any[]) => click_1ft4np8i6i1({ scope_1kqd4haeckr }, args)"
></XAction>
<XAction
mode="icon"
size="default"
type="primary"
label="设置"
@click="(...args: any[]) => click_1e8ewprm6ts({ scope_1kqd4haeckr }, args)"
></XAction>
<XAction
mode="icon"
size="default"
type="danger"
label="删除"
@click="(...args: any[]) => click_2a1cq8bhwej({ scope_1kqd4haeckr }, args)"
></XAction> </template
></XGrid>
</template>
<script lang="ts">
// @ts-nocheck
import { defineComponent, reactive } from 'vue'
import { XGrid, XAction } from '@vtj/ui'
import { ElInput, ElButton, ElTag } from 'element-plus'
import { useProvider } from '@vtj/renderer'
export default defineComponent({
name: 'Users',
components: { XGrid, ElInput, ElButton, ElTag, XAction },
setup(props) {
const provider = useProvider({ id: '7pdk4mxyf', version: '1736401534792' })
const state = reactive<Record<string, any>>({ keyword: '' })
return { state, props, provider }
},
computed: {
columns() {
return [
{
type: 'checkbox',
width: 40
},
{
type: 'seq',
width: 60,
title: '序号'
},
{
field: 'name',
title: '名称',
minWidth: 100
},
{
field: 'label',
title: '描述',
minWidth: 100
},
{
field: 'isDefault',
title: '默认角色',
minWidth: 100,
slots: {
default: 'isDefault'
}
},
{
field: 'order',
title: '排序',
minWidth: 100,
sortable: true
},
{
field: 'desc',
title: '备注',
minWidth: 100
},
{
field: 'createdAt',
title: '创建时间',
minWidth: 140
},
{
field: 'actions',
title: '操作',
width: 160,
slots: {
default: 'actions'
}
}
]
}
},
methods: {
async getData(...args: any[]) {
const mock = this.provider.createMock((params) => {
const { page = 1, pageSize = 50 } = params || {}
const total = 1000
return {
[`list|${pageSize}`]: [
{
id: '@guid',
name: '@cname',
label: '@ctitle',
'isDefault|1': '@boolean',
'order|+1': 1,
desc: '@cparagraph',
createdAt: '@date(yyyy-MM-dd hh:mm:ss)'
}
],
total,
page,
limit: pageSize
}
})
return await mock.apply(this, args)
},
click_2ammdsu07o6({ scope_1kqd4haeckr }, args) {
return (() => {
this.$message.info('click add button')
}).apply(this, args)
},
click_257nvaelda4({ scope_1kqd4haeckr }, args) {
return (() => {
const rows = this.$refs.gridRef.getSelected()
if (rows.length) {
this.$message.info('delete data rows: ' + rows.length)
} else {
this.$message.warning('请选择需要删除的数据项')
}
}).apply(this, args)
},
click_1ft4np8i6i1({ scope_1kqd4haeckr }, args) {
return (() => {
this.rowAction(scope_1kqd4haeckr.row)
}).apply(this, args)
},
click_1e8ewprm6ts({ scope_1kqd4haeckr }, args) {
return (() => {
this.rowAction(scope_1kqd4haeckr.row)
}).apply(this, args)
},
click_2a1cq8bhwej({ scope_1kqd4haeckr }, args) {
return (async () => {
const ret = await this.$confirm('确定删除?', '提示', {
type: 'warning'
}).catch(() => false)
if (ret) {
this.rowAction(scope_1kqd4haeckr.row)
}
}).apply(this, args)
},
loader(data) {
return this.getData(data)
},
search() {
this.$refs.gridRef.search()
},
rowAction(row) {
this.$message.info('click row id: ' + row.id)
}
}
})
</script>
<style lang="scss" scoped>
.XGrid_1kqd4haeckr {
background-color: #ffffff;
padding-top: 10px;
padding-left: 10px;
padding-right: 10px;
padding-bottom: 10px;
border-radius: 4px 4px 4px 4px;
}
.ElInput_1mp9b7mk9wj {
width: 300px;
margin-right: 10px;
}
</style>

1391
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

13
Dockerfile.dev Normal file
View File

@ -0,0 +1,13 @@
FROM node:18.0.0
WORKDIR /app
RUN npm install -g pnpm@8.1.0
COPY package.json .
RUN pnpm install
COPY . .
CMD [ "pnpm", "run", "dev" ]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present Archer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
README.md Normal file
View File

@ -0,0 +1,22 @@
获取代码
git clone
安装依赖
cd vue-element-plus-admin
pnpm config set registry https://registry.npmmirror.com/
pnpm install
运行
pnpm run dev
打包
pnpm run build:pro
问题
1.集成到本地项目 本地启动运行可以看到设计器入口
设计器是在开发环境提供辅助开发服务的,不是在线的
在线参考 https://gitee.com/newgateway/vtj/tree/master/lcdp
npm install -g lerna@latest pnpm@latest --registry=https://registry.npmmirror.com
2.关于下载依赖报错error (ERR_INVALID_THIS)
pnpm升级到 8.3.1
npm install -g pnpm@8.3.1

148
README.zh-CN.md Normal file
View File

@ -0,0 +1,148 @@
<div align="center"> <a href="https://github.com/kailong321200875/vue-element-plus-admin"> <img width="100" src="./public/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) [![repo-size](https://img.shields.io/github/repo-size/kailong321200875/vue-element-plus-admin.svg)](repo-size) [![last-commit](https://img.shields.io/github/last-commit/kailong321200875/vue-element-plus-admin.svg)](last-commit) [![stars](https://img.shields.io/github/stars/kailong321200875/vue-element-plus-admin.svg)](stars) [![forks](https://img.shields.io/github/forks/kailong321200875/vue-element-plus-admin.svg)](forks) [![release](https://img.shields.io/github/release/kailong321200875/vue-element-plus-admin.svg)](release) [![watchers](https://img.shields.io/github/watchers/kailong321200875/vue-element-plus-admin.svg)](watchers)
<h1>vue-element-plus-admin</h1>
</div>
[English](./README.md) | **中文**
## 介绍
vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台模版。使用了最新的`vue3``vite``TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。
vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。
如需要基础模版,请切换到 `mini` 分支,`mini` 只简单集成了一些如:布局、动态菜单等常用布局功能,更适合开发者进行二次开发。
## 特性
- **最新技术栈**:使用 Vue3/vite4 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**: 可配置的主题
- **国际化**:内置完善的国际化方案
- **自定义数据** 内置 Mock 数据方案
- **权限** 内置完善的动态路由权限生成方案
- **组件** 二次封装了多个常用的组件
- **示例** 内置丰富的示例
## 预览
- [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点
- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点
帐号:**admin/admin**
在线例子默认不适用菜单过滤,而是直接使用静态路由表
## 文档
[文档地址 Github](https://element-plus-admin-doc.cn/)
[文档地址 Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc)
## 前序准备
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) - 项目开发环境
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法
- [TypeScript](https://www.typescriptlang.org/) - 熟悉 `TypeScript` 基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Element-Plus](https://element-plus.org/) - element-plus 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 安装和使用
- 获取代码
```bash
git clone https://github.com/kailong321200875/vue-element-plus-admin.git
```
- 安装依赖
```bash
cd vue-element-plus-admin
pnpm install
```
- 运行
```bash
pnpm run dev
```
- 打包
```bash
pnpm run build:pro
```
## 更新日志
[更新日志](./CHANGELOG.md)
## 如何贡献
<a href="https://github.com/kailong321200875/vue-element-plus-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=kailong321200875/vue-element-plus-admin" />
</a>
你可以[提一个 issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) 或者提交一个 Pull Request。
**Pull Request:**
1. Fork 代码
2. 创建自己的分支: `git checkout -b feat/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feat/xxxx`
5. 提交 `pull request`
## Git 贡献提交规范
- `feat` 新功能
- `fix` 修补 bug
- `docs` 文档
- `style` 格式、样式(不影响代码运行的变动)
- `refactor` 重构(即不是新增功能,也不是修改 BUG 的代码)
- `perf` 优化相关,比如提升性能、体验
- `test` 添加测试
- `build` 编译相关的修改,对项目构建或者依赖的改动
- `ci` 持续集成修改
- `chore` 构建过程或辅助工具的变动
- `revert` 回滚到上一个版本
- `workflow` 工作流改进
- `mod` 不确定分类的修改
- `wip` 开发中
- `types` 类型
## 浏览器支持
本地开发推荐使用 `Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Donate
如果你觉得这个项目有帮助,欢迎赞助以示支持~
[Paypal Me](https://www.paypal.com/paypalme/ckl94)
<img src="https://gitee.com/kailong110120130/my-image/raw/master/pay.jpg" />
### 我的二维码
如有项目合作或项目外包,扫码加我好友,请备注来意。
<img src="https://gitee.com/kailong110120130/my-image/raw/master/me.jpg" />
## 许可证
[MIT](./LICENSE)

28
commitlint.config.cjs Normal file
View File

@ -0,0 +1,28 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // 新功能(feature)
'fix', // 修补bug
'docs', // 文档(documentation)
'style', // 格式、样式(不影响代码运行的变动)
'refactor', // 重构(即不是新增功能也不是修改BUG的代码)
'perf', // 优化相关,比如提升性能、体验
'test', // 添加测试
'ci', // 持续集成修改
'chore', // 构建过程或辅助工具的变动
'revert', // 回滚到上一个版本
'workflow', // 工作流改进
'mod', // 不确定分类的修改
'wip', // 开发中
'types', // 类型修改
'release' // 版本发布
]
],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never']
}
}

10
docker-compose.dev.yaml Normal file
View File

@ -0,0 +1,10 @@
services:
vue-element-plus-admin:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "4000:4000"
volumes:
- /app/node_modules
- .:/app

81
eslint.config.mjs Normal file
View File

@ -0,0 +1,81 @@
// 引入vue模版的eslint
import pluginVue from 'eslint-plugin-vue'
import eslint from '@eslint/js'
// ts-eslint解析器使 eslint 可以解析 ts 语法
import tseslint from 'typescript-eslint'
// vue文件解析器
import vueParser from 'vue-eslint-parser'
import prettier from 'eslint-plugin-prettier'
export default tseslint.config({
// ignores: ['node_modules', 'prettier.config.cjs', 'dist*'],
files: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
// tseslint.config添加了extends扁平函数直接用。否则是eslint9.0版本是没有extends的
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/essential']
],
plugins: {
prettier
},
languageOptions: {
parser: vueParser, // 使用vue解析器这个可以识别vue文件
parserOptions: {
parser: tseslint.parser, // 在vue文件上使用ts解析器
sourceType: 'module',
ecmaVersion: 2020,
ecmaFeatures: {
jsx: true
}
}
},
rules: {
'prettier/prettier': 'error',
'no-useless-escape': 0,
'no-undef': 0,
'vue/no-setup-props-destructure': 0,
'vue/script-setup-uses-vars': 1,
'vue/no-reserved-component-names': 0,
'@typescript-eslint/ban-ts-ignore': 0,
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-empty-function': 0,
'vue/custom-event-name-casing': 0,
'no-use-before-define': 0,
'@typescript-eslint/no-use-before-define': 0,
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-unused-vars': 0,
'no-unused-vars': 0,
'space-before-function-paren': 0,
'vue/attributes-order': 0,
'vue/one-component-per-file': 0,
'vue/html-closing-bracket-newline': 0,
'vue/max-attributes-per-line': 0,
'vue/multiline-html-element-content-newline': 0,
'vue/singleline-html-element-content-newline': 0,
'vue/attribute-hyphenation': 0,
'vue/require-default-prop': 0,
'vue/require-explicit-emits': 0,
'vue/html-self-closing': [
1,
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 0,
'vue/no-v-html': 0,
'vue/require-toggle-inside-transition': 0
}
})

142
index.html Normal file
View File

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title>
</head>
<body>
<div id="app">
<style>
.app-loading {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background: #f0f2f5;
}
.app-loading .app-loading-wrap {
position: absolute;
top: 50%;
left: 50%;
display: flex;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
justify-content: center;
align-items: center;
flex-direction: column;
}
.app-loading .app-loading-title {
margin-bottom: 30px;
font-size: 20px;
font-weight: bold;
text-align: center;
}
.app-loading .app-loading-logo {
width: 100px;
margin: 0 auto 15px auto;
}
.app-loading .app-loading-item {
position: relative;
display: inline-block;
width: 60px;
height: 60px;
vertical-align: middle;
border-radius: 50%;
}
.app-loading .app-loading-outter {
position: absolute;
width: 100%;
height: 100%;
border: 4px solid #2d8cf0;
border-bottom: 0;
border-left-color: transparent;
border-radius: 50%;
animation: loader-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
}
.app-loading .app-loading-inner {
position: absolute;
top: calc(50% - 20px);
left: calc(50% - 20px);
width: 40px;
height: 40px;
border: 4px solid #87bdff;
border-right: 0;
border-top-color: transparent;
border-radius: 50%;
animation: loader-inner 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
}
@-webkit-keyframes loader-outter {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes loader-outter {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes loader-inner {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
@keyframes loader-inner {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
</style>
<div class="app-loading">
<div class="app-loading-wrap">
<div class="app-loading-title">
<img src="/logo.png" class="app-loading-logo" alt="Logo" />
<div class="app-loading-title"><%= title %></div>
</div>
<div class="app-loading-item">
<div class="app-loading-outter"></div>
<div class="app-loading-inner"></div>
</div>
</div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,18 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
const modules = import.meta.glob('./**/*.mock.ts', {
import: 'default',
eager: true
})
const mockModules: any[] = []
Object.keys(modules).forEach(async (key) => {
if (key.includes('_')) {
return
}
mockModules.push(...(modules[key] as any))
})
export function setupProdMockServer() {
createProdMockServer(mockModules)
}

View File

@ -0,0 +1,87 @@
import { SUCCESS_CODE } from '@/constants'
import { MockMethod } from 'vite-plugin-mock'
const timeout = 1000
export default [
// 分析页统计接口
{
url: '/mock/analysis/total',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: {
users: 102400,
messages: 81212,
moneys: 9280,
shoppings: 13600
}
}
}
},
// 用户来源
{
url: '/mock/analysis/userAccessSource',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ value: 1000, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
}
}
},
// 每周用户活跃量
{
url: '/mock/analysis/weeklyUserActivity',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
}
}
},
// 每月销售额
{
url: '/mock/analysis/monthlySales',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ estimate: 100, actual: 120, name: 'analysis.january' },
{ estimate: 120, actual: 82, name: 'analysis.february' },
{ estimate: 161, actual: 91, name: 'analysis.march' },
{ estimate: 134, actual: 154, name: 'analysis.april' },
{ estimate: 105, actual: 162, name: 'analysis.may' },
{ estimate: 160, actual: 140, name: 'analysis.june' },
{ estimate: 165, actual: 145, name: 'analysis.july' },
{ estimate: 114, actual: 250, name: 'analysis.august' },
{ estimate: 163, actual: 134, name: 'analysis.september' },
{ estimate: 185, actual: 56, name: 'analysis.october' },
{ estimate: 118, actual: 99, name: 'analysis.november' },
{ estimate: 123, actual: 123, name: 'analysis.december' }
]
}
}
}
] as MockMethod[]

View File

@ -0,0 +1,206 @@
import { toAnyString } from '@/utils'
import Mock from 'mockjs'
import { SUCCESS_CODE } from '@/constants'
const departmentList: any = []
const citys = ['厦门总公司', '北京分公司', '上海分公司', '福州分公司', '深圳分公司', '杭州分公司']
for (let i = 0; i < 5; i++) {
departmentList.push({
// 部门名称
departmentName: citys[i],
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)',
children: [
{
// 部门名称
departmentName: '研发部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '产品部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '运营部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '市场部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '销售部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '客服部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
}
]
})
}
export default [
// 列表接口
{
url: '/mock/department/list',
method: 'get',
response: () => {
return {
code: SUCCESS_CODE,
data: {
list: departmentList
}
}
}
},
{
url: '/mock/department/table/list',
method: 'get',
response: () => {
return {
code: SUCCESS_CODE,
data: {
list: departmentList,
total: 5
}
}
}
},
{
url: '/mock/department/users',
method: 'get',
timeout: 1000,
response: ({ query }) => {
const { pageSize } = query
// 根据pageSize来创建数据
const mockList: any = []
for (let i = 0; i < pageSize; i++) {
mockList.push(
Mock.mock({
// 用户名
username: '@cname',
// 账号
account: '@first',
// 邮箱
email: '@EMAIL',
// 创建时间
createTime: '@datetime',
// 用户id
id: toAnyString()
})
)
}
return {
code: SUCCESS_CODE,
data: {
total: 100,
list: mockList
}
}
}
},
// 保存接口
{
url: '/mock/department/user/save',
method: 'post',
timeout: 1000,
response: () => {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
},
// 删除接口
{
url: '/mock/department/user/delete',
method: 'post',
response: ({ body }) => {
const ids = body.ids
if (!ids) {
return {
code: 500,
message: '请选择需要删除的数据'
}
} else {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
},
// 保存接口
{
url: '/mock/department/save',
method: 'post',
timeout: 1000,
response: () => {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
},
// 删除接口
{
url: '/mock/department/delete',
method: 'post',
response: ({ body }) => {
const ids = body.ids
if (!ids) {
return {
code: 500,
message: '请选择需要删除的数据'
}
} else {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
}
]

60
mock/dict/index.mock.ts Normal file
View File

@ -0,0 +1,60 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
const dictObj: Recordable = {
importance: [
{
value: 0,
label: 'tableDemo.commonly'
},
{
value: 1,
label: 'tableDemo.good'
},
{
value: 2,
label: 'tableDemo.important'
}
]
}
export default [
// 字典接口
{
url: '/mock/dict/list',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: dictObj
}
}
},
// 获取某个字典
{
url: '/mock/dict/one',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
label: 'test1',
value: 0
},
{
label: 'test2',
value: 1
},
{
label: 'test3',
value: 2
}
]
}
}
}
]

357
mock/menu/index.mock.ts Normal file
View File

@ -0,0 +1,357 @@
import Mock from 'mockjs'
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
export default [
// 列表接口
{
url: '/mock/menu/list',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: {
list: [
{
path: '/dashboard',
component: '#',
redirect: '/dashboard/analysis',
name: 'Dashboard',
status: Mock.Random.integer(0, 1),
id: 1,
type: 0,
parentId: undefined,
title: '首页',
meta: {
title: '首页',
icon: 'vi-ant-design:dashboard-filled',
alwaysShow: true
},
children: [
{
path: 'analysis',
component: 'views/Dashboard/Analysis',
name: 'Analysis',
status: Mock.Random.integer(0, 1),
id: 2,
type: 1,
parentId: 1,
title: '分析页',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
}
],
meta: {
title: '分析页',
noCache: true,
permission: ['add', 'edit']
}
},
{
path: 'workplace',
component: 'views/Dashboard/Workplace',
name: 'Workplace',
status: Mock.Random.integer(0, 1),
id: 3,
type: 1,
parentId: 1,
title: '工作台',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
},
{
id: 3,
label: '删除',
value: 'delete'
}
],
meta: {
title: '工作台',
noCache: true
}
}
]
},
{
path: '/external-link',
component: '#',
meta: {
title: '文档',
icon: 'vi-clarity:document-solid'
},
name: 'ExternalLink',
status: Mock.Random.integer(0, 1),
id: 4,
type: 0,
parentId: undefined,
title: '文档',
children: [
{
path: 'https://element-plus-admin-doc.cn/',
name: 'DocumentLink',
status: Mock.Random.integer(0, 1),
id: 5,
type: 1,
parentId: 4,
title: '文档',
meta: {
title: '文档'
}
}
]
},
{
path: '/level',
component: '#',
redirect: '/level/menu1/menu1-1/menu1-1-1',
name: 'Level',
status: Mock.Random.integer(0, 1),
id: 6,
type: 0,
parentId: undefined,
title: '菜单',
meta: {
title: '菜单',
icon: 'vi-carbon:skill-level-advanced'
},
children: [
{
path: 'menu1',
name: 'Menu1',
component: '##',
status: Mock.Random.integer(0, 1),
id: 7,
type: 0,
parentId: 6,
title: '菜单1',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: {
title: '菜单1'
},
children: [
{
path: 'menu1-1',
name: 'Menu11',
component: '##',
status: Mock.Random.integer(0, 1),
id: 8,
type: 0,
parentId: 7,
title: '菜单1-1',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: {
title: '菜单1-1',
alwaysShow: true
},
children: [
{
path: 'menu1-1-1',
name: 'Menu111',
component: 'views/Level/Menu111',
status: Mock.Random.integer(0, 1),
id: 9,
type: 1,
parentId: 8,
title: '菜单1-1-1',
meta: {
title: '菜单1-1-1'
}
}
]
},
{
path: 'menu1-2',
name: 'Menu12',
component: 'views/Level/Menu12',
status: Mock.Random.integer(0, 1),
id: 10,
type: 1,
parentId: 7,
title: '菜单1-2',
meta: {
title: '菜单1-2'
}
}
]
},
{
path: 'menu2',
name: 'Menu2Demo',
component: 'views/Level/Menu2',
status: Mock.Random.integer(0, 1),
id: 11,
type: 1,
parentId: 6,
title: '菜单2',
meta: {
title: '菜单2'
}
}
]
},
{
path: '/example',
component: '#',
redirect: '/example/example-dialog',
name: 'Example',
status: Mock.Random.integer(0, 1),
id: 12,
type: 0,
parentId: undefined,
title: '综合示例',
meta: {
title: '综合示例',
icon: 'vi-ep:management',
alwaysShow: true
},
children: [
{
path: 'example-dialog',
component: 'views/Example/Dialog/ExampleDialog',
name: 'ExampleDialog',
status: Mock.Random.integer(0, 1),
id: 13,
type: 1,
parentId: 12,
title: '综合示例-弹窗',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
},
{
id: 3,
label: '删除',
value: 'delete'
},
{
id: 4,
label: '查看',
value: 'view'
}
],
meta: {
title: '综合示例-弹窗'
}
},
{
path: 'example-page',
component: 'views/Example/Page/ExamplePage',
name: 'ExamplePage',
status: Mock.Random.integer(0, 1),
id: 14,
type: 1,
parentId: 12,
title: '综合示例-页面',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
},
{
id: 3,
label: '删除',
value: 'delete'
},
{
id: 4,
label: '查看',
value: 'view'
}
],
meta: {
title: '综合示例-页面'
}
},
{
path: 'example-add',
component: 'views/Example/Page/ExampleAdd',
name: 'ExampleAdd',
status: Mock.Random.integer(0, 1),
id: 15,
type: 1,
parentId: 12,
title: '综合示例-新增',
meta: {
title: '综合示例-新增',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example/example-page'
}
},
{
path: 'example-edit',
component: 'views/Example/Page/ExampleEdit',
name: 'ExampleEdit',
status: Mock.Random.integer(0, 1),
id: 16,
type: 1,
parentId: 12,
title: '综合示例-编辑',
meta: {
title: '综合示例-编辑',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example/example-page'
}
},
{
path: 'example-detail',
component: 'views/Example/Page/ExampleDetail',
name: 'ExampleDetail',
status: Mock.Random.integer(0, 1),
id: 17,
type: 1,
parentId: 12,
title: '综合示例-详情',
meta: {
title: '综合示例-详情',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example/example-page'
}
}
]
}
]
}
}
}
}
]

View File

@ -0,0 +1,72 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 600000
export default [
{
url: '/mock/request/1',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-1'
}
}
},
{
url: '/mock/request/2',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-2'
}
}
},
{
url: '/mock/request/3',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-3'
}
}
},
{
url: '/mock/request/4',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-4'
}
}
},
{
url: '/mock/request/5',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-5'
}
}
},
{
url: '/mock/request/expired',
method: 'get',
timeout: 0,
response: () => {
return {
code: 401,
message: 'token expired'
}
}
}
]

1227
mock/role/index.mock.ts Normal file

File diff suppressed because it is too large Load Diff

319
mock/table/index.mock.ts Normal file
View File

@ -0,0 +1,319 @@
import Mock from 'mockjs'
import { SUCCESS_CODE } from '@/constants'
import { toAnyString } from '@/utils'
const timeout = 1000
const count = 100
const baseContent =
'<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
interface ListProps {
id: string
author: string
title: string
content: string
importance: number
display_time: any
pageviews: number
image_uri: string
video_uri?: string
}
interface TreeListProps {
id: string
author: string
title: string
content: string
importance: number
display_time: any
image_uri: string
pageviews: number
video_uri?: string
children?: TreeListProps[]
}
let List: ListProps[] = []
for (let i = 0; i < count; i++) {
List.push(
Mock.mock({
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(100, 500)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)'),
video_uri:
'//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4'
})
)
}
const treeList: TreeListProps[] = []
for (let i = 0; i < count; i++) {
treeList.push(
Mock.mock({
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)'),
children: [
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)'),
children: [
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
}
]
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
}
]
// image_uri
})
)
}
const cardList = [
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png',
name: 'Alipay',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png',
name: 'Angular',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png',
name: 'Bootstrap',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png',
name: 'React',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png',
name: 'Vue',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png',
name: 'Webpack',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
}
]
export default [
// 树形列表接口
{
url: '/mock/example/treeList',
method: 'get',
timeout,
response: ({ query }) => {
const { title, pageIndex, pageSize } = query
const mockList = treeList.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 列表接口
{
url: '/mock/example/list',
method: 'get',
timeout,
response: ({ query }) => {
const { title, pageIndex, pageSize } = query
const mockList = List.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 保存接口
{
url: '/mock/example/save',
method: 'post',
timeout,
response: ({ body }) => {
if (!body.id) {
List = [
Object.assign(body, {
id: toAnyString()
})
].concat(List)
return {
code: SUCCESS_CODE,
data: 'success'
}
} else {
List.map((item) => {
if (item.id === body.id) {
for (const key in item) {
item[key] = body[key]
}
}
})
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
},
// 详情接口
{
url: '/mock/example/detail',
method: 'get',
response: ({ query }) => {
const { id } = query
for (const example of List) {
if (example.id === id) {
return {
code: SUCCESS_CODE,
data: example
}
}
}
}
},
// 删除接口
{
url: '/mock/example/delete',
method: 'post',
response: ({ body }) => {
const ids = body.ids
if (!ids) {
return {
code: 500,
message: '请选择需要删除的数据'
}
} else {
let i = List.length
while (i--) {
if (ids.indexOf(List[i].id) !== -1) {
List.splice(i, 1)
}
}
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
},
{
url: '/mock/card/list',
method: 'get',
timeout,
response: ({ query }) => {
const { name, pageIndex, pageSize } = query
const mockList = cardList.filter((item) => {
if (name && item.name.indexOf(name) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
}
]

90
mock/user/index.mock.ts Normal file
View File

@ -0,0 +1,90 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
const List: {
username: string
password: string
role: string
roleId: string
permissions: string | string[]
}[] = [
{
username: 'admin',
password: 'admin',
role: 'admin',
roleId: '1',
permissions: ['*.*.*']
},
{
username: 'test',
password: 'test',
role: 'test',
roleId: '2',
permissions: ['example:dialog:create', 'example:dialog:delete']
}
]
export default [
// 列表接口
{
url: '/mock/user/list',
method: 'get',
response: ({ query }) => {
const { username, pageIndex, pageSize } = query
const mockList = List.filter((item) => {
if (username && item.username.indexOf(username) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 登录接口
{
url: '/mock/user/login',
method: 'post',
timeout,
response: ({ body }) => {
const data = body
let hasUser = false
for (const user of List) {
if (user.username === data.username && user.password === data.password) {
hasUser = true
return {
code: SUCCESS_CODE,
data: user
}
}
}
if (!hasUser) {
return {
code: 500,
message: '账号或密码错误'
}
}
}
},
// 退出接口
{
url: '/mock/user/loginOut',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: null
}
}
}
]

View File

@ -0,0 +1,169 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
export default [
// 获取统计
{
url: '/mock/workplace/total',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: {
project: 40,
access: 2340,
todo: 10
}
}
}
},
// 获取项目
{
url: '/mock/workplace/project',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
name: 'Github',
icon: 'akar-icons:github-fill',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vue',
icon: 'logos:vue',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Angular',
icon: 'logos:angular-icon',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'React',
icon: 'logos:react',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Webpack',
icon: 'logos:webpack',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
}
]
}
}
},
// 获取动态
{
url: '/mock/workplace/dynamic',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
}
]
}
}
},
// 获取团队信息
{
url: '/mock/workplace/team',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
name: 'Github',
icon: 'akar-icons:github-fill'
},
{
name: 'Vue',
icon: 'logos:vue'
},
{
name: 'Angular',
icon: 'logos:angular-icon'
},
{
name: 'React',
icon: 'logos:react'
},
{
name: 'Webpack',
icon: 'logos:webpack'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite'
}
]
}
}
},
// 获取指数
{
url: '/mock/workplace/radar',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ name: 'workplace.quote', max: 65, personal: 42, team: 50 },
{ name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
{ name: 'workplace.hot', max: 300, personal: 20, team: 28 },
{ name: 'workplace.yield', max: 130, personal: 35, team: 35 },
{ name: 'workplace.follow', max: 100, personal: 80, team: 90 }
]
}
}
}
]

147
package.json Normal file
View File

@ -0,0 +1,147 @@
{
"name": "vue-element-plus-admin",
"version": "2.8.1",
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
"author": "Archer <502431556@qq.com>",
"private": false,
"type": "module",
"scripts": {
"i": "pnpm install",
"dev": "pnpm vite --mode base",
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
"build:pro": "pnpm vite build --mode pro",
"build:gitee": "pnpm vite build --mode gitee",
"build:dev": "pnpm vite build --mode dev",
"build:test": "pnpm vite build --mode test",
"serve:pro": "pnpm vite preview --mode pro",
"serve:dev": "pnpm vite preview --mode dev",
"serve:test": "pnpm vite preview --mode test",
"npm:check": "pnpx npm-check-updates -u",
"clean": "pnpx rimraf node_modules",
"clean:cache": "pnpx rimraf node_modules/.cache",
"lint:eslint": "eslint . --fix \"src/**/*.{js,ts,tsx,vue,html}\"",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.cjs",
"prepare": "husky install",
"p": "plop",
"icon": "esno ./scripts/icon.ts"
},
"dependencies": {
"@iconify/iconify": "^3.1.1",
"@iconify/vue": "^4.1.2",
"@vtj/icons": "^0.11.6",
"@vtj/web": "latest",
"@vueuse/core": "^10.11.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
"axios": "^1.7.2",
"cropperjs": "^1.6.2",
"dayjs": "^1.11.11",
"driver.js": "^1.3.1",
"echarts": "^5.5.1",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.7.7",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"monaco-editor": "^0.50.0",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode": "^1.5.3",
"qs": "^6.12.3",
"url": "^0.11.3",
"vue": "3.4.32",
"vue-draggable-plus": "^0.5.2",
"vue-i18n": "9.13.1",
"vue-json-pretty": "^2.4.0",
"vue-router": "^4.4.0",
"vue-types": "^5.1.3",
"xgplayer": "^3.0.18"
},
"devDependencies": {
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@iconify/json": "^2.2.229",
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@types/fs-extra": "^11.0.4",
"@types/inquirer": "^9.0.7",
"@types/lodash-es": "^4.17.12",
"@types/mockjs": "^1.0.10",
"@types/node": "^20.14.11",
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.15",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
"@unocss/transformer-variant-group": "^0.61.5",
"@vitejs/plugin-legacy": "^5.4.1",
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"@vtj/cli": "latest",
"@vtj/pro": "latest",
"autoprefixer": "^10.4.19",
"chalk": "^5.3.0",
"consola": "^3.2.3",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.27.0",
"esno": "^4.7.0",
"fs-extra": "^11.2.0",
"husky": "^9.1.0",
"inquirer": "^10.0.3",
"less": "^4.2.0",
"lint-staged": "^15.2.7",
"mockjs": "^1.1.0",
"plop": "^4.0.1",
"postcss": "^8.4.39",
"postcss-html": "^1.7.0",
"postcss-less": "^6.0.0",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"rollup": "^4.18.1",
"rollup-plugin-visualizer": "^5.12.0",
"stylelint": "^16.7.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-standard": "^36.0.1",
"stylelint-order": "^6.0.4",
"terser": "^5.31.3",
"typescript": "5.5.3",
"typescript-eslint": "^7.16.1",
"unocss": "^0.61.5",
"vite": "5.3.4",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-mock": "2.9.6",
"vite-plugin-progress": "^0.0.7",
"vite-plugin-purge-icons": "^0.10.0",
"vite-plugin-style-import": "2.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-url-copy": "^1.1.4",
"vue-tsc": "^2.0.26"
},
"packageManager": "pnpm@8.1.0",
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.1.0"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/kailong321200875/vue-element-plus-admin.git"
},
"bugs": {
"url": "https://github.com/kailong321200875/vue-element-plus-admin/issues"
},
"homepage": "https://github.com/kailong321200875/vue-element-plus-admin",
"vtj": {
"id": "vue-element-plus-admin",
"name": "ElementAdmin"
}
}

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('{{ name }}')
</script>
<template>
<div :class="`${prefixCls}-{{ name }}`">{{ upperFirstName }}</div>
</template>

3
plop/component/index.hbs Normal file
View File

@ -0,0 +1,3 @@
import {{ upperFirstName }} from './src/{{ upperFirstName }}.vue'
export { {{ upperFirstName }} }

38
plop/component/prompt.cjs Normal file
View File

@ -0,0 +1,38 @@
const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
module.exports = {
description: 'Create vue component',
prompts: [
{
type: 'input',
name: 'name',
message: '请输入组件名称Please enter the component name'
}
],
actions: (data) => {
const { name } = data
const upperFirstName = toUpperCase(name)
const actions = []
if (name) {
actions.push({
type: 'add',
path: `./src/components/${upperFirstName}/src/${upperFirstName}.vue`,
templateFile: './plop/component/component.hbs',
data: {
name,
upperFirstName
}
}, {
type: 'add',
path: `./src/components/${upperFirstName}/index.ts`,
templateFile: './plop/component/index.hbs',
data: {
upperFirstName
}
})
}
return actions
}
}

37
plop/view/prompt.cjs Normal file
View File

@ -0,0 +1,37 @@
const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
module.exports = {
description: 'Create vue view',
prompts: [
{
type: 'input',
name: 'path',
message: '请输入路径Please enter a path',
default: 'views'
},
{
type: 'input',
name: 'name',
message: '请输入模块名称Please enter module name'
}
],
actions: (data) => {
const { name, path } = data
const upperFirstName = toUpperCase(name)
const actions = []
if (name) {
actions.push({
type: 'add',
path: `./src/${path}/${upperFirstName}.vue`,
templateFile: './plop/view/view.hbs',
data: {
name,
upperFirstName
}
})
}
return actions
}
}

7
plop/view/view.hbs Normal file
View File

@ -0,0 +1,7 @@
<script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap'
</script>
<template>
<ContentWrap title="{{ upperFirstName }}"> {{ name }} </ContentWrap>
</template>

7
plopfile.cjs Normal file
View File

@ -0,0 +1,7 @@
const viewGenerator = require('./plop/view/prompt.cjs')
const componentGenerator = require('./plop/component/prompt.cjs')
module.exports = function (plop) {
plop.setGenerator('view', viewGenerator)
plop.setGenerator('component', componentGenerator)
}

0
pnpm Normal file
View File

5
postcss.config.cjs Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

19
prettier.config.cjs Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false,
vueIndentScriptAndStyle: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'none',
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
rangeStart: 0
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/logo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

73
scripts/icon.ts Normal file
View File

@ -0,0 +1,73 @@
import path from 'path'
import fs from 'fs-extra'
import inquirer from 'inquirer'
import chalk from 'chalk'
import pkg from '../package.json'
import { ICON_PREFIX } from '../src/constants'
interface Icon {
name: string
prefix: string
icons: string[]
}
async function generateIcon() {
const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json')
const raw = await fs.readJSON(path.join(dir, 'collections.json'))
const collections = Object.entries(raw).map(([id, v]) => ({
...(v as any),
id
}))
const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }))
inquirer
.prompt([
// {
// type: 'list',
// name: 'useType',
// choices: [
// { key: 'local', value: 'local', name: 'Local' },
// { key: 'onLine', value: 'onLine', name: 'OnLine' }
// ],
// message: 'How to use icons?'
// },
{
type: 'list',
name: 'iconSet',
choices: choices,
message: 'Select the icon set that needs to be generated?'
}
])
// ↓命令行问答的答案
.then(async (answers) => {
const { iconSet } = answers
// const isOnLine = useType === 'onLine'
const outputDir = path.resolve(process.cwd(), 'src/components/IconPicker/src/data')
fs.ensureDir(outputDir)
const genCollections = collections.filter((item) => [iconSet].includes(item.id))
const prefixSet: string[] = []
for (const info of genCollections) {
const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`))
if (data) {
const { prefix } = data
const prefixName = `${ICON_PREFIX}${prefix}`
const icons = Object.keys(data.icons).map((item) => `${prefixName}:${item}`)
await fs.writeFileSync(
path.join('src/components/IconPicker/src/data', `icons.${prefix}.ts`),
`export default ${JSON.stringify({ name: info.name, prefix: prefixName, icons })}`
)
// ↓分类处理完成push类型名称
prefixSet.push(prefix)
}
}
console.log(
`${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`
)
})
}
generateIcon()

50
src/App.vue Normal file
View File

@ -0,0 +1,50 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { ConfigGlobal } from '@/components/ConfigGlobal'
import { useDesign } from '@/hooks/web/useDesign'
import { ElNotification } from 'element-plus'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('app')
const appStore = useAppStore()
const currentSize = computed(() => appStore.getCurrentSize)
const greyMode = computed(() => appStore.getGreyMode)
appStore.initTheme()
</script>
<template>
<ConfigGlobal :size="currentSize">
<RouterView :class="greyMode ? `${prefixCls}-grey-mode` : ''" />
</ConfigGlobal>
</template>
<style lang="less">
@prefix-cls: ~'@{adminNamespace}-app';
.size {
width: 100%;
height: 100%;
}
html,
body {
padding: 0 !important;
margin: 0;
overflow: hidden;
.size;
#app {
.size;
}
}
.@{prefix-cls}-grey-mode {
filter: grayscale(100%);
}
</style>

11
src/api/common/index.ts Normal file
View File

@ -0,0 +1,11 @@
import request from '@/axios'
// 获取所有字典
export const getDictApi = () => {
return request.get({ url: '/mock/dict/list' })
}
// 模拟获取某个字典
export const getDictOneApi = async () => {
return request.get({ url: '/mock/dict/one' })
}

View File

@ -0,0 +1,23 @@
import request from '@/axios'
import type {
AnalysisTotalTypes,
UserAccessSource,
WeeklyUserActivity,
MonthlySales
} from './types'
export const getCountApi = (): Promise<IResponse<AnalysisTotalTypes[]>> => {
return request.get({ url: '/mock/analysis/total' })
}
export const getUserAccessSourceApi = (): Promise<IResponse<UserAccessSource[]>> => {
return request.get({ url: '/mock/analysis/userAccessSource' })
}
export const getWeeklyUserActivityApi = (): Promise<IResponse<WeeklyUserActivity[]>> => {
return request.get({ url: '/mock/analysis/weeklyUserActivity' })
}
export const getMonthlySalesApi = (): Promise<IResponse<MonthlySales[]>> => {
return request.get({ url: '/mock/analysis/monthlySales' })
}

View File

@ -0,0 +1,22 @@
export type AnalysisTotalTypes = {
users: number
messages: number
moneys: number
shoppings: number
}
export type UserAccessSource = {
value: number
name: string
}
export type WeeklyUserActivity = {
value: number
name: string
}
export type MonthlySales = {
name: string
estimate: number
actual: number
}

View File

@ -0,0 +1,22 @@
import request from '@/axios'
import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types'
export const getCountApi = (): Promise<IResponse<WorkplaceTotal>> => {
return request.get({ url: '/mock/workplace/total' })
}
export const getProjectApi = (): Promise<IResponse<Project>> => {
return request.get({ url: '/mock/workplace/project' })
}
export const getDynamicApi = (): Promise<IResponse<Dynamic[]>> => {
return request.get({ url: '/mock/workplace/dynamic' })
}
export const getTeamApi = (): Promise<IResponse<Team[]>> => {
return request.get({ url: '/mock/workplace/team' })
}
export const getRadarApi = (): Promise<IResponse<RadarData[]>> => {
return request.get({ url: '/mock/workplace/radar' })
}

View File

@ -0,0 +1,30 @@
export type WorkplaceTotal = {
project: number
access: number
todo: number
}
export type Project = {
name: string
icon: string
message: string
personal: string
time: Date | number | string
}
export type Dynamic = {
keys: string[]
time: Date | number | string
}
export type Team = {
name: string
icon: string
}
export type RadarData = {
personal: number
team: number
max: number
name: string
}

View File

@ -0,0 +1,30 @@
import request from '@/axios'
import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
export const getDepartmentApi = () => {
return request.get<DepartmentListResponse>({ url: '/mock/department/list' })
}
export const getUserByIdApi = (params: DepartmentUserParams) => {
return request.get<DepartmentUserResponse>({ url: '/mock/department/users', params })
}
export const deleteUserByIdApi = (ids: string[] | number[]) => {
return request.post({ url: '/mock/department/user/delete', data: { ids } })
}
export const saveUserApi = (data: any) => {
return request.post({ url: '/mock/department/user/save', data })
}
export const saveDepartmentApi = (data: any) => {
return request.post({ url: '/mock/department/save', data })
}
export const deleteDepartmentApi = (ids: string[] | number[]) => {
return request.post({ url: '/mock/department/delete', data: { ids } })
}
export const getDepartmentTableApi = (params: any) => {
return request.get({ url: '/mock/department/table/list', params })
}

View File

@ -0,0 +1,32 @@
export interface DepartmentItem {
id: string
departmentName: string
children?: DepartmentItem[]
}
export interface DepartmentListResponse {
list: DepartmentItem[]
}
export interface DepartmentUserParams {
pageSize: number
pageIndex: number
id: string
username?: string
account?: string
}
export interface DepartmentUserItem {
id: string
username: string
account: string
email: string
createTime: string
role: string
department: DepartmentItem
}
export interface DepartmentUserResponse {
list: DepartmentUserItem[]
total: number
}

86
src/api/login/index.ts Normal file
View File

@ -0,0 +1,86 @@
import request from '@/axios'
import { getRefreshToken } from '@/utils/auth'
import type { UserVO, UserLoginVO, RegisterVO } from './types'
export interface SmsCodeVO {
mobile: string
scene: number
}
export interface SmsLoginVO {
mobile: string
code: string
}
// 登录
export const login = (data: UserLoginVO) => {
return request.post({ url: '/system/auth/login', data })
}
// 注册
export const register = (data: RegisterVO) => {
return request.post({ url: '/system/auth/register', data })
}
// 使用租户名,获得租户编号
export const getTenantIdByName = (name: string) => {
return request.get({ url: '/system/tenant/get-id-by-name?name=' + name })
}
// 使用租户域名,获得租户信息
export const getTenantByWebsite = (website: string) => {
return request.get({ url: '/system/tenant/get-by-website?website=' + website })
}
// 登出
export const loginOutApi = () => {
return request.post({ url: '/system/auth/logout' })
}
// 获取用户权限信息
export const getInfo = () => {
return request.get({ url: '/system/auth/get-permission-info' })
}
//获取登录验证码
export const sendSmsCode = (data: SmsCodeVO) => {
return request.post({ url: '/system/auth/send-sms-code', data })
}
// 短信验证码登录
export const smsLogin = (data: SmsLoginVO) => {
return request.post({ url: '/system/auth/sms-login', data })
}
// 社交快捷登录,使用 code 授权码
export function socialLogin(type: string, code: string, state: string) {
return request.post({
url: '/system/auth/social-login',
data: {
type,
code,
state
}
})
}
// 社交授权的跳转
export const socialAuthRedirect = (type: number, redirectUri: string) => {
return request.get({
url: '/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri
})
}
// 获取验证图片以及 token
export const getCode = (data: any) => {
return request.post({ url: '/system/captcha/get', data })
}
// 滑动或者点选验证
export const reqCheck = (data: any) => {
return request.post({ url: '/system/captcha/check', data })
}
// 通过短信重置密码
export const smsResetPassword = (data: any) => {
return request.post({ url: '/system/auth/reset-password', data })
}

39
src/api/login/types.ts Normal file
View File

@ -0,0 +1,39 @@
export type UserLoginVO = {
username: string
password: string
captchaVerification: string
rememberMe?: boolean
socialType?: string
socialCode?: string
socialState?: string
}
export type TokenType = {
id: number // 编号
accessToken: string // 访问令牌
refreshToken: string // 刷新令牌
userId: number // 用户编号
userType: number //用户类型
clientId: string //客户端编号
expiresTime: number //过期时间
}
export type UserVO = {
id: number
username: string
nickname: string
deptId: number
email: string
mobile: string
sex: number
avatar: string
loginIp: string
loginDate: string
}
export type RegisterVO = {
tenantName: string
username: string
password: string
captchaVerification: string
}

5
src/api/menu/index.ts Normal file
View File

@ -0,0 +1,5 @@
import request from '@/axios'
export const getMenuListApi = () => {
return request.get({ url: '/mock/menu/list' })
}

38
src/api/request/index.ts Normal file
View File

@ -0,0 +1,38 @@
import request from '@/axios'
import { RequestResponse } from './types'
export const request1 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/1'
})
}
export const request2 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/2'
})
}
export const request3 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/3'
})
}
export const request4 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/4'
})
}
export const request5 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/5'
})
}
export const expired = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/expired'
})
}

3
src/api/request/types.ts Normal file
View File

@ -0,0 +1,3 @@
export interface RequestResponse {
data: string
}

5
src/api/role/index.ts Normal file
View File

@ -0,0 +1,5 @@
import request from '@/axios'
export const getRoleListApi = () => {
return request.get({ url: '/mock/role/table' })
}

26
src/api/table/index.ts Normal file
View File

@ -0,0 +1,26 @@
import request from '@/axios'
import type { TableData } from './types'
export const getTableListApi = (params: any) => {
return request.get({ url: '/mock/example/list', params })
}
export const getCardTableListApi = (params: any) => {
return request.get({ url: '/mock/card/list', params })
}
export const getTreeTableListApi = (params: any) => {
return request.get({ url: '/mock/example/treeList', params })
}
export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
return request.post({ url: '/mock/example/save', data })
}
export const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {
return request.get({ url: '/mock/example/detail', params: { id } })
}
export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {
return request.post({ url: '/mock/example/delete', data: { ids } })
}

9
src/api/table/types.ts Normal file
View File

@ -0,0 +1,9 @@
export type TableData = {
id: string
author: string
title: string
content: string
importance: number
display_time: string
pageviews: number
}

BIN
src/assets/imgs/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
src/assets/imgs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

1
src/assets/svgs/403.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

1
src/assets/svgs/404.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

1
src/assets/svgs/500.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

1
src/assets/svgs/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 014.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 012.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5760" height="3040"><image width="5760" height="3040" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAFoAAAAvgAQMAAAC1QKagAAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEUsNEr///91v/yPAAAA AWJLR0QB/wIt3gAAAAd0SU1FB+YBBQYyN1c3BnEAAAhjSURBVHja7cExAQAAAMKg9U9tDB+gAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAACAtwFzzwABY3VrRQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMi0wMS0wNVQwNjo1 MDo1MyswMDowMCfNlVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjItMDEtMDVUMDY6NTA6NTQrMDA6 MDCTNxNoAAAAAElFTkSuQmCC"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>

After

Width:  |  Height:  |  Size: 669 B

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg>

After

Width:  |  Height:  |  Size: 731 B

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 013.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 01-3.889 2.843 10.582 10.582 0 01-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 01-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 01-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 013.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 013.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 01-3.89 2.843 11 11 0 01-4.732 1.066 10.58 10.58 0 01-4.667-1.066 12.478 12.478 0 01-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 01-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 013.824-2.772 11.212 11.212 0 014.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 01.778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 01-2.788 8.743 1236.373 1236.373 0 00-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 01-1.88-4.478 44.128 44.128 0 01-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 01-2.14-2.558 10.416 10.416 0 01-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 011.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

78
src/axios/config.ts Normal file
View File

@ -0,0 +1,78 @@
import { AxiosResponse, InternalAxiosRequestConfig } from './types'
import { ElMessage } from 'element-plus'
import qs from 'qs'
import { SUCCESS_CODE, TRANSFORM_REQUEST_DATA } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user'
import { objToFormData } from '@/utils'
import { getTenantId } from '@/utils/auth'
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
// 添加租户ID请求头
const tenantId = getTenantId()
if (tenantId) {
config.headers['Tenant-Id'] = tenantId
}
if (
config.method === 'post' &&
config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
) {
config.data = qs.stringify(config.data)
} else if (
TRANSFORM_REQUEST_DATA &&
config.method === 'post' &&
config.headers['Content-Type'] === 'multipart/form-data' &&
!(config.data instanceof FormData)
) {
config.data = objToFormData(config.data)
}
if (config.method === 'get' && config.params) {
let url = config.url as string
url += '?'
const keys = Object.keys(config.params)
for (const key of keys) {
if (config.params[key] !== void 0 && config.params[key] !== null) {
url += `${key}=${encodeURIComponent(config.params[key])}&`
}
}
url = url.substring(0, url.length - 1)
config.params = {}
config.url = url
}
return config
}
const defaultResponseInterceptors = (response: AxiosResponse) => {
// 添加调试日志
console.log('响应URL:', response.config.url);
console.log('响应状态:', response.status);
console.log('响应数据类型:', typeof response.data);
// 验证码接口特殊处理
if (response.config.url?.includes('/system/captcha')) {
console.log('验证码接口响应:', response.data);
return response.data;
}
if (response?.config?.responseType === 'blob') {
// 如果是文件流,直接过
return response
} else if (response.data.code === SUCCESS_CODE) {
return response.data
} else {
// 添加更详细的错误日志
console.error('响应错误:', response.data);
console.error('错误代码:', response.data.code);
console.error('错误消息:', response.data.message);
ElMessage.error(response?.data?.message || '请求失败')
if (response?.data?.code === 401) {
const userStore = useUserStoreWithOut()
userStore.logout()
}
// 返回响应数据,让调用方处理错误
return response.data;
}
}
export { defaultResponseInterceptors, defaultRequestInterceptors }

42
src/axios/index.ts Normal file
View File

@ -0,0 +1,42 @@
import service from './service'
import { CONTENT_TYPE } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user'
const request = (option: AxiosConfig) => {
const { url, method, params, data, headers, responseType } = option
const userStore = useUserStoreWithOut()
return service.request({
url: url,
method,
params,
data: data,
responseType: responseType,
headers: {
'Content-Type': CONTENT_TYPE,
[userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
...headers
}
})
}
export default {
get: <T = any>(option: AxiosConfig) => {
return request({ method: 'get', ...option }) as Promise<IResponse<T>>
},
post: <T = any>(option: AxiosConfig) => {
return request({ method: 'post', ...option }) as Promise<IResponse<T>>
},
delete: <T = any>(option: AxiosConfig) => {
return request({ method: 'delete', ...option }) as Promise<IResponse<T>>
},
put: <T = any>(option: AxiosConfig) => {
return request({ method: 'put', ...option }) as Promise<IResponse<T>>
},
cancelRequest: (url: string | string[]) => {
return service.cancelRequest(url)
},
cancelAllRequest: () => {
return service.cancelAllRequest()
}
}

77
src/axios/service.ts Normal file
View File

@ -0,0 +1,77 @@
import axios, { AxiosError } from 'axios'
import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
import { ElMessage } from 'element-plus'
import { REQUEST_TIMEOUT } from '@/constants'
export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
const abortControllerMap: Map<string, AbortController> = new Map()
const axiosInstance: AxiosInstance = axios.create({
timeout: REQUEST_TIMEOUT,
baseURL: PATH_URL
})
axiosInstance.interceptors.request.use((res: InternalAxiosRequestConfig) => {
const controller = new AbortController()
const url = res.url || ''
res.signal = controller.signal
abortControllerMap.set(
import.meta.env.VITE_USE_MOCK === 'true' ? url.replace('/mock', '') : url,
controller
)
return res
})
axiosInstance.interceptors.response.use(
(res: AxiosResponse) => {
const url = res.config.url || ''
abortControllerMap.delete(url)
// 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
return res
},
(error: AxiosError) => {
console.log('err ' + error) // for debug
ElMessage.error(error.message)
return Promise.reject(error)
}
)
axiosInstance.interceptors.request.use(defaultRequestInterceptors)
axiosInstance.interceptors.response.use(defaultResponseInterceptors)
const service = {
request: (config: RequestConfig) => {
return new Promise((resolve, reject) => {
if (config.interceptors?.requestInterceptors) {
config = config.interceptors.requestInterceptors(config as any)
}
axiosInstance
.request(config)
.then((res) => {
resolve(res)
})
.catch((err: any) => {
reject(err)
})
})
},
cancelRequest: (url: string | string[]) => {
const urlList = Array.isArray(url) ? url : [url]
for (const _url of urlList) {
abortControllerMap.get(_url)?.abort()
abortControllerMap.delete(_url)
}
},
cancelAllRequest() {
for (const [_, controller] of abortControllerMap) {
controller.abort()
}
abortControllerMap.clear()
}
}
export default service

31
src/axios/types/index.ts Normal file
View File

@ -0,0 +1,31 @@
import type {
InternalAxiosRequestConfig,
AxiosResponse,
AxiosRequestConfig,
AxiosInstance,
AxiosRequestHeaders,
AxiosError
} from 'axios'
interface RequestInterceptors<T> {
// 请求拦截
requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
requestInterceptorsCatch?: (err: any) => any
// 响应拦截
responseInterceptors?: (config: T) => T
responseInterceptorsCatch?: (err: any) => any
}
interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: RequestInterceptors<T>
}
export {
AxiosResponse,
RequestInterceptors,
RequestConfig,
AxiosInstance,
InternalAxiosRequestConfig,
AxiosRequestHeaders,
AxiosError
}

View File

@ -0,0 +1,4 @@
import Avatars from './src/Avatars.vue'
export type { AvatarItem } from './src/types'
export { Avatars }

View File

@ -0,0 +1,79 @@
<script setup lang="ts">
import { ComponentSize, ElAvatar, ElTooltip } from 'element-plus'
import { PropType, computed } from 'vue'
import { AvatarItem } from './types'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('avatars')
const props = defineProps({
size: {
type: [String, Number] as PropType<ComponentSize | number>,
default: ''
},
max: {
type: Number,
default: 5
},
data: {
type: Array as PropType<AvatarItem[]>,
default: () => []
},
showTooltip: {
type: Boolean,
default: true
}
})
const filterData = computed(() => props.data.slice(0, props.max))
</script>
<template>
<div :class="prefixCls" class="flex items-center">
<template v-for="item in filterData" :key="item.url">
<template v-if="showTooltip && item.name">
<ElTooltip :content="item.name" placement="top">
<ElAvatar
:size="size"
:src="item.url"
class="relative"
:style="{
zIndex: filterData.indexOf(item)
}"
/>
</ElTooltip>
</template>
<template v-else>
<ElAvatar
:size="size"
:src="item.url"
class="relative"
:style="{
zIndex: filterData.indexOf(item)
}"
/>
</template>
</template>
<ElAvatar
v-if="data.length > max"
:style="{
zIndex: data.length
}"
>
<span>+{{ data.length - max }}</span>
</ElAvatar>
</div>
</template>
<style scoped lang="less">
@prefix-cls: ~'@{adminNamespace}-avatars';
.@{prefix-cls} {
.@{elNamespace}-avatar + .@{elNamespace}-avatar {
margin-left: -15px;
}
}
</style>

View File

@ -0,0 +1,4 @@
export interface AvatarItem {
url: string
name?: string
}

View File

@ -0,0 +1,3 @@
import Backtop from './src/Backtop.vue'
export { Backtop }

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { ElBacktop } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls, variables } = useDesign()
const prefixCls = getPrefixCls('backtop')
</script>
<template>
<ElBacktop
:class="prefixCls"
:target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
/>
</template>

View File

@ -0,0 +1,3 @@
import Breadcrumb from './src/Breadcrumb.vue'
export { Breadcrumb }

View File

@ -0,0 +1,126 @@
<script lang="tsx">
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
import { useRouter } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
import { filterBreadcrumb } from './helper'
import { filter, treeToList } from '@/utils/tree'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
import { Icon } from '@/components/Icon'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('breadcrumb')
const appStore = useAppStore()
//
const breadcrumbIcon = computed(() => appStore.getBreadcrumbIcon)
export default defineComponent({
name: 'Breadcrumb',
setup() {
const { currentRoute } = useRouter()
const { t } = useI18n()
const levelList = ref<AppRouteRecordRaw[]>([])
const permissionStore = usePermissionStore()
const menuRouters = computed(() => {
const routers = permissionStore.getRouters
return filterBreadcrumb(routers)
})
const getBreadcrumb = () => {
const currentPath = currentRoute.value.matched.slice(-1)[0].path
levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
return node.path === currentPath
})
}
const renderBreadcrumb = () => {
const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
return breadcrumbList.map((v) => {
const disabled = !v.redirect || v.redirect === 'noredirect'
const meta = v.meta
return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon && breadcrumbIcon.value ? (
<>
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
</>
) : (
t(v?.meta?.title || '')
)}
</ElBreadcrumbItem>
)
})
}
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
if (route.path.startsWith('/redirect/')) {
return
}
getBreadcrumb()
},
{
immediate: true
}
)
return () => (
<ElBreadcrumb separator="/" class={`${prefixCls} flex items-center h-full ml-[10px]`}>
<TransitionGroup appear enter-active-class="animate__animated animate__fadeInRight">
{renderBreadcrumb()}
</TransitionGroup>
</ElBreadcrumb>
)
}
})
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{elNamespace}-breadcrumb';
.@{prefix-cls} {
:deep(&__item) {
display: flex;
.@{prefix-cls}__inner {
display: flex;
align-items: center;
color: var(--top-header-text-color);
&:hover {
color: var(--el-color-primary);
}
}
}
:deep(&__item):not(:last-child) {
.@{prefix-cls}__inner {
color: var(--top-header-text-color);
&:hover {
color: var(--el-color-primary);
}
}
}
:deep(&__item):last-child {
.@{prefix-cls}__inner {
color: var(--el-text-color-placeholder);
&:hover {
color: var(--el-text-color-placeholder);
}
}
}
}
</style>

View File

@ -0,0 +1,30 @@
import { pathResolve } from '@/utils/routerHelper'
export const filterBreadcrumb = (
routes: AppRouteRecordRaw[],
parentPath = ''
): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
const meta = route?.meta
if (meta.hidden && !meta.canTo) {
continue
}
const data: AppRouteRecordRaw =
!meta.alwaysShow && route.children?.length === 1
? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) }
: { ...route }
data.path = pathResolve(parentPath, data.path)
if (data.children) {
data.children = filterBreadcrumb(data.children, data.path)
}
if (data) {
res.push(data)
}
}
return res
}

View File

@ -0,0 +1,3 @@
import BaseButton from './src/Button.vue'
export { BaseButton }

View File

@ -0,0 +1,121 @@
<script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign'
import { ElButton, ComponentSize, ButtonType } from 'element-plus'
import { PropType, Component, computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
const getTheme = computed(() => appStore.getTheme)
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('button')
const props = defineProps({
size: {
type: String as PropType<ComponentSize>,
default: undefined
},
type: {
type: String as PropType<ButtonType>,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
plain: {
type: Boolean,
default: false
},
text: {
type: Boolean,
default: false
},
bg: {
type: Boolean,
default: false
},
link: {
type: Boolean,
default: false
},
round: {
type: Boolean,
default: false
},
circle: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
},
loadingIcon: {
type: [String, Object] as PropType<String | Component>,
default: undefined
},
icon: {
type: [String, Object] as PropType<String | Component>,
default: undefined
},
autofocus: {
type: Boolean,
default: false
},
nativeType: {
type: String as PropType<'button' | 'submit' | 'reset'>,
default: 'button'
},
autoInsertSpace: {
type: Boolean,
default: false
},
color: {
type: String,
default: ''
},
darker: {
type: Boolean,
default: false
},
tag: {
type: [String, Object] as PropType<String | Component>,
default: 'button'
}
})
const emits = defineEmits(['click'])
const color = computed(() => {
const { type, link } = props
if (type === 'primary' && !link) {
return unref(getTheme).elColorPrimary
}
return ''
})
const style = computed(() => {
const { type, link } = props
if (type === 'primary' && !link) {
return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
}
return ''
})
</script>
<template>
<ElButton
:class="`${prefixCls} color-#fff`"
v-bind="{ ...props }"
:color="color"
:style="style"
@click="() => emits('click')"
>
<slot></slot>
<slot name="icon"></slot>
<slot name="loading"></slot>
</ElButton>
</template>

View File

@ -0,0 +1,3 @@
import CodeEditor from './src/CodeEditor.vue'
export { CodeEditor }

View File

@ -0,0 +1,119 @@
<script setup lang="tsx">
import { useMonacoEditor } from '@/hooks/web/useMonacoEditor'
import { onMounted, computed, watch, ref } from 'vue'
import { ElSelect, ElOption, ElFormItem, ElForm } from 'element-plus'
import { languageOptions, themeOptions } from './config/config'
const props = withDefaults(
defineProps<{
width?: string | number
height?: string | number
languageSelector?: boolean
language?: string
themeSelector?: boolean
theme?: string
editorOption?: Object
modelValue: string
}>(),
{
width: '100%',
height: '100%',
languageSelector: true,
language: 'javascript',
themeSelector: true,
theme: 'vs-dark',
editorOption: () => ({}),
modelValue: ''
}
)
const emits = defineEmits<{
(e: 'blur'): void
(e: 'update:modelValue', val: string): void
}>()
const monacoEditorStyle = computed(() => {
return {
width: typeof props.width === 'string' ? props.width : props.width + 'px',
height: typeof props.height === 'string' ? props.height : props.height + 'px'
}
})
const {
monacoEditorRef,
createEditor,
updateVal,
updateOptions,
getEditor,
changeLanguage,
changeTheme
} = useMonacoEditor(props.language)
onMounted(() => {
const monacoEditor = createEditor(props.editorOption)
updateMonacoVal(props.modelValue)
monacoEditor?.onDidChangeModelContent(() => {
emits('update:modelValue', monacoEditor!.getValue())
})
monacoEditor?.onDidBlurEditorText(() => {
emits('blur')
})
})
watch(
() => props.modelValue,
() => {
updateMonacoVal(props.modelValue)
}
)
const localLanguage = ref(props.language)
watch(localLanguage, (newLanguage) => {
changeLanguage(newLanguage)
})
const localTheme = ref(props.theme)
watch(localTheme, (newTheme) => {
changeTheme(newTheme)
})
function updateMonacoVal(val: string) {
if (val !== getEditor()?.getValue()) {
updateVal(val)
}
}
defineExpose({ updateOptions })
</script>
<template>
<ElForm :inline="true">
<ElFormItem v-if="languageSelector" label="language" class="w-30% mb-5px!">
<ElSelect
v-model="localLanguage"
placeholder="Please select language"
size="small"
filterable
>
<ElOption
v-for="item in languageOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem v-if="themeSelector" label="theme" class="w-30% mb-5px!">
<ElSelect v-model="localTheme" placeholder="Please select language" size="small" filterable>
<ElOption
v-for="item in themeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<div ref="monacoEditorRef" :style="monacoEditorStyle"></div>
</template>

View File

@ -0,0 +1,129 @@
export const languageOptions = [
{ label: 'plaintext', value: 'plaintext' },
{ label: 'abap', value: 'abap' },
{ label: 'apex', value: 'apex' },
{ label: 'azcli', value: 'azcli' },
{ label: 'bat', value: 'bat' },
{ label: 'bicep', value: 'bicep' },
{ label: 'cameligo', value: 'cameligo' },
{ label: 'clojure', value: 'clojure' },
{ label: 'coffeescript', value: 'coffeescript' },
{ label: 'c', value: 'c' },
{ label: 'cpp', value: 'cpp' },
{ label: 'csharp', value: 'csharp' },
{ label: 'csp', value: 'csp' },
{ label: 'css', value: 'css' },
{ label: 'cypher', value: 'cypher' },
{ label: 'dart', value: 'dart' },
{ label: 'dockerfile', value: 'dockerfile' },
{ label: 'ecl', value: 'ecl' },
{ label: 'elixir', value: 'elixir' },
{ label: 'flow9', value: 'flow9' },
{ label: 'fsharp', value: 'fsharp' },
{ label: 'freemarker2', value: 'freemarker2' },
{
label: 'freemarker2.tag-angle.interpolation-dollar',
value: 'freemarker2.tag-angle.interpolation-dollar'
},
{
label: 'freemarker2.tag-bracket.interpolation-dollar',
value: 'freemarker2.tag-bracket.interpolation-dollar'
},
{
label: 'freemarker2.tag-angle.interpolation-bracket',
value: 'freemarker2.tag-angle.interpolation-bracket'
},
{
label: 'freemarker2.tag-bracket.interpolation-bracket',
value: 'freemarker2.tag-bracket.interpolation-bracket'
},
{
label: 'freemarker2.tag-auto.interpolation-dollar',
value: 'freemarker2.tag-auto.interpolation-dollar'
},
{
label: 'freemarker2.tag-auto.interpolation-bracket',
value: 'freemarker2.tag-auto.interpolation-bracket'
},
{ label: 'go', value: 'go' },
{ label: 'graphql', value: 'graphql' },
{ label: 'handlebars', value: 'handlebars' },
{ label: 'hcl', value: 'hcl' },
{ label: 'html', value: 'html' },
{ label: 'ini', value: 'ini' },
{ label: 'java', value: 'java' },
{ label: 'javascript', value: 'javascript' },
{ label: 'julia', value: 'julia' },
{ label: 'kotlin', value: 'kotlin' },
{ label: 'less', value: 'less' },
{ label: 'lexon', value: 'lexon' },
{ label: 'lua', value: 'lua' },
{ label: 'liquid', value: 'liquid' },
{ label: 'm3', value: 'm3' },
{ label: 'markdown', value: 'markdown' },
{ label: 'mdx', value: 'mdx' },
{ label: 'mips', value: 'mips' },
{ label: 'msdax', value: 'msdax' },
{ label: 'mysql', value: 'mysql' },
{ label: 'objective-c', value: 'objective-c' },
{ label: 'pascal', value: 'pascal' },
{ label: 'pascaligo', value: 'pascaligo' },
{ label: 'perl', value: 'perl' },
{ label: 'pgsql', value: 'pgsql' },
{ label: 'php', value: 'php' },
{ label: 'pla', value: 'pla' },
{ label: 'postiats', value: 'postiats' },
{ label: 'powerquery', value: 'powerquery' },
{ label: 'powershell', value: 'powershell' },
{ label: 'proto', value: 'proto' },
{ label: 'pug', value: 'pug' },
{ label: 'python', value: 'python' },
{ label: 'qsharp', value: 'qsharp' },
{ label: 'r', value: 'r' },
{ label: 'razor', value: 'razor' },
{ label: 'redis', value: 'redis' },
{ label: 'redshift', value: 'redshift' },
{ label: 'restructuredtext', value: 'restructuredtext' },
{ label: 'ruby', value: 'ruby' },
{ label: 'rust', value: 'rust' },
{ label: 'sb', value: 'sb' },
{ label: 'scala', value: 'scala' },
{ label: 'scheme', value: 'scheme' },
{ label: 'scss', value: 'scss' },
{ label: 'shell', value: 'shell' },
{ label: 'sol', value: 'sol' },
{ label: 'aes', value: 'aes' },
{ label: 'sparql', value: 'sparql' },
{ label: 'sql', value: 'sql' },
{ label: 'st', value: 'st' },
{ label: 'swift', value: 'swift' },
{ label: 'systemverilog', value: 'systemverilog' },
{ label: 'verilog', value: 'verilog' },
{ label: 'tcl', value: 'tcl' },
{ label: 'twig', value: 'twig' },
{ label: 'typescript', value: 'typescript' },
{ label: 'vb', value: 'vb' },
{ label: 'wgsl', value: 'wgsl' },
{ label: 'xml', value: 'xml' },
{ label: 'yaml', value: 'yaml' },
{ label: 'json', value: 'json' }
]
export const themeOptions = [
{
label: 'vs',
value: 'vs'
},
{
label: 'vs-dark',
value: 'vs-dark'
},
{
label: 'hc-black',
value: 'hc-black'
},
{
label: 'hc-light',
value: 'hc-light'
}
]

View File

@ -0,0 +1,3 @@
import Collapse from './src/Collapse.vue'
export { Collapse }

Some files were not shown because too many files have changed in this diff Show More