Compare commits

..
91 Commits
Author SHA1 Message Date
hooray cefe0c6d78 chore: release v2.6.1 2023-01-09 11:00:52 +08:00
hooray 1187a29ca4 chore: 更新依赖 2023-01-09 10:20:07 +08:00
hooray 4cdfd7210e chore: 更新默认 copyright 信息 2023-01-03 11:26:15 +08:00
hooray 3d84d73e20 chore: release v2.6.0 2022-12-28 12:57:19 +08:00
hooray abbbd74e91 chore: 更新依赖 2022-12-28 12:57:01 +08:00
hooray 4c3512a13c fix: 修复次导航收起时图标不居中 2022-12-22 12:02:22 +08:00
hooray 7387cea6ce chore: 更新依赖 2022-12-22 11:56:37 +08:00
hooray 2e4610aec3 fix: 修复预览图片无法完整覆盖 el-table 固定列
涉及组件 image-preview / image-upload / images-upload
2022-12-19 22:13:32 +08:00
hooray b7b949c160 feat: 引入 vite-plugin-vue-inspector 插件 2022-12-19 20:43:46 +08:00
hooray 7ee5bcb935 fix: 修复应用配置中复制配置按钮,复制内容过多的问题 2022-12-18 18:52:23 +08:00
hooray c239f2fa0f chore: 移除环境配置文件里无效配置 2022-12-15 22:35:12 +08:00
hooray dcdba0d310 chore: 更新依赖 2022-12-13 22:29:41 +08:00
hooray 800e729c71 refactor!: pinia store 改为 setup 写法 2022-12-13 22:27:34 +08:00
hooray 77f62626de chore: release v2.5.0 2022-12-10 17:00:23 +08:00
hooray d1fc70175e chore: 更新依赖 2022-12-10 16:59:51 +08:00
hooray 55f7e0b9d2 refactor: 优化搜索界面布局样式 2022-12-10 16:58:20 +08:00
hooray 7960d9c6a2 refactor: 给 /src/types/ 路径增加 # alias 2022-12-09 01:01:19 +08:00
hooray 6f88513a5f refactor!: 将类型定义 d.ts 文件统一存放到 /src/types/ 目录下 2022-12-08 02:32:08 +08:00
hooray b9e48842ab fix: 修复导航栏a标签href异常 2022-12-07 12:02:44 +08:00
hooray 135dc85fa8 chore: 调整全局搜索快捷键说明文字因背景导致显示不清晰 2022-12-06 21:27:50 +08:00
hooray 87ac51726f refactor: 优化遮罩层样式 2022-12-05 00:36:57 +08:00
hooray 7882cd8f22 feat!: 顶栏增加粘性模式
设置粘性模式时,页面往下滚动隐藏,往上滚动显示

BREAKING CHANGE: 移除`topbar.fixed`属性,新增`topbar.mode`属性
2022-12-04 23:11:03 +08:00
hooray 5644268411 refactor: 改用 element plus 原生 api 实现密码输入框显示密码功能 2022-12-04 03:57:51 +08:00
hooray 989453a644 chore: 更新依赖 2022-12-03 16:04:20 +08:00
hooray 18285821d9 chore: 修改 readme 2022-12-03 16:02:54 +08:00
hooray 9c3085367b chore: readme 增加部分 badge 2022-12-02 17:51:11 +08:00
hooray 52f5a76ca4 chore: 更新readme 2022-12-02 10:45:16 +08:00
hooray 12c268d18d chore: release v2.4.0 2022-12-01 22:53:50 +08:00
hooray 0c43078d32 feat!: 增加外链路由跳转提示页面,重构外链路由的配置方式
BREAKING CHANGE: 原先在路由`path`上设置外链,现在在路由`meta.link`上设置外链
2022-12-01 20:12:53 +08:00
hooray c534577feb chore: 代码微调 2022-11-30 04:09:55 +08:00
hooray 7474a19af2 fix: 修复 eslint 警告 2022-11-30 03:59:14 +08:00
hooray ae1f81762f chore: 更新依赖 2022-11-30 02:11:47 +08:00
hooray ffb3c85b83 chore: release v2.3.0 2022-11-28 00:35:20 +08:00
hooray ca1ea48dc6 refactor: 修改 Menu.recordRaw 和 Menu.recordMainRaw 类型里的 meta 为可选参数 2022-11-27 23:40:16 +08:00
hooray 5c6494931b fix: 修复标题显示异常 2022-11-27 23:37:03 +08:00
hooray e24431c38a refactor!: 移除自定义 Route.recordRaw 类型,改用 RouteRecordRaw 类型 2022-11-27 17:50:31 +08:00
hooray f85a911b8e fix: 修复文件系统路由模式下,全局搜索列表 icon 显示不全 2022-11-27 04:09:30 +08:00
hooray 776fe7a944 feat: 路由meta.title修改为非必须 2022-11-27 03:52:22 +08:00
hooray ce099b3bea chore: release v2.2.0 2022-11-26 15:37:52 +08:00
hooray eff431a09d chore: 更新依赖 2022-11-26 09:42:50 +08:00
hooray 501a8955c3 chore: 更新依赖 2022-11-19 21:37:54 +08:00
hooray f0a523f505 fix: 修复文件系统路由下,次导航无法按层级依次展开 2022-11-19 09:11:03 +08:00
hooray b2e33ca05f refactor!: 控制台修改为主页,路由调整为根路由
从原有 /dashboard 路由调整为 / 根路由
2022-11-19 09:10:30 +08:00
hooray a401b1f5bf refactor: 优化 PageHeader 组件在移动端下的展示 2022-11-13 00:51:02 +08:00
hooray 07a13b004e chore: release v2.1.3 2022-11-11 10:09:02 +08:00
hooray defd87869e ci: 优化 release 产物 2022-11-11 10:06:45 +08:00
hooray 051e6a99e1 chore: release v2.1.1 2022-11-10 19:54:08 +08:00
hooray 5f3855cc82 chore: 更新依赖 2022-11-10 19:49:23 +08:00
hooray 6489e8b94a ci: 优化 release 发布工作流 2022-11-10 19:30:30 +08:00
hooray 98ca00326a chore: husky 替换为 simple-git-hooks 2022-11-09 16:05:52 +08:00
hooray fc033c8693 chore: 更新依赖 2022-11-08 00:38:40 +08:00
hooray 57b18c2710 feat: logo 支持非正方形图片 2022-11-04 17:24:16 +08:00
hooray d667088c89 chore: release v2.1.0 2022-10-30 04:14:59 +08:00
hooray 41b3cd7a8e chore: 更新依赖 2022-10-29 18:46:32 +08:00
hooray 8e9a1e5683 chore: 更新依赖 2022-10-25 08:33:13 +08:00
hooray 05939a99ee chore: 调整 copyright 信息和展示逻辑 2022-10-25 08:27:07 +08:00
hooray 84e1581dcf chore: 移除 vite-plugin-restart 2022-10-25 08:25:12 +08:00
hooray 4a2538cfe3 refactor: 优化配置项合并 2022-10-25 08:23:20 +08:00
hooray c091c685e8 refactor!: useCurrentInstance() 重命名为 useGlobalProperties() 2022-10-23 14:25:18 +08:00
hooray 613972f4a1 refactor: 将 element plus 反馈组件改为手动引入 2022-10-23 14:18:17 +08:00
hooray d860fe74c3 chore: 给 eslint 和 stylelint 命令增加 --cache 设置 2022-10-23 14:04:53 +08:00
hooray ffa8943267 chore: 重建 pnpm-lock.yaml 文件 2022-10-23 14:03:08 +08:00
hooray babd6c9c2a chore: 更新依赖 2022-10-23 13:59:59 +08:00
hooray 7250872ac0 chore: 增加 vscode 推荐扩展 2022-10-22 00:50:57 +08:00
hooray 7f42710a55 ci: 增加 vue-tsc 校验脚本 2022-10-21 03:59:06 +08:00
hooray fe6d81c08d fix: 修复类型报错 2022-10-16 07:11:04 +08:00
hooray 1842f10172 ci: 调整 commit 配置 2022-10-16 07:10:12 +08:00
hooray 5bbde8d102 refactor: 优化拦截器 2022-10-16 06:48:53 +08:00
hooray 8771b33834 chore: 更新依赖 2022-10-16 06:00:43 +08:00
hooray 842519768e chore: 更新 tinymce 主题样式和汉化文件 2022-10-16 05:59:19 +08:00
hooray 25458835e9 chore: release v2.0.2 2022-10-15 04:05:54 +08:00
hooray abc0ac8e4c chore: 更新依赖 2022-10-14 18:42:59 +08:00
hooray 0e222b986d revert: 回退部分定位组件的 z-index 设置,强制重写 v-loading 的 z-index 样式 2022-10-13 19:19:11 +08:00
hooray 925ee00287 fix: 调整部分定位组件的z-index层级
element plus 的 v-loading 遮罩层默认 z-index 为 2000
导致会遮挡住框架部分组件
2022-10-13 14:21:47 +08:00
hooray d9291175b9 docs: 路由导航守卫代码注释调整 2022-10-13 14:19:13 +08:00
hooray c2e9b16917 refactor: 优化代码逻辑 2022-10-13 14:15:23 +08:00
hooray c0881951d3 chore: 更新依赖 2022-10-13 14:12:48 +08:00
hooray f0c6ddd221 chore: 调整文件忽略 2022-10-12 01:35:34 +08:00
hooray 07b9e31b22 fix: 修复获取侧边栏导航最深一级路由可能返回为空的问题 2022-10-12 01:30:40 +08:00
hooray a0708253af refactor: 简化导航守卫里的逻辑 2022-10-12 01:29:32 +08:00
hooray a1e1f2e4db chore: 更新依赖 2022-10-10 23:43:55 +08:00
Hooray b93c86aa44 chore: 更新依赖 2022-10-09 02:41:17 +08:00
Hooray 49e78d5c20 style: 更新 stylelint 规则 2022-10-03 22:12:09 +08:00
Hooray 33ea08fa4b style: 控制语句代码风格调整 2022-10-02 03:29:37 +08:00
Hooray 0e89630b54 docs: 更新 readme 链接 2022-09-30 14:27:31 +08:00
Hooray d6d67d357d style: 优化文件生成模版 2022-09-29 22:04:29 +08:00
Hooray 848e107388 chore: 更新依赖 2022-09-29 21:59:19 +08:00
hooray cc54fc07e4 chore: release v2.0.1 2022-09-26 00:31:14 +08:00
hooray 5a75e566e7 fix: 修改 mock 文件引入后缀 2022-09-26 00:30:00 +08:00
hooray 47461cdfa5 chore: release v2.0.0 2022-09-26 00:23:29 +08:00
hooray f17fd5ec8c chore!: typeScript 支持 2022-09-26 00:21:50 +08:00
206 changed files with 11169 additions and 7864 deletions
+69
View File
@@ -0,0 +1,69 @@
// .commitlintrc.js
/** @type {import('cz-git').UserConfig} */
module.exports = {
rules: {
// @see: https://commitlint.js.org/#/reference-rules
},
prompt: {
alias: { fd: 'docs: fix typos' },
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixsSelect: '选择关联issue前缀(可选):',
customFooterPrefixs: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?'
},
types: [
{ value: 'feat', name: 'feat: ✨ 新增功能 | A new feature', emoji: ':sparkles:' },
{ value: 'fix', name: 'fix: 🐛 修复缺陷 | A bug fix', emoji: ':bug:' },
{ value: 'docs', name: 'docs: 📝 文档更新 | Documentation only changes', emoji: ':memo:' },
{ value: 'style', name: 'style: 💄 代码格式 | Changes that do not affect the meaning of the code', emoji: ':lipstick:' },
{ value: 'refactor', name: 'refactor: ♻️ 代码重构 | A code change that neither fixes a bug nor adds a feature', emoji: ':recycle:' },
{ value: 'perf', name: 'perf: ⚡️ 性能提升 | A code change that improves performance', emoji: ':zap:' },
{ value: 'test', name: 'test: ✅ 测试相关 | Adding missing tests or correcting existing tests', emoji: ':white_check_mark:' },
{ value: 'build', name: 'build: 📦️ 构建相关 | Changes that affect the build system or external dependencies', emoji: ':package:' },
{ value: 'ci', name: 'ci: 🎡 持续集成 | Changes to our CI configuration files and scripts', emoji: ':ferris_wheel:' },
{ value: 'revert', name: 'revert: ⏪️ 回退代码 | Revert to a commit', emoji: ':rewind:' },
{ value: 'chore', name: 'chore: 🔨 其他修改 | Other changes that do not modify src or test files', emoji: ':hammer:' },
],
useEmoji: false,
emojiAlign: 'center',
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: true,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixs: [
// 如果使用 gitee 作为开发管理
{ value: 'link', name: 'link: 链接 ISSUES 进行中' },
{ value: 'closed', name: 'closed: 标记 ISSUES 已完成' }
],
customIssuePrefixsAlign: 'top',
emptyIssuePrefixsAlias: 'skip',
customIssuePrefixsAlias: 'custom',
allowCustomIssuePrefixs: true,
allowEmptyIssuePrefixs: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
}
+1 -4
View File
@@ -3,10 +3,7 @@ root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
+1 -5
View File
@@ -1,7 +1,5 @@
NODE_ENV = production
# 页面标题
VITE_APP_TITLE = Fantastic-admin 基础版
VITE_APP_TITLE = 页面标题
# 接口请求地址,会设置到 axios 的 baseURL 参数上
VITE_APP_API_BASEURL = /
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
@@ -11,7 +9,5 @@ VITE_APP_DEBUG_TOOL =
VITE_BUILD_MOCK = false
# 是否在打包时生成 sourcemap
VITE_BUILD_SOURCEMAP = false
# 是否在打包时删除 console 代码
VITE_BUILD_DROP_CONSOLE = true
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip,brotli
+2 -6
View File
@@ -1,7 +1,5 @@
NODE_ENV = production
# 页面标题
VITE_APP_TITLE = Fantastic-admin 专业版
VITE_APP_TITLE = 页面标题(test)
# 接口请求地址,会设置到 axios 的 baseURL 参数上
VITE_APP_API_BASEURL = /
# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
@@ -11,7 +9,5 @@ VITE_APP_DEBUG_TOOL =
VITE_BUILD_MOCK = true
# 是否在打包时生成 sourcemap
VITE_BUILD_SOURCEMAP = true
# 是否在打包时删除 console 代码
VITE_BUILD_DROP_CONSOLE = true
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip,brotli
VITE_BUILD_COMPRESS =
-4
View File
@@ -1,4 +0,0 @@
dist/
node_modules/
babel.config.js
.eslintrc.js
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "@antfu",
"rules": {
"curly": ["error", "all"]
}
}
-101
View File
@@ -1,101 +0,0 @@
module.exports = {
root: true,
env: {
browser: true,
es6: true
},
globals: {
__dirname: true,
process: true,
require: true,
module: true,
// element-plus
ElMessage: true,
ElMessageBox: true,
ElNotification: true,
ElLoading: true
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-strongly-recommended',
'eslint:recommended',
'./.eslintrc-auto-import.json'
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: '2020',
ecmaFeatures: {
jsx: true
}
},
rules: {
// 代码风格
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-dangle': [2, 'never'],
'comma-style': [2, 'last'],
'computed-property-spacing': [2, 'never'],
'indent': [2, 4, {
'SwitchCase': 1
}],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'linebreak-style': 0,
'multiline-ternary': [2, 'always-multiline'],
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'quotes': [2, 'single'],
'semi': [2, 'never'],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'switch-colon-spacing': [2, {
'after': true,
'before': false
}],
'object-curly-spacing': [2, 'always'],
// ES6
'arrow-parens': [2, 'as-needed'],
'arrow-spacing': [2, {
'before': true,
'after': true
}],
// Vue - https://github.com/vuejs/eslint-plugin-vue
'vue/multi-word-component-names': 0,
'vue/html-indent': [2, 4],
'vue/no-v-html': 0,
'vue/max-attributes-per-line': 0,
'vue/require-default-prop': 0,
'vue/singleline-html-element-content-newline': 0,
'vue/require-explicit-emits': [2, {
'allowProps': true
}],
'vue/script-indent': [2, 4, {
'switchCase': 1
}]
}
};
+87
View File
@@ -0,0 +1,87 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 16.x
- run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
upload-archive:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Release
id: last_release
uses: joutvhu/get-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
latest: true
- name: Create Archive
uses: thedoctor0/zip-release@main
with:
type: 'zip'
filename: fantastic-admin.${{ steps.last_release.outputs.tag_name }}.zip
exclusions: '/.git/* /.github/*'
- name: Upload Archive To Release
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
release_id: ${{ steps.last_release.outputs.id }}
draft: false
file: fantastic-admin.${{ steps.last_release.outputs.tag_name }}.zip
upload-archive-example:
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: example
- name: Get Release
id: last_release
uses: joutvhu/get-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
latest: true
- name: Create Archive
uses: thedoctor0/zip-release@main
with:
type: 'zip'
filename: fantastic-admin.example.${{ steps.last_release.outputs.tag_name }}.zip
exclusions: '/.git/* /.github/*'
- name: Upload Archive To Release
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
release_id: ${{ steps.last_release.outputs.id }}
draft: false
file: fantastic-admin.example.${{ steps.last_release.outputs.tag_name }}.zip
+3 -2
View File
@@ -3,7 +3,8 @@ node_modules
dist*
dist-ssr
*.local
.eslintrc-auto-import.json
public/icons
.eslintcache
.stylelintcache
src/assets/sprites/*.*
!src/assets/sprites/.gitkeep
public/icons
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm lint-staged
+3 -3
View File
@@ -1,5 +1,5 @@
{
"src/**/*.{js,jsx}": "eslint --fix",
"src/**/*.{css,scss}": "stylelint --fix --allow-empty-input",
"src/**/*.vue": ["eslint --fix", "stylelint --fix --allow-empty-input"]
"src/**/*.{js,jsx,ts,tsx}": "eslint --cache --fix",
"src/**/*.{css,scss}": "stylelint --cache --fix --allow-empty-input",
"src/**/*.vue": ["eslint --cache --fix", "stylelint --cache --fix --allow-empty-input"]
}
+2
View File
@@ -0,0 +1,2 @@
shamefully-hoist=true
strict-peer-dependencies=false
+18 -46
View File
@@ -1,48 +1,20 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-recommended-vue/scss"
],
"overrides": [
{
"files": ["**/*.scss"],
"customSyntax": "postcss-scss"
}
],
"plugins": [
"stylelint-scss"
],
"rules": {
"indentation": 4,
"rule-empty-line-before": "never",
"at-rule-empty-line-before": "never",
"at-rule-no-unknown": null,
"no-descending-specificity": null,
"selector-pseudo-class-no-unknown": null,
"property-no-unknown": null,
"font-family-no-missing-generic-family-keyword": null,
"selector-id-pattern": null,
"selector-class-pattern": null,
"scss/no-global-function-names": null,
"scss/at-import-partial-extension": null,
"function-no-unknown": [
true,
{
"ignoreFunctions": [
"v-bind",
"lighten",
"darken"
]
}
],
"property-no-vendor-prefix": [
true,
{
"ignoreProperties": [
"mask-image"
]
}
],
"max-line-length": null
}
"extends": [
"stylelint-config-standard",
"stylelint-config-standard-scss",
"stylelint-config-standard-vue/scss"
],
"plugins": [
"stylelint-scss"
],
"rules": {
"at-rule-no-unknown": null,
"no-descending-specificity": null,
"property-no-unknown": null,
"font-family-no-missing-generic-family-keyword": null,
"selector-class-pattern": null,
"max-line-length": null,
"scss/double-slash-comment-empty-line-before": null,
"scss/no-global-function-names": null
}
}
+8 -7
View File
@@ -1,9 +1,10 @@
{
"recommendations": [
"EditorConfig.EditorConfig",
"mikestead.dotenv",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"vue.volar"
]
"recommendations": [
"EditorConfig.EditorConfig",
"mikestead.dotenv",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
]
}
+5 -5
View File
@@ -1,7 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"stylelint.validate": ["css", "scss", "vue"]
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"stylelint.validate": ["css", "scss", "vue"]
}
+56 -51
View File
@@ -1,5 +1,5 @@
<p align="center">
<img src="https://hooray.gitee.io/fantastic-admin/logo.png" width="200" height="200" />
<img src="https://hooray.gitee.io/fantastic-admin/logo.png" width="200" height="200" />
</p>
<h1 align="center">Fantastic-admin</h1>
@@ -7,18 +7,23 @@
<p align="center">一款<b>开箱即用</b>的 Vue 中后台管理系统框架</p>
<p align="center">
<a href="https://hooray.gitee.io/fantastic-admin/" target="_blank">官网</a>
( <a href="https://hooray.github.io/fantastic-admin" target="_blank">备用</a> )
<span>&nbsp;|&nbsp;</span>
演示
( <a href="https://hooray.gitee.io/fantastic-admin/vue3/basic" target="_blank">基础版</a>
<a href="https://hooray.gitee.io/fantastic-admin/vue3/pro" target="_blank">专业版</a> )
<span>&nbsp;|&nbsp;</span>
<a href="https://hooray.gitee.io/fantastic-admin/support.html" target="_blank">技术支持</a>
<span>&nbsp;|&nbsp;</span>
<a href="https://hooray.gitee.io/fantastic-admin/buy.html" target="_blank">购买</a>
<a href="https://hooray.gitee.io/fantastic-admin" target="_blank">官网</a>
( <a href="https://hooray.github.io/fantastic-admin" target="_blank">备用</a> )
<span>&nbsp;|&nbsp;</span>
演示
( <a href="https://hooray.gitee.io/fantastic-admin-example" target="_blank">基础版</a>
<a href="https://hooray.gitee.io/fantastic-admin-pro-example" target="_blank">专业版</a> )
<span>&nbsp;|&nbsp;</span>
<a href="https://hooray.gitee.io/fantastic-admin/support.html" target="_blank">技术支持</a>
<span>&nbsp;|&nbsp;</span>
<a href="https://hooray.gitee.io/fantastic-admin/buy.html" target="_blank">购买</a>
<p>
<p align="center">
<a href="###"><img src="https://img.shields.io/github/license/hooray/fantastic-admin?label=%E5%BC%80%E6%BA%90%E5%8D%8F%E8%AE%AE&style=flat-square" /></a>
<a href="https://github.com/hooray/fantastic-admin/releases" target="_blank"><img src="https://img.shields.io/github/v/release/hooray/fantastic-admin?label=%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC&style=flat-square" /></a>
</p>
## 特点
- 丰富的布局与主题,覆盖市面上各种中后台应用场景,兼容PC、平板和移动端
@@ -43,16 +48,16 @@
> 预览截图为 Vue3 专业版
<table>
<tr>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview1.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview2.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview3.png" /></td>
</tr>
<tr>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview4.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview5.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview6.png" /></td>
</tr>
<tr>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview1.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview2.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview3.png" /></td>
</tr>
<tr>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview4.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview5.png" /></td>
<td><img src="https://hooray.gitee.io/fantastic-admin/preview6.png" /></td>
</tr>
</table>
## 支持
@@ -66,37 +71,37 @@
## 生态
<table>
<tr>
<th colspan="3" align="center">
<a href="https://hooray.gitee.io/fantastic-startkit/" target="_blank">Fantastic-startkit</a>
</th>
</tr>
<tr>
<th colspan="3" align="center">
一款开箱即用的 Vue 项目启动套件
</th>
</tr>
<tr>
<th colspan="3" align="center">
<a href="https://hooray.gitee.io/fantastic-startkit/" target="_blank">Fantastic-startkit</a>
</th>
</tr>
<tr>
<th colspan="3" align="center">
一款开箱即用的 Vue 项目启动套件
</th>
</tr>
</table>
<table>
<tr>
<th colspan="3" align="center">
<a href="https://hooray.gitee.io/one-step-admin/" target="_blank">One-step-admin</a>
</th>
</tr>
<tr>
<th colspan="3" align="center">
一款干啥都快人一步的 Vue 中后台管理系统框架
</th>
</tr>
<tr>
<td><img src="https://hooray.gitee.io/one-step-admin/preview1.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview2.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview3.png" /></td>
</tr>
<tr>
<td><img src="https://hooray.gitee.io/one-step-admin/preview4.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview5.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview6.png" /></td>
</tr>
<tr>
<th colspan="3" align="center">
<a href="https://hooray.gitee.io/one-step-admin/" target="_blank">One-step-admin</a>
</th>
</tr>
<tr>
<th colspan="3" align="center">
一款干啥都快人一步的 Vue 中后台管理系统框架
</th>
</tr>
<tr>
<td><img src="https://hooray.gitee.io/one-step-admin/preview1.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview2.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview3.png" /></td>
</tr>
<tr>
<td><img src="https://hooray.gitee.io/one-step-admin/preview4.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview5.png" /></td>
<td><img src="https://hooray.gitee.io/one-step-admin/preview6.png" /></td>
</tr>
</table>
+36 -58
View File
@@ -1,62 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title>
<%- copyrightScript %>
</head>
<body>
<div id="app">
<div class="fantastic-admin-home">
<div class="loading">
<div class="square"></div>
<div class="square"></div>
<div class="square"></div>
<div class="square"></div>
</div>
<div class="text"><%= title %>载入中…</div>
</div>
<div id="browser-upgrade">
<div class="title">为了您的体验,推荐使用以下浏览器</div>
<div class="browsers">
<a href="https://www.microsoft.com/edge" target="_blank" class="browser">
<img class="browser-icon" src="/browser_upgrade/edge.png" />
<div class="browser-name">Mircosoft Edge</div>
</a>
<a href="https://www.google.cn/chrome/" target="_blank" class="browser">
<img class="browser-icon" src="/browser_upgrade/chrome.png" />
<div class="browser-name">Google Chrome</div>
</a>
</div>
</div>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/browser_upgrade/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title>
<%- copyrightScript %>
</head>
<body>
<div id="app">
<div class="fantastic-admin-home">
<div class="loading">
<div class="square"></div>
<div class="square"></div>
<div class="square"></div>
<div class="square"></div>
</div>
<script>
if(!!window.ActiveXObject || "ActiveXObject" in window) {
document.getElementById('browser-upgrade').style.display = 'block'
} else {
var Loading = document.querySelector('.loading')
Loading.classList.add('animate')
Loading.addEventListener('animationend', function() {
setTimeout(function() {
Loading.classList.remove('animate')
}, 600)
setTimeout(function() {
Loading.classList.add('animate')
}, 1000)
})
}
</script>
<script type="module" src="/src/main.js"></script>
<% if (debugTool == 'eruda') { %>
<script src="//unpkg.com/eruda/eruda.js"></script>
<script>eruda.init();</script>
<% } %>
<% if (debugTool == 'vconsole') { %>
<script src="//unpkg.com/vconsole/dist/vconsole.min.js"></script>
<script>new VConsole();</script>
<% } %>
</body>
<div class="text"><%= title %>载入中…</div>
</div>
<div id="browser-upgrade">
<div class="title">为了您的体验,推荐使用以下浏览器</div>
<div class="browsers">
<a href="https://www.microsoft.com/edge" target="_blank" class="browser">
<img class="browser-icon" src="/browser_upgrade/edge.png" />
<div class="browser-name">Mircosoft Edge</div>
</a>
<a href="https://www.google.cn/chrome/" target="_blank" class="browser">
<img class="browser-icon" src="/browser_upgrade/chrome.png" />
<div class="browser-name">Google Chrome</div>
</a>
</div>
</div>
</div>
<%- loadingScript %>
<script type="module" src="/src/main.ts"></script>
<%- devtoolScript %>
</body>
</html>
-12
View File
@@ -1,12 +0,0 @@
{
"compilerOptions": {
"target": "es2017",
"allowSyntheticDefaultImports": false,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"jsx": "preserve"
},
"exclude": ["node_modules", "dist", "dist-test"]
}
+90 -73
View File
@@ -1,76 +1,93 @@
{
"name": "fantastic-admin",
"version": "0.1.0",
"scripts": {
"dev": "vite",
"build:test": "vite build --mode test",
"build": "vite build",
"serve:test": "http-server ./dist-test -o",
"serve": "http-server ./dist -o",
"svgo": "svgo -f src/assets/icons",
"new": "plop",
"lint:eslint": "eslint src/**/*.{js,vue} --fix",
"lint:stylelint": "stylelint src/**/*.{css,scss,vue} --fix",
"prepare": "husky install",
"postinstall": "esno scripts/prepare.js",
"preinstall": "npx only-allow pnpm"
},
"dependencies": {
"@tinymce/tinymce-vue": "^5.0.0",
"@vueuse/core": "^9.2.0",
"@vueuse/integrations": "^9.2.0",
"axios": "^0.27.2",
"dayjs": "^1.11.5",
"element-plus": "^2.2.16",
"hotkeys-js": "^3.10.0",
"js-cookie": "^3.0.1",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.1",
"pinia": "^2.0.22",
"qs": "^6.11.0",
"tinymce": "^6.2.0",
"vue": "^3.2.39",
"vue-router": "^4.1.3"
},
"devDependencies": {
"@iconify-json/ep": "^1.1.7",
"@iconify/vue": "^4.0.0",
"@vitejs/plugin-vue": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^2.0.1",
"@vue/compiler-sfc": "^3.2.39",
"eslint": "^8.23.1",
"eslint-plugin-vue": "^9.3.0",
"esno": "^0.16.3",
"fs-extra": "^10.1.0",
"http-server": "^14.1.1",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"plop": "^3.1.1",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.5",
"sass": "^1.54.9",
"stylelint": "^14.10.0",
"stylelint-config-recommended-scss": "^7.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^28.0.0",
"stylelint-scss": "^4.3.0",
"svgo": "^2.8.0",
"unplugin-auto-import": "^0.11.2",
"unplugin-vue-components": "^0.22.7",
"vite": "^3.1.1",
"vite-plugin-banner": "^0.5.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-pages": "^0.26.0",
"vite-plugin-restart": "^0.2.0",
"vite-plugin-spritesmith": "^0.1.1",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-layouts": "^0.6.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-eslint-parser": "^9.1.0"
"version": "2.6.1",
"scripts": {
"dev": "vite",
"build:test": "vue-tsc --noEmit && vite build --mode test",
"build": "vue-tsc --noEmit && vite build",
"serve:test": "http-server ./dist-test -o",
"serve": "http-server ./dist -o",
"svgo": "svgo -f src/assets/icons",
"new": "plop",
"lint": "npm-run-all -s lint:tsc lint:eslint lint:stylelint",
"lint:tsc": "vue-tsc --noEmit",
"lint:eslint": "eslint src/**/*.{ts,tsx,vue} --cache --fix",
"lint:stylelint": "stylelint src/**/*.{css,scss,vue} --cache --fix",
"postinstall": "simple-git-hooks && esno scripts/prepare.js",
"preinstall": "npx only-allow pnpm",
"commit": "git cz",
"release": "bumpp"
},
"dependencies": {
"@tinymce/tinymce-vue": "^5.0.0",
"@vueuse/core": "^9.10.0",
"@vueuse/integrations": "^9.10.0",
"axios": "^1.2.1",
"dayjs": "^1.11.7",
"element-plus": "^2.2.28",
"hotkeys-js": "^3.10.1",
"lodash-es": "^4.17.21",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.1",
"pinia": "^2.0.28",
"qs": "^6.11.0",
"tinymce": "^6.3.1",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@antfu/eslint-config": "^0.34.0",
"@iconify-json/ep": "^1.1.8",
"@iconify/vue": "^4.0.2",
"@types/nprogress": "^0.2.0",
"@types/path-browserify": "^1.0.0",
"@types/qs": "^6.9.7",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"autoprefixer": "^10.4.13",
"bumpp": "^8.2.1",
"cz-git": "^1.4.1",
"eslint": "^8.31.0",
"esno": "^0.16.3",
"fs-extra": "^11.1.0",
"http-server": "^14.1.1",
"lint-staged": "^13.1.0",
"npm-run-all": "^4.1.5",
"plop": "^3.1.1",
"postcss-html": "^1.5.0",
"sass": "^1.57.1",
"simple-git-hooks": "^2.8.1",
"stylelint": "^14.16.1",
"stylelint-config-standard": "^29.0.0",
"stylelint-config-standard-scss": "^6.1.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-scss": "^4.3.0",
"svgo": "^3.0.2",
"typescript": "^4.9.4",
"unplugin-auto-import": "^0.12.1",
"unplugin-vue-components": "^0.22.12",
"vite": "^4.0.4",
"vite-plugin-banner": "^0.7.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-pages": "^0.28.0",
"vite-plugin-spritesmith": "^0.1.1",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-inspector": "^3.0.1",
"vite-plugin-vue-layouts": "^0.6.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^1.0.24"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged",
"preserveUnused": true
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
}
}
+6 -7
View File
@@ -1,14 +1,13 @@
<script setup{{#if isGlobal}} name="{{ properCase name }}"{{/if}}>
// const router = useRouter()
// const route = useRoute()
<script lang="ts" setup{{#if isGlobal}} name="{{ properCase name }}"{{/if}}>
// 逻辑代码
</script>
<template>
<div>
<!-- 内容区 -->
</div>
<div>
<!-- 布局 -->
</div>
</template>
<style lang="scss" scoped>
// scss
// 样式
</style>
+55 -53
View File
@@ -1,63 +1,65 @@
const fs = require('fs')
function getFolder(path) {
let components = []
const files = fs.readdirSync(path)
files.forEach(function(item) {
let stat = fs.lstatSync(path + '/' + item)
if (stat.isDirectory() === true && item != 'components') {
components.push(path + '/' + item)
components.push.apply(components, getFolder(path + '/' + item))
}
})
return components
const components = []
const files = fs.readdirSync(path)
files.forEach((item) => {
const stat = fs.lstatSync(`${path}/${item}`)
if (stat.isDirectory() === true && item !== 'components') {
components.push(`${path}/${item}`)
components.push(...getFolder(`${path}/${item}`))
}
})
return components
}
module.exports = {
description: '创建组件',
prompts: [
{
type: 'confirm',
name: 'isGlobal',
message: '是否为全局组件',
default: false
},
{
type: 'list',
name: 'path',
message: '请选择组件创建目录',
choices: getFolder('src/views'),
when: answers => {
return !answers.isGlobal
}
},
{
type: 'input',
name: 'name',
message: '请输入组件名称',
validate: v => {
if (!v || v.trim === '') {
return '组件名称不能为空'
} else {
return true
}
}
description: '创建组件',
prompts: [
{
type: 'confirm',
name: 'isGlobal',
message: '是否为全局组件',
default: false,
},
{
type: 'list',
name: 'path',
message: '请选择组件创建目录',
choices: getFolder('src/views'),
when: (answers) => {
return !answers.isGlobal
},
},
{
type: 'input',
name: 'name',
message: '请输入组件名称',
validate: (v) => {
if (!v || v.trim === '') {
return '组件名称不能为空'
}
],
actions: data => {
let path = ''
if (data.isGlobal) {
path = 'src/components/{{properCase name}}/index.vue'
} else {
path = `${data.path}/components/{{properCase name}}/index.vue`
else {
return true
}
const actions = [
{
type: 'add',
path: path,
templateFile: 'plop-templates/component/index.hbs'
}
]
return actions
},
},
],
actions: (data) => {
let path = ''
if (data.isGlobal) {
path = 'src/components/{{properCase name}}/index.vue'
}
else {
path = `${data.path}/components/{{properCase name}}/index.vue`
}
const actions = [
{
type: 'add',
path,
templateFile: 'plop-templates/component/index.hbs',
},
]
return actions
},
}
-80
View File
@@ -1,80 +0,0 @@
import Mock from 'mockjs'
const AllList = []
for (let i = 0; i < 50; i++) {
AllList.push(Mock.mock({
id: '@id',
title: '@ctitle(10, 20)'
}))
}
export default [
{
url: '/mock/{{#if relativePath}}{{ relativePath }}/{{/if}}{{ moduleName }}/list',
method: 'get',
response: option => {
let { title, from, limit } = option.query
from = ~~from
limit = ~~limit
let list = AllList.filter(item => {
return title ? item.title.includes(title) : true
})
let pageList = list.filter((item, index) => {
return index >= from && index < (from + limit)
})
return {
error: '',
status: 1,
data: {
list: pageList,
total: list.length
}
}
}
},
{
url: '/mock/{{#if relativePath}}{{ relativePath }}/{{/if}}{{ moduleName }}/detail',
method: 'get',
response: option => {
let info = AllList.filter(item => item.id == option.query.id)
return {
error: '',
status: 1,
data: info[0]
}
}
},
{
url: '/mock/{{#if relativePath}}{{ relativePath }}/{{/if}}{{ moduleName }}/create',
method: 'post',
response: {
error: '',
status: 1,
data: {
isSuccess: true
}
}
},
{
url: '/mock/{{#if relativePath}}{{ relativePath }}/{{/if}}{{ moduleName }}/edit',
method: 'post',
response: {
error: '',
status: 1,
data: {
isSuccess: true
}
}
},
{
url: '/mock/{{#if relativePath}}{{ relativePath }}/{{/if}}{{ moduleName }}/delete',
method: 'post',
response: {
error: '',
status: 1,
data: {
isSuccess: true
}
}
}
]
-43
View File
@@ -1,43 +0,0 @@
const path = require('path')
const fs = require('fs')
function getFolder(path) {
let components = []
const files = fs.readdirSync(path)
files.forEach(function(item) {
let stat = fs.lstatSync(path + '/' + item)
if (stat.isDirectory() === true && item != 'components') {
components.push(path + '/' + item)
components.push.apply(components, getFolder(path + '/' + item))
}
})
return components
}
module.exports = {
description: '创建标准模块 Mock',
prompts: [
{
type: 'list',
name: 'path',
message: '请选择模块目录',
choices: getFolder('src/views')
}
],
actions: data => {
let pathArr = path.relative('src/views', data.path).split('\\')
let moduleName = pathArr.pop()
let relativePath = pathArr.join('/')
const actions = []
actions.push({
type: 'add',
path: pathArr.length == 0 ? 'src/mock/{{moduleName}}.js' : `src/mock/${pathArr.join('.')}.{{moduleName}}.js`,
templateFile: 'plop-templates/mock/mock.hbs',
data: {
relativePath,
moduleName
}
})
return actions
}
}
+6 -10
View File
@@ -1,17 +1,13 @@
<script setup name="{{ properCase componentName }}">
// const router = useRouter()
// const route = useRoute()
<script lang="ts" setup name="{{ properCase componentName }}">
// 逻辑代码
</script>
<template>
<div>
<page-header title="{{ cname }}" />
<page-main>
页面内容区域
</page-main>
</div>
<div>
<!-- 布局 -->
</div>
</template>
<style lang="scss" scoped>
// scss
// 样式
</style>
+45 -52
View File
@@ -2,60 +2,53 @@ const path = require('path')
const fs = require('fs')
function getFolder(path) {
let components = []
const files = fs.readdirSync(path)
files.forEach(function(item) {
let stat = fs.lstatSync(path + '/' + item)
if (stat.isDirectory() === true && item != 'components') {
components.push(path + '/' + item)
components.push.apply(components, getFolder(path + '/' + item))
}
})
return components
const components = []
const files = fs.readdirSync(path)
files.forEach((item) => {
const stat = fs.lstatSync(`${path}/${item}`)
if (stat.isDirectory() === true && item !== 'components') {
components.push(`${path}/${item}`)
components.push(...getFolder(`${path}/${item}`))
}
})
return components
}
module.exports = {
description: '创建页面',
prompts: [
{
type: 'list',
name: 'path',
message: '请选择页面创建目录',
choices: getFolder('src/views')
},
{
type: 'input',
name: 'name',
message: '请输入文件名',
validate: v => {
if (!v || v.trim === '') {
return '文件名不能为空'
} else if (v == 'list' || v == 'detail') {
return 'list 和 detail 为保留关键字,无法直接创建,请通过创建标准模块进行生成'
} else {
return true
}
}
},
{
type: 'input',
name: 'cname',
message: '请输入页面中文名称',
default: '默认页面'
description: '创建页面',
prompts: [
{
type: 'list',
name: 'path',
message: '请选择页面创建目录',
choices: getFolder('src/views'),
},
{
type: 'input',
name: 'name',
message: '请输入文件名',
validate: (v) => {
if (!v || v.trim === '') {
return '文件名不能为空'
}
],
actions: data => {
let relativePath = path.relative('src/views', data.path)
const actions = [
{
type: 'add',
path: `${data.path}/{{dotCase name}}.vue`,
templateFile: 'plop-templates/page/index.hbs',
data: {
componentName: `${relativePath} ${data.name}`
}
}
]
return actions
}
else {
return true
}
},
},
],
actions: (data) => {
const relativePath = path.relative('src/views', data.path)
const actions = [
{
type: 'add',
path: `${data.path}/{{dotCase name}}.vue`,
templateFile: 'plop-templates/page/index.hbs',
data: {
componentName: `${relativePath} ${data.name}`,
},
},
]
return actions
},
}
+7 -7
View File
@@ -1,11 +1,11 @@
const use{{ properCase name }}Store = defineStore(
// 唯一ID
'{{ camelCase name }}',
{
state: () => ({}),
getters: {},
actions: {}
}
// 唯一ID
'{{ camelCase name }}',
{
state: () => ({}),
getters: {},
actions: {},
},
)
export default use{{ properCase name }}Store
+25 -24
View File
@@ -1,27 +1,28 @@
module.exports = {
description: '创建全局状态',
prompts: [
{
type: 'input',
name: 'name',
message: '请输入模块名称',
validate: v => {
if (!v || v.trim === '') {
return '模块名称不能为空'
} else {
return true
}
}
description: '创建全局状态',
prompts: [
{
type: 'input',
name: 'name',
message: '请输入模块名称',
validate: (v) => {
if (!v || v.trim === '') {
return '模块名称不能为空'
}
],
actions: () => {
const actions = [
{
type: 'add',
path: 'src/store/modules/{{camelCase name}}.js',
templateFile: 'plop-templates/store/index.hbs'
}
]
return actions
}
else {
return true
}
},
},
],
actions: () => {
const actions = [
{
type: 'add',
path: 'src/store/modules/{{camelCase name}}.ts',
templateFile: 'plop-templates/store/index.hbs',
},
]
return actions
},
}
+5 -6
View File
@@ -1,7 +1,6 @@
module.exports = function(plop) {
plop.setWelcomeMessage('请选择需要创建的模式:')
plop.setGenerator('page', require('./plop-templates/page/prompt'))
plop.setGenerator('component', require('./plop-templates/component/prompt'))
plop.setGenerator('store', require('./plop-templates/store/prompt'))
plop.setGenerator('mock', require('./plop-templates/mock/prompt'))
module.exports = function (plop) {
plop.setWelcomeMessage('请选择需要创建的模式:')
plop.setGenerator('page', require('./plop-templates/page/prompt'))
plop.setGenerator('component', require('./plop-templates/component/prompt'))
plop.setGenerator('store', require('./plop-templates/store/prompt'))
}
+2498 -1038
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {},
},
}
File diff suppressed because one or more lines are too long
+51 -6
View File
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -186,6 +203,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -215,6 +253,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.3);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.3);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -506,10 +551,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -205,6 +222,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -234,6 +272,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.1);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.1);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -525,10 +570,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+90 -19
View File
@@ -252,11 +252,24 @@ button::-moz-focus-inner {
margin: 0;
outline: none;
padding: 4px 16px;
position: relative;
text-align: center;
text-decoration: none;
text-transform: none;
white-space: nowrap;
}
.tox .tox-button::before {
border-radius: 6px;
bottom: -1px;
box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #006ce7, 0 0 0 3px rgba(0, 108, 231, 0.25);
content: '';
left: -1px;
opacity: 0;
pointer-events: none;
position: absolute;
right: -1px;
top: -1px;
}
.tox .tox-button[disabled] {
background-color: #006ce7;
background-image: none;
@@ -272,6 +285,9 @@ button::-moz-focus-inner {
box-shadow: none;
color: #fff;
}
.tox .tox-button:focus-visible:not(:disabled)::before {
opacity: 1;
}
.tox .tox-button:hover:not(:disabled) {
background-color: #0060ce;
background-image: none;
@@ -933,8 +949,9 @@ button::-moz-focus-inner {
justify-content: space-between;
}
.tox .tox-comment__date {
color: rgba(255, 255, 255, 0.5);
color: #fff;
font-size: 12px;
line-height: 18px;
}
.tox .tox-comment__body {
color: #fff;
@@ -1093,20 +1110,30 @@ button::-moz-focus-inner {
.tox .tox-user__avatar svg {
fill: rgba(255, 255, 255, 0.5);
}
.tox .tox-user__avatar img {
border-radius: 50%;
height: 36px;
object-fit: cover;
vertical-align: middle;
width: 36px;
}
.tox .tox-user__name {
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
color: #fff;
font-size: 14px;
font-style: normal;
font-weight: bold;
text-transform: uppercase;
line-height: 18px;
text-transform: none;
}
.tox:not([dir=rtl]) .tox-user__avatar svg {
.tox:not([dir=rtl]) .tox-user__avatar svg,
.tox:not([dir=rtl]) .tox-user__avatar img {
margin-right: 8px;
}
.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar svg {
.tox[dir=rtl] .tox-user__avatar svg,
.tox[dir=rtl] .tox-user__avatar img {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar + .tox-user__name {
@@ -1151,10 +1178,11 @@ button::-moz-focus-inner {
width: 95vw;
z-index: 2;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog {
align-self: flex-start;
margin: 8px auto;
max-height: calc(100vh - 8px * 2);
width: calc(100vw - 16px);
}
}
@@ -1210,7 +1238,7 @@ button::-moz-focus-inner {
text-align: left;
text-transform: none;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body {
flex-direction: column;
}
@@ -1221,7 +1249,7 @@ button::-moz-focus-inner {
flex-direction: column;
padding: 16px 16px;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {
flex-direction: row;
-webkit-overflow-scrolling: touch;
@@ -1389,6 +1417,9 @@ button::-moz-focus-inner {
padding-bottom: 8px;
padding-top: 8px;
}
.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {
background: #fff;
}
.tox .tox-dialog__popups {
position: absolute;
width: 100%;
@@ -1421,12 +1452,12 @@ button::-moz-focus-inner {
.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {
transition-delay: 0s;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {
margin-right: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {
margin-left: 8px;
}
@@ -1438,12 +1469,12 @@ button::-moz-focus-inner {
.tox[dir=rtl] .tox-dialog__body {
text-align: right;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {
margin-left: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {
margin-right: 8px;
}
@@ -1500,6 +1531,8 @@ body.tox-dialog__disable-scroll {
overflow: hidden;
}
.tox .tox-editor-header {
display: grid;
grid-template-columns: 1fr min-content;
z-index: 1;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header {
@@ -1581,8 +1614,7 @@ body.tox-dialog__disable-scroll {
max-width: 25em;
}
.tox .tox-autocompleter .tox-menu {
border-color: #161f29;
box-shadow: none;
box-sizing: border-box;
max-width: 25em;
}
.tox .tox-autocompleter .tox-autocompleter-highlight {
@@ -2073,6 +2105,12 @@ body.tox-dialog__disable-scroll {
.tox .tox-menu.tox-collection.tox-collection--grid {
padding: 8px;
}
@media only screen and (min-width: 768px ) {
.tox .tox-menu .tox-collection__item-label {
overflow-wrap: break-word;
word-break: normal;
}
}
.tox .tox-menu__label h1,
.tox .tox-menu__label h2,
.tox .tox-menu__label h3,
@@ -2085,16 +2123,45 @@ body.tox-dialog__disable-scroll {
margin: 0;
}
.tox .tox-menubar {
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='transparent'/%3E%3C/svg%3E") left 0 top 0 #222F3E;
background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;
background-color: #222F3E;
display: flex;
flex: 0 0 auto;
flex-shrink: 0;
flex-wrap: wrap;
grid-column: 1 / -1;
grid-row: 1;
padding: 0 11px 0 12px;
}
.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {
border-top: 1px solid transparent;
.tox .tox-promotion + .tox-menubar {
grid-column: 1;
}
.tox .tox-promotion {
background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;
background-color: #222F3E;
grid-column: 2;
grid-row: 1;
padding-inline-end: 8px;
padding-inline-start: 4px;
padding-top: 5px;
}
.tox .tox-promotion-link {
align-items: unsafe center;
background-color: #E8F1F8;
border-radius: 5px;
color: #086BE6;
cursor: pointer;
display: flex;
font-size: 14px;
height: 26.6px;
padding: 4px 8px;
white-space: nowrap;
}
.tox .tox-promotion-link:hover {
background-color: #B4D7FF;
}
.tox .tox-promotion-link:focus {
background-color: #D9EDF7;
}
/* Deprecated. Remove in next major release */
.tox .tox-mbtn {
@@ -2874,10 +2941,14 @@ body.tox-dialog__disable-scroll {
.tox .tox-toolbar__overflow--shrinking {
transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;
}
.tox .tox-toolbar-overlord,
.tox .tox-anchorbar {
grid-column: 1 / -1;
}
.tox .tox-menubar + .tox-toolbar,
.tox .tox-menubar + .tox-toolbar-overlord {
border-top: 1px solid transparent;
margin-top: 0px;
margin-top: -1px;
padding-bottom: 1px;
padding-top: 1px;
}
File diff suppressed because one or more lines are too long
+51 -6
View File
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -205,6 +222,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -234,6 +272,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.1);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.1);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -525,10 +570,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -205,6 +222,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -234,6 +272,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.1);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.1);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -525,10 +570,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+90 -19
View File
@@ -252,11 +252,24 @@ button::-moz-focus-inner {
margin: 0;
outline: none;
padding: 4px 16px;
position: relative;
text-align: center;
text-decoration: none;
text-transform: none;
white-space: nowrap;
}
.tox .tox-button::before {
border-radius: 6px;
bottom: -1px;
box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #006ce7, 0 0 0 3px rgba(0, 108, 231, 0.25);
content: '';
left: -1px;
opacity: 0;
pointer-events: none;
position: absolute;
right: -1px;
top: -1px;
}
.tox .tox-button[disabled] {
background-color: #006ce7;
background-image: none;
@@ -272,6 +285,9 @@ button::-moz-focus-inner {
box-shadow: none;
color: #fff;
}
.tox .tox-button:focus-visible:not(:disabled)::before {
opacity: 1;
}
.tox .tox-button:hover:not(:disabled) {
background-color: #0060ce;
background-image: none;
@@ -933,8 +949,9 @@ button::-moz-focus-inner {
justify-content: space-between;
}
.tox .tox-comment__date {
color: rgba(34, 47, 62, 0.7);
color: #222f3e;
font-size: 12px;
line-height: 18px;
}
.tox .tox-comment__body {
color: #222f3e;
@@ -1093,20 +1110,30 @@ button::-moz-focus-inner {
.tox .tox-user__avatar svg {
fill: rgba(34, 47, 62, 0.7);
}
.tox .tox-user__avatar img {
border-radius: 50%;
height: 36px;
object-fit: cover;
vertical-align: middle;
width: 36px;
}
.tox .tox-user__name {
color: rgba(34, 47, 62, 0.7);
font-size: 12px;
color: #222f3e;
font-size: 14px;
font-style: normal;
font-weight: bold;
text-transform: uppercase;
line-height: 18px;
text-transform: none;
}
.tox:not([dir=rtl]) .tox-user__avatar svg {
.tox:not([dir=rtl]) .tox-user__avatar svg,
.tox:not([dir=rtl]) .tox-user__avatar img {
margin-right: 8px;
}
.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar svg {
.tox[dir=rtl] .tox-user__avatar svg,
.tox[dir=rtl] .tox-user__avatar img {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar + .tox-user__name {
@@ -1151,10 +1178,11 @@ button::-moz-focus-inner {
width: 95vw;
z-index: 2;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog {
align-self: flex-start;
margin: 8px auto;
max-height: calc(100vh - 8px * 2);
width: calc(100vw - 16px);
}
}
@@ -1210,7 +1238,7 @@ button::-moz-focus-inner {
text-align: left;
text-transform: none;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body {
flex-direction: column;
}
@@ -1221,7 +1249,7 @@ button::-moz-focus-inner {
flex-direction: column;
padding: 16px 16px;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {
flex-direction: row;
-webkit-overflow-scrolling: touch;
@@ -1389,6 +1417,9 @@ button::-moz-focus-inner {
padding-bottom: 8px;
padding-top: 8px;
}
.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {
background: #fff;
}
.tox .tox-dialog__popups {
position: absolute;
width: 100%;
@@ -1421,12 +1452,12 @@ button::-moz-focus-inner {
.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {
transition-delay: 0s;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {
margin-right: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {
margin-left: 8px;
}
@@ -1438,12 +1469,12 @@ button::-moz-focus-inner {
.tox[dir=rtl] .tox-dialog__body {
text-align: right;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {
margin-left: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {
margin-right: 8px;
}
@@ -1500,6 +1531,8 @@ body.tox-dialog__disable-scroll {
overflow: hidden;
}
.tox .tox-editor-header {
display: grid;
grid-template-columns: 1fr min-content;
z-index: 1;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header {
@@ -1581,8 +1614,7 @@ body.tox-dialog__disable-scroll {
max-width: 25em;
}
.tox .tox-autocompleter .tox-menu {
border-color: #eeeeee;
box-shadow: none;
box-sizing: border-box;
max-width: 25em;
}
.tox .tox-autocompleter .tox-autocompleter-highlight {
@@ -2073,6 +2105,12 @@ body.tox-dialog__disable-scroll {
.tox .tox-menu.tox-collection.tox-collection--grid {
padding: 8px;
}
@media only screen and (min-width: 768px ) {
.tox .tox-menu .tox-collection__item-label {
overflow-wrap: break-word;
word-break: normal;
}
}
.tox .tox-menu__label h1,
.tox .tox-menu__label h2,
.tox .tox-menu__label h3,
@@ -2085,16 +2123,45 @@ body.tox-dialog__disable-scroll {
margin: 0;
}
.tox .tox-menubar {
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='transparent'/%3E%3C/svg%3E") left 0 top 0 #fff;
background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;
background-color: #fff;
display: flex;
flex: 0 0 auto;
flex-shrink: 0;
flex-wrap: wrap;
grid-column: 1 / -1;
grid-row: 1;
padding: 0 11px 0 12px;
}
.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {
border-top: 1px solid transparent;
.tox .tox-promotion + .tox-menubar {
grid-column: 1;
}
.tox .tox-promotion {
background: repeating-linear-gradient(transparent 0px 1px, transparent 1px 39px) center top 39px / 100% calc(100% - 39px) no-repeat;
background-color: #fff;
grid-column: 2;
grid-row: 1;
padding-inline-end: 8px;
padding-inline-start: 4px;
padding-top: 5px;
}
.tox .tox-promotion-link {
align-items: unsafe center;
background-color: #E8F1F8;
border-radius: 5px;
color: #086BE6;
cursor: pointer;
display: flex;
font-size: 14px;
height: 26.6px;
padding: 4px 8px;
white-space: nowrap;
}
.tox .tox-promotion-link:hover {
background-color: #B4D7FF;
}
.tox .tox-promotion-link:focus {
background-color: #D9EDF7;
}
/* Deprecated. Remove in next major release */
.tox .tox-mbtn {
@@ -2874,10 +2941,14 @@ body.tox-dialog__disable-scroll {
.tox .tox-toolbar__overflow--shrinking {
transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;
}
.tox .tox-toolbar-overlord,
.tox .tox-anchorbar {
grid-column: 1 / -1;
}
.tox .tox-menubar + .tox-toolbar,
.tox .tox-menubar + .tox-toolbar-overlord {
border-top: 1px solid transparent;
margin-top: 0px;
margin-top: -1px;
padding-bottom: 1px;
padding-top: 1px;
}
File diff suppressed because one or more lines are too long
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -186,6 +203,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -215,6 +253,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.3);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.3);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -506,10 +551,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -205,6 +222,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -234,6 +272,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.1);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.1);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -525,10 +570,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+98 -18
View File
@@ -252,11 +252,24 @@ button::-moz-focus-inner {
margin: 0;
outline: none;
padding: 4px 16px;
position: relative;
text-align: center;
text-decoration: none;
text-transform: none;
white-space: nowrap;
}
.tox .tox-button::before {
border-radius: 3px;
bottom: -1px;
box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #207ab7, 0 0 0 3px rgba(32, 122, 183, 0.25);
content: '';
left: -1px;
opacity: 0;
pointer-events: none;
position: absolute;
right: -1px;
top: -1px;
}
.tox .tox-button[disabled] {
background-color: #207ab7;
background-image: none;
@@ -272,6 +285,9 @@ button::-moz-focus-inner {
box-shadow: none;
color: #fff;
}
.tox .tox-button:focus-visible:not(:disabled)::before {
opacity: 1;
}
.tox .tox-button:hover:not(:disabled) {
background-color: #1c6ca1;
background-image: none;
@@ -933,8 +949,9 @@ button::-moz-focus-inner {
justify-content: space-between;
}
.tox .tox-comment__date {
color: rgba(255, 255, 255, 0.5);
color: #fff;
font-size: 12px;
line-height: 18px;
}
.tox .tox-comment__body {
color: #fff;
@@ -1093,20 +1110,30 @@ button::-moz-focus-inner {
.tox .tox-user__avatar svg {
fill: rgba(255, 255, 255, 0.5);
}
.tox .tox-user__avatar img {
border-radius: 50%;
height: 36px;
object-fit: cover;
vertical-align: middle;
width: 36px;
}
.tox .tox-user__name {
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
color: #fff;
font-size: 14px;
font-style: normal;
font-weight: bold;
text-transform: uppercase;
line-height: 18px;
text-transform: none;
}
.tox:not([dir=rtl]) .tox-user__avatar svg {
.tox:not([dir=rtl]) .tox-user__avatar svg,
.tox:not([dir=rtl]) .tox-user__avatar img {
margin-right: 8px;
}
.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar svg {
.tox[dir=rtl] .tox-user__avatar svg,
.tox[dir=rtl] .tox-user__avatar img {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar + .tox-user__name {
@@ -1151,10 +1178,11 @@ button::-moz-focus-inner {
width: 95vw;
z-index: 2;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog {
align-self: flex-start;
margin: 8px auto;
max-height: calc(100vh - 8px * 2);
width: calc(100vw - 16px);
}
}
@@ -1210,7 +1238,7 @@ button::-moz-focus-inner {
text-align: left;
text-transform: none;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body {
flex-direction: column;
}
@@ -1221,7 +1249,7 @@ button::-moz-focus-inner {
flex-direction: column;
padding: 16px 16px;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {
flex-direction: row;
-webkit-overflow-scrolling: touch;
@@ -1389,6 +1417,9 @@ button::-moz-focus-inner {
padding-bottom: 8px;
padding-top: 8px;
}
.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {
background: #fff;
}
.tox .tox-dialog__popups {
position: absolute;
width: 100%;
@@ -1421,12 +1452,12 @@ button::-moz-focus-inner {
.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {
transition-delay: 0s;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {
margin-right: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {
margin-left: 8px;
}
@@ -1438,12 +1469,12 @@ button::-moz-focus-inner {
.tox[dir=rtl] .tox-dialog__body {
text-align: right;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {
margin-left: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {
margin-right: 8px;
}
@@ -1500,6 +1531,8 @@ body.tox-dialog__disable-scroll {
overflow: hidden;
}
.tox .tox-editor-header {
display: grid;
grid-template-columns: 1fr min-content;
z-index: 1;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header {
@@ -1581,8 +1614,7 @@ body.tox-dialog__disable-scroll {
max-width: 25em;
}
.tox .tox-autocompleter .tox-menu {
border-color: #000000;
box-shadow: none;
box-sizing: border-box;
max-width: 25em;
}
.tox .tox-autocompleter .tox-autocompleter-highlight {
@@ -2073,6 +2105,12 @@ body.tox-dialog__disable-scroll {
.tox .tox-menu.tox-collection.tox-collection--grid {
padding: 4px;
}
@media only screen and (min-width: 768px ) {
.tox .tox-menu .tox-collection__item-label {
overflow-wrap: break-word;
word-break: normal;
}
}
.tox .tox-menu__label h1,
.tox .tox-menu__label h2,
.tox .tox-menu__label h3,
@@ -2091,10 +2129,39 @@ body.tox-dialog__disable-scroll {
flex: 0 0 auto;
flex-shrink: 0;
flex-wrap: wrap;
grid-column: 1 / -1;
grid-row: 1;
padding: 0 4px 0 4px;
}
.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {
border-top: 1px solid #000000;
.tox .tox-promotion + .tox-menubar {
grid-column: 1;
}
.tox .tox-promotion {
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E") left 0 top 0 #222f3e;
background-color: #222f3e;
grid-column: 2;
grid-row: 1;
padding-inline-end: 8px;
padding-inline-start: 4px;
padding-top: 5px;
}
.tox .tox-promotion-link {
align-items: unsafe center;
background-color: #E8F1F8;
border-radius: 5px;
color: #086BE6;
cursor: pointer;
display: flex;
font-size: 14px;
height: 26.6px;
padding: 4px 8px;
white-space: nowrap;
}
.tox .tox-promotion-link:hover {
background-color: #B4D7FF;
}
.tox .tox-promotion-link:focus {
background-color: #D9EDF7;
}
/* Deprecated. Remove in next major release */
.tox .tox-mbtn {
@@ -2874,10 +2941,14 @@ body.tox-dialog__disable-scroll {
.tox .tox-toolbar__overflow--shrinking {
transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;
}
.tox .tox-toolbar-overlord,
.tox .tox-anchorbar {
grid-column: 1 / -1;
}
.tox .tox-menubar + .tox-toolbar,
.tox .tox-menubar + .tox-toolbar-overlord {
border-top: 1px solid #000000;
margin-top: 0;
margin-top: -1px;
padding-bottom: 0px;
padding-top: 0px;
}
@@ -3087,6 +3158,12 @@ body.tox-dialog__disable-scroll {
border: 1px solid #000000;
padding: 0;
}
.tox .tox-pop .tox-pop__dialog .tox-toolbar {
padding: 0;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {
border-top: 1px solid #000000;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,
.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child {
border-top: 1px solid #000000;
@@ -3117,3 +3194,6 @@ body.tox-dialog__disable-scroll {
padding-bottom: 0;
padding-right: 0;
}
.tox .tox-button::before {
display: none;
}
File diff suppressed because one or more lines are too long
+51 -6
View File
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -205,6 +222,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -234,6 +272,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.1);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.1);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -525,10 +570,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
@@ -21,11 +21,28 @@
.mce-content-body .mce-item-anchor[data-mce-selected] {
outline-offset: 1px;
}
.tox-comments-visible .tox-comment {
background-color: #fff0b7;
.tox-comments-visible .tox-comment[contenteditable="false"]:not([data-mce-selected]),
.tox-comments-visible span.tox-comment img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #ffe89d;
}
.tox-comments-visible .tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #ffe168;
.tox-comments-visible .tox-comment[contenteditable="false"][data-mce-annotation-active="true"]:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] img:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > audio:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] > video:not([data-mce-selected]),
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"] span.mce-preview-object:not([data-mce-selected]) {
outline: 3px solid #fed635;
}
.tox-comments-visible span.tox-comment:not([data-mce-selected]) {
background-color: #ffe89d;
outline: none;
}
.tox-comments-visible span.tox-comment[data-mce-annotation-active="true"]:not([data-mce-selected="inline-boundary"]) {
background-color: #fed635;
}
.tox-checklist > li:not(.tox-checklist--hidden) {
list-style: none;
@@ -205,6 +222,27 @@ pre[class*="language-"] {
.tox-cursor-format-painter {
cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default;
}
div.mce-footnotes hr {
margin-inline-end: auto;
margin-inline-start: 0;
width: 25%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
text-decoration: none;
}
@media print {
sup.mce-footnote a {
color: black;
text-decoration: none;
}
div.mce-footnotes {
break-inside: avoid;
width: 100%;
}
div.mce-footnotes li > a.mce-footnotes-backlink {
display: none;
}
}
.mce-content-body figure.align-left {
float: left;
}
@@ -234,6 +272,13 @@ pre[class*="language-"] {
.mce-preview-object[data-mce-selected="2"] .mce-shim {
display: none;
}
.mce-content-body .mce-mergetag:hover {
background-color: rgba(0, 108, 231, 0.1);
}
.mce-content-body .mce-mergetag-affix {
background-color: rgba(0, 108, 231, 0.1);
color: #006ce7;
}
.mce-object {
background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;
border: 1px dashed #aaa;
@@ -525,10 +570,10 @@ pre[class*="language-"] {
border-color: rgba(0, 84, 180, 0.7);
}
}
.mce-content-body img::-moz-selection {
.mce-content-body img[data-mce-selected]::-moz-selection {
background: none;
}
.mce-content-body img::selection {
.mce-content-body img[data-mce-selected]::selection {
background: none;
}
.ephox-snooker-resizer-bar {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+99 -19
View File
@@ -252,11 +252,24 @@ button::-moz-focus-inner {
margin: 0;
outline: none;
padding: 4px 16px;
position: relative;
text-align: center;
text-decoration: none;
text-transform: none;
white-space: nowrap;
}
.tox .tox-button::before {
border-radius: 3px;
bottom: -1px;
box-shadow: inset 0 0 0 2px #fff, 0 0 0 1px #207ab7, 0 0 0 3px rgba(32, 122, 183, 0.25);
content: '';
left: -1px;
opacity: 0;
pointer-events: none;
position: absolute;
right: -1px;
top: -1px;
}
.tox .tox-button[disabled] {
background-color: #207ab7;
background-image: none;
@@ -272,6 +285,9 @@ button::-moz-focus-inner {
box-shadow: none;
color: #fff;
}
.tox .tox-button:focus-visible:not(:disabled)::before {
opacity: 1;
}
.tox .tox-button:hover:not(:disabled) {
background-color: #1c6ca1;
background-image: none;
@@ -933,8 +949,9 @@ button::-moz-focus-inner {
justify-content: space-between;
}
.tox .tox-comment__date {
color: rgba(34, 47, 62, 0.7);
color: #222f3e;
font-size: 12px;
line-height: 18px;
}
.tox .tox-comment__body {
color: #222f3e;
@@ -1093,20 +1110,30 @@ button::-moz-focus-inner {
.tox .tox-user__avatar svg {
fill: rgba(34, 47, 62, 0.7);
}
.tox .tox-user__avatar img {
border-radius: 50%;
height: 36px;
object-fit: cover;
vertical-align: middle;
width: 36px;
}
.tox .tox-user__name {
color: rgba(34, 47, 62, 0.7);
font-size: 12px;
color: #222f3e;
font-size: 14px;
font-style: normal;
font-weight: bold;
text-transform: uppercase;
line-height: 18px;
text-transform: none;
}
.tox:not([dir=rtl]) .tox-user__avatar svg {
.tox:not([dir=rtl]) .tox-user__avatar svg,
.tox:not([dir=rtl]) .tox-user__avatar img {
margin-right: 8px;
}
.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar svg {
.tox[dir=rtl] .tox-user__avatar svg,
.tox[dir=rtl] .tox-user__avatar img {
margin-left: 8px;
}
.tox[dir=rtl] .tox-user__avatar + .tox-user__name {
@@ -1151,10 +1178,11 @@ button::-moz-focus-inner {
width: 95vw;
z-index: 2;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog {
align-self: flex-start;
margin: 8px auto;
max-height: calc(100vh - 8px * 2);
width: calc(100vw - 16px);
}
}
@@ -1210,7 +1238,7 @@ button::-moz-focus-inner {
text-align: left;
text-transform: none;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body {
flex-direction: column;
}
@@ -1221,7 +1249,7 @@ button::-moz-focus-inner {
flex-direction: column;
padding: 16px 16px;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox .tox-dialog__body-nav {
flex-direction: row;
-webkit-overflow-scrolling: touch;
@@ -1389,6 +1417,9 @@ button::-moz-focus-inner {
padding-bottom: 8px;
padding-top: 8px;
}
.tox .tox-dialog__iframe.tox-dialog__iframe--opaque {
background: #fff;
}
.tox .tox-dialog__popups {
position: absolute;
width: 100%;
@@ -1421,12 +1452,12 @@ button::-moz-focus-inner {
.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein {
transition-delay: 0s;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav {
margin-right: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) {
margin-left: 8px;
}
@@ -1438,12 +1469,12 @@ button::-moz-focus-inner {
.tox[dir=rtl] .tox-dialog__body {
text-align: right;
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav {
margin-left: 0;
}
}
@media only screen and (max-width:767px) {
@media only screen and (max-width: 767px ) {
body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) {
margin-right: 8px;
}
@@ -1500,6 +1531,8 @@ body.tox-dialog__disable-scroll {
overflow: hidden;
}
.tox .tox-editor-header {
display: grid;
grid-template-columns: 1fr min-content;
z-index: 1;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header {
@@ -1510,7 +1543,7 @@ body.tox-dialog__disable-scroll {
transition: box-shadow 0.5s;
}
.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header {
border-top: 1px solid #c1c1c1;
border-top: 1px solid #cccccc;
box-shadow: none;
}
.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header {
@@ -1581,8 +1614,7 @@ body.tox-dialog__disable-scroll {
max-width: 25em;
}
.tox .tox-autocompleter .tox-menu {
border-color: #cccccc;
box-shadow: none;
box-sizing: border-box;
max-width: 25em;
}
.tox .tox-autocompleter .tox-autocompleter-highlight {
@@ -2073,6 +2105,12 @@ body.tox-dialog__disable-scroll {
.tox .tox-menu.tox-collection.tox-collection--grid {
padding: 4px;
}
@media only screen and (min-width: 768px ) {
.tox .tox-menu .tox-collection__item-label {
overflow-wrap: break-word;
word-break: normal;
}
}
.tox .tox-menu__label h1,
.tox .tox-menu__label h2,
.tox .tox-menu__label h3,
@@ -2091,10 +2129,39 @@ body.tox-dialog__disable-scroll {
flex: 0 0 auto;
flex-shrink: 0;
flex-wrap: wrap;
grid-column: 1 / -1;
grid-row: 1;
padding: 0 4px 0 4px;
}
.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {
border-top: 1px solid #cccccc;
.tox .tox-promotion + .tox-menubar {
grid-column: 1;
}
.tox .tox-promotion {
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E") left 0 top 0 #fff;
background-color: #fff;
grid-column: 2;
grid-row: 1;
padding-inline-end: 8px;
padding-inline-start: 4px;
padding-top: 5px;
}
.tox .tox-promotion-link {
align-items: unsafe center;
background-color: #E8F1F8;
border-radius: 5px;
color: #086BE6;
cursor: pointer;
display: flex;
font-size: 14px;
height: 26.6px;
padding: 4px 8px;
white-space: nowrap;
}
.tox .tox-promotion-link:hover {
background-color: #B4D7FF;
}
.tox .tox-promotion-link:focus {
background-color: #D9EDF7;
}
/* Deprecated. Remove in next major release */
.tox .tox-mbtn {
@@ -2874,10 +2941,14 @@ body.tox-dialog__disable-scroll {
.tox .tox-toolbar__overflow--shrinking {
transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s;
}
.tox .tox-toolbar-overlord,
.tox .tox-anchorbar {
grid-column: 1 / -1;
}
.tox .tox-menubar + .tox-toolbar,
.tox .tox-menubar + .tox-toolbar-overlord {
border-top: 1px solid #cccccc;
margin-top: 0;
margin-top: -1px;
padding-bottom: 0px;
padding-top: 0px;
}
@@ -3087,6 +3158,12 @@ body.tox-dialog__disable-scroll {
border: 1px solid #cccccc;
padding: 0;
}
.tox .tox-pop .tox-pop__dialog .tox-toolbar {
padding: 0;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar {
border-top: 1px solid #cccccc;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,
.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child {
border-top: 1px solid #cccccc;
@@ -3117,3 +3194,6 @@ body.tox-dialog__disable-scroll {
padding-bottom: 0;
padding-right: 0;
}
.tox .tox-button::before {
display: none;
}
File diff suppressed because one or more lines are too long
+8 -8
View File
@@ -1,15 +1,15 @@
import path from 'path'
import path from 'node:path'
import fs from 'fs-extra'
import { icons } from '@iconify-json/ep'
async function prepare() {
const outputDir = path.resolve(process.cwd(), 'public/icons')
await fs.ensureDir(outputDir)
await fs.emptyDir(outputDir)
await fs.writeJSON(
path.resolve(outputDir, 'icons.json'),
icons
)
const outputDir = path.resolve(process.cwd(), 'public/icons')
await fs.ensureDir(outputDir)
await fs.emptyDir(outputDir)
await fs.writeJSON(
path.resolve(outputDir, 'icons.json'),
icons,
)
}
prepare()
+22 -22
View File
@@ -1,7 +1,7 @@
{
// Default options
'functions': true,
'variableNameTransforms': ['dasherize']
// Default options
'functions': true,
'variableNameTransforms': ['dasherize']
}
{{#block "sprites"}}
@@ -19,35 +19,35 @@ ${{spritesheet_info.strings.name}}-sprites: (
{{#block "sprite-functions"}}
{{#if options.functions}}
@mixin {{spritesheet_info.strings.name}}-sprite-width($sprite) {
width: nth($sprite, 5);
width: nth($sprite, 5);
}
@mixin {{spritesheet_info.strings.name}}-sprite-height($sprite) {
height: nth($sprite, 6);
height: nth($sprite, 6);
}
@mixin {{spritesheet_info.strings.name}}-sprite-position($sprite) {
$sprite-offset-x: nth($sprite, 3);
$sprite-offset-y: nth($sprite, 4);
background-position: $sprite-offset-x $sprite-offset-y;
$sprite-offset-x: nth($sprite, 3);
$sprite-offset-y: nth($sprite, 4);
background-position: $sprite-offset-x $sprite-offset-y;
}
@mixin {{spritesheet_info.strings.name}}-sprite-size($sprite) {
background-size: nth($sprite, 7) nth($sprite, 8);
background-size: nth($sprite, 7) nth($sprite, 8);
}
@mixin {{spritesheet_info.strings.name}}-sprite-image($sprite) {
$sprite-image: nth($sprite, 9);
background-image: url(#{$sprite-image});
$sprite-image: nth($sprite, 9);
background-image: url(#{$sprite-image});
}
@mixin {{spritesheet_info.strings.name}}-sprite($name) {
@include {{spritesheet_info.strings.name}}-sprite-image(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-position(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-size(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-width(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-height(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
background-repeat: no-repeat;
@include {{spritesheet_info.strings.name}}-sprite-image(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-position(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-size(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-width(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
@include {{spritesheet_info.strings.name}}-sprite-height(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name}));
background-repeat: no-repeat;
}
{{/if}}
{{/block}}
@@ -55,12 +55,12 @@ ${{spritesheet_info.strings.name}}-sprites: (
{{#block "spritesheet-functions"}}
{{#if options.functions}}
@mixin all-{{spritesheet_info.strings.name}}-sprites() {
@each $key, $val in ${{spritesheet_info.strings.name}}-sprites {
$sprite-name: nth($val, 10);
.{{spritesheet_info.strings.name}}-#{$sprite-name}-sprites {
@include {{spritesheet_info.strings.name}}-sprite($key);
}
@each $key, $val in ${{spritesheet_info.strings.name}}-sprites {
$sprite-name: nth($val, 10);
.{{spritesheet_info.strings.name}}-#{$sprite-name}-sprites {
@include {{spritesheet_info.strings.name}}-sprite($key);
}
}
}
{{/if}}
{{/block}}
+43 -66
View File
@@ -1,89 +1,66 @@
<script setup>
<script lang="ts" setup>
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import hotkeys from 'hotkeys-js'
import eventBus from './utils/eventBus'
import useSettingsStore from '@/store/modules/settings'
import useMenuStore from '@/store/modules/menu'
const settingsStore = useSettingsStore()
const buttonConfig = ref({
autoInsertSpace: true,
})
// 侧边栏主导航当前实际宽度
const mainSidebarActualWidth = computed(() => {
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width')
actualWidth = parseInt(actualWidth)
if (['head', 'single'].includes(settingsStore.menu.menuMode)) {
actualWidth = 0
}
return `${actualWidth}px`
let actualWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width'))
if (['head', 'single'].includes(settingsStore.settings.menu.menuMode)) {
actualWidth = 0
}
return `${actualWidth}px`
})
// 侧边栏次导航当前实际宽度
const subSidebarActualWidth = computed(() => {
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width')
actualWidth = parseInt(actualWidth)
if (settingsStore.menu.subMenuCollapse) {
actualWidth = 64
}
return `${actualWidth}px`
})
watch(() => settingsStore.app.colorScheme, () => {
if (settingsStore.app.colorScheme === 'dark') {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}, {
immediate: true
})
watch(() => settingsStore.mode, () => {
if (settingsStore.mode === 'pc') {
settingsStore.$patch(state => {
state.menu.subMenuCollapse = settingsStore.subMenuCollapseLastStatus
})
} else if (settingsStore.mode === 'mobile') {
settingsStore.$patch(state => {
state.menu.subMenuCollapse = true
})
}
document.body.setAttribute('data-mode', settingsStore.mode)
}, {
immediate: true
})
watch(() => settingsStore.menu.menuMode, () => {
document.body.setAttribute('data-menu-mode', settingsStore.menu.menuMode)
}, {
immediate: true
let actualWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width'))
if (settingsStore.settings.menu.subMenuCollapse) {
actualWidth = 64
}
return `${actualWidth}px`
})
watch([
() => settingsStore.app.enableDynamicTitle,
() => settingsStore.title
() => settingsStore.settings.app.enableDynamicTitle,
() => settingsStore.title,
], () => {
if (settingsStore.app.enableDynamicTitle && settingsStore.title) {
let title = settingsStore.title
document.title = `${title} - ${import.meta.env.VITE_APP_TITLE}`
} else {
document.title = import.meta.env.VITE_APP_TITLE
}
}, {
immediate: true
if (settingsStore.settings.app.enableDynamicTitle && settingsStore.title) {
const title = typeof settingsStore.title === 'function' ? settingsStore.title() : settingsStore.title
document.title = `${title} - ${import.meta.env.VITE_APP_TITLE}`
}
else {
document.title = import.meta.env.VITE_APP_TITLE
}
})
onMounted(() => {
window.onresize = () => {
settingsStore.setMode(document.documentElement.clientWidth)
}
window.onresize()
settingsStore.setMode(document.documentElement.clientWidth)
window.onresize = () => {
settingsStore.setMode(document.documentElement.clientWidth)
}
hotkeys('alt+i', () => {
eventBus.emit('global-system-info-toggle')
})
})
</script>
<template>
<el-config-provider :locale="zhCn" :size="settingsStore.app.elementSize" :button="{autoInsertSpace: true}">
<RouterView
:style="{
'--g-main-sidebar-actual-width': mainSidebarActualWidth,
'--g-sub-sidebar-actual-width': subSidebarActualWidth
}"
/>
</el-config-provider>
<el-config-provider :locale="zhCn" :size="settingsStore.settings.app.elementSize" :button="buttonConfig">
<RouterView
:style="{
'--g-main-sidebar-actual-width': mainSidebarActualWidth,
'--g-sub-sidebar-actual-width': subSidebarActualWidth,
}"
/>
<system-info />
</el-config-provider>
</template>
-82
View File
@@ -1,82 +0,0 @@
import axios from 'axios'
// import qs from 'qs'
import router from '@/router/index'
import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
const toLogin = () => {
useUserStore().logout().then(() => {
router.push({
name: 'login',
query: {
redirect: router.currentRoute.value.path !== '/login' ? router.currentRoute.value.fullPath : undefined
}
})
})
}
const api = axios.create({
baseURL: import.meta.env.DEV && import.meta.env.VITE_OPEN_PROXY === 'true' ? '/proxy/' : import.meta.env.VITE_APP_API_BASEURL,
timeout: 10000,
responseType: 'json'
})
api.interceptors.request.use(
request => {
const userStore = useUserStore()
/**
* 全局拦截请求发送前提交的参数
* 以下代码为示例,在请求头里带上 token 信息
*/
if (userStore.isLogin) {
request.headers['Token'] = userStore.token
}
// 是否将 POST 请求参数进行字符串化处理
if (request.method === 'post') {
// request.data = qs.stringify(request.data, {
// arrayFormat: 'brackets'
// })
}
return request
}
)
api.interceptors.response.use(
response => {
/**
* 全局拦截请求发送后返回的数据,如果数据有报错则在这做全局的错误提示
* 假设返回数据格式为:{ status: 1, error: '', data: '' }
* 规则是当 status 为 1 时表示请求成功,为 0 时表示接口需要登录或者登录状态失效,需要重新登录
* 请求出错时 error 会返回错误信息
*/
if (response.data.status === 1) {
if (response.data.error === '') {
// 请求成功并且没有报错
return Promise.resolve(response.data)
} else {
// 这里做错误提示
// ElMessage.error(options)
return Promise.reject(response.data)
}
} else {
toLogin()
}
},
error => {
let message = error.message
if (message == 'Network Error') {
message = '后端网络故障'
} else if (message.includes('timeout')) {
message = '接口请求超时'
} else if (message.includes('Request failed with status code')) {
message = '接口' + message.substr(message.length - 3) + '异常'
}
ElMessage({
message,
type: 'error'
})
return Promise.reject(error)
}
)
export default api
+83
View File
@@ -0,0 +1,83 @@
import axios from 'axios'
// import qs from 'qs'
import { ElMessage } from 'element-plus'
import router from '@/router/index'
import useUserStore from '@/store/modules/user'
const toLogin = () => {
useUserStore().logout().then(() => {
router.push({
path: '/login',
query: {
redirect: router.currentRoute.value.path !== '/login' ? router.currentRoute.value.fullPath : undefined,
},
})
})
}
const api = axios.create({
baseURL: import.meta.env.DEV && import.meta.env.VITE_OPEN_PROXY === 'true' ? '/proxy/' : import.meta.env.VITE_APP_API_BASEURL,
timeout: 1000 * 60,
responseType: 'json',
})
api.interceptors.request.use(
(request) => {
const userStore = useUserStore()
/**
* 全局拦截请求发送前提交的参数
* 以下代码为示例,在请求头里带上 token 信息
*/
if (userStore.isLogin && request.headers) {
request.headers.Token = userStore.token
}
// 是否将 POST 请求参数进行字符串化处理
if (request.method === 'post') {
// request.data = qs.stringify(request.data, {
// arrayFormat: 'brackets',
// })
}
return request
},
)
api.interceptors.response.use(
(response) => {
/**
* 全局拦截请求发送后返回的数据,如果数据有报错则在这做全局的错误提示
* 假设返回数据格式为:{ status: 1, error: '', data: '' }
* 规则是当 status 为 1 时表示请求成功,为 0 时表示接口需要登录或者登录状态失效,需要重新登录
* 请求出错时 error 会返回错误信息
*/
if (response.data.status === 1) {
if (response.data.error !== '') {
// 这里做错误提示,如果使用了 element plus 则可以使用 Message 进行提示
// ElMessage.error(options)
return Promise.reject(response.data)
}
}
else {
toLogin()
}
return Promise.resolve(response.data)
},
(error) => {
let message = error.message
if (message === 'Network Error') {
message = '后端网络故障'
}
else if (message.includes('timeout')) {
message = '接口请求超时'
}
else if (message.includes('Request failed with status code')) {
message = `接口${message.substr(message.length - 3)}异常`
}
ElMessage({
message,
type: 'error',
})
return Promise.reject(error)
},
)
export default api
-1
View File
@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1623578868505" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5778" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M972.4928 217.4976a73.4208 73.4208 0 0 0-58.5728-64.0512 1415.7312 1415.7312 0 0 1-189.184-42.9568A706.1504 706.1504 0 0 1 555.52 14.4384a77.6704 77.6704 0 0 0-90.112 0 445.8496 445.8496 0 0 1-170.496 96.0512 639.0272 639.0272 0 0 1-183.8592 44.8512A67.8912 67.8912 0 0 0 53.1968 219.392S51.2 379.2384 51.2 520.448C51.2 776.4992 358.1952 1024 511.8464 1024c153.6 0 413.5936-174.592 455.0144-499.712 6.5536-101.888 7.68-204.0832 3.328-306.176H972.8l-0.3072-0.6144z m-167.168 197.9392l-320.512 297.216a43.6736 43.6736 0 0 1-53.248 5.12l-8.5504-7.0144L246.272 529.408a42.752 42.752 0 0 1 2.304-61.184 43.776 43.776 0 0 1 62.1568 2.56l145.7664 150.2208 288.8704-269.6704a44.4416 44.4416 0 0 1 62.5152 2.56c16.5376 17.7664 15.36 45.1072-2.6112 61.44z" p-id="5779"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-1
View File
@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

-1
View File
@@ -1 +0,0 @@
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>

Before

Width:  |  Height:  |  Size: 944 B

-1
View File
@@ -1 +0,0 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>

Before

Width:  |  Height:  |  Size: 623 B

-1
View File
@@ -1 +0,0 @@
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>

Before

Width:  |  Height:  |  Size: 440 B

+5 -5
View File
@@ -1,9 +1,9 @@
// 暗黑模式
@forward "element-plus/theme-chalk/src/dark/var.scss" with (
$bg-color: (
"page": #0a0a0a,
"": #141414,
"overlay": #1d1e1f,
)
$bg-color: (
"page": #0a0a0a,
"": #141414,
"overlay": #1d1e1f,
)
);
@use "element-plus/theme-chalk/src/dark/css-vars.scss" as *;
+4 -4
View File
@@ -1,9 +1,9 @@
// @forward "element-plus/theme-chalk/src/common/var.scss" with (
// $colors: (
// "primary": (
// "base": green
// )
// $colors: (
// "primary": (
// "base": green
// )
// )
// );
@use "element-plus/theme-chalk/src/index.scss" as *;
@use "./dark.scss";
+55 -42
View File
@@ -3,73 +3,86 @@
// 全局变量
:root {
// 头部宽度(默认自适应宽度,可固定宽度,固定宽度后为居中显示)
--g-header-width: 100%;
// 头部高度
--g-header-height: 70px;
// 侧边栏宽度
--g-main-sidebar-width: 70px;
--g-sub-sidebar-width: 220px;
// 侧边栏Logo高度
--g-sidebar-logo-height: 50px;
// 顶栏高度
--g-topbar-height: 50px;
// 头部宽度(默认自适应宽度,可固定宽度,固定宽度后为居中显示)
--g-header-width: 100%;
// 头部高度
--g-header-height: 70px;
// 侧边栏宽度
--g-main-sidebar-width: 70px;
--g-sub-sidebar-width: 220px;
// 侧边栏Logo高度
--g-sidebar-logo-height: 50px;
// 顶栏高度
--g-topbar-height: 50px;
}
// 全局样式
::-webkit-scrollbar {
width: 12px;
height: 12px;
width: 12px;
height: 12px;
}
::-webkit-scrollbar-thumb {
background-color: rgb(0 0 0 / 40%);
background-clip: padding-box;
border: 3px solid transparent;
border-radius: 6px;
background-color: rgb(0 0 0 / 40%);
background-clip: padding-box;
border: 3px solid transparent;
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgb(0 0 0 / 50%);
background-color: rgb(0 0 0 / 50%);
}
::-webkit-scrollbar-track {
background-color: transparent;
background-color: transparent;
}
html,
body {
height: 100%;
height: 100%;
}
body {
margin: 0;
box-sizing: border-box;
font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif;
-webkit-tap-highlight-color: transparent;
&.hidden {
overflow: hidden;
}
margin: 0;
box-sizing: border-box;
font-family: Lato, "PingFang SC", "Microsoft YaHei", sans-serif;
-webkit-tap-highlight-color: transparent;
&.hidden {
overflow: hidden;
}
}
* {
box-sizing: inherit;
box-sizing: inherit;
}
// 右侧内容区针对fixed元素,有横向铺满的需求,可在fixed元素上设置 [data-fixed-calc-width]
[data-fixed-calc-width] {
position: fixed;
left: 50%;
right: 0;
width: calc(100% - var(--g-main-sidebar-actual-width) - var(--g-sub-sidebar-actual-width));
transform: translateX(-50%) translateX(calc(var(--g-main-sidebar-actual-width) / 2)) translateX(calc(var(--g-sub-sidebar-actual-width) / 2));
position: fixed;
left: 50%;
right: 0;
width: calc(100% - var(--g-main-sidebar-actual-width) - var(--g-sub-sidebar-actual-width));
transform: translateX(-50%) translateX(calc(var(--g-main-sidebar-actual-width) / 2)) translateX(calc(var(--g-sub-sidebar-actual-width) / 2));
}
[data-mode="mobile"] {
[data-fixed-calc-width] {
width: 100% !important;
transform: translateX(-50%) !important;
}
[data-fixed-calc-width] {
width: 100% !important;
transform: translateX(-50%) !important;
}
}
// textarea 字体跟随系统
textarea {
font-family: inherit;
font-family: inherit;
}
// 列表页全局样式
.el-table.list-table {
margin: 20px 0;
.el-button + .el-dropdown {
margin-left: 10px;
}
margin: 20px 0;
.el-button + .el-dropdown {
margin-left: 10px;
}
}
.el-loading-mask {
z-index: 900 !important;
}
+54 -46
View File
@@ -1,55 +1,63 @@
#nprogress {
pointer-events: none;
.bar {
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: var(--g-nprogress-color);
}
.peg {
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
opacity: 1;
transform: rotate(3deg) translate(0, -4px);
box-shadow: 0 0 10px var(--g-nprogress-color), 0 0 5px var(--g-nprogress-color);
}
.spinner {
display: block;
position: fixed;
z-index: 1031;
top: 11px;
right: 14px;
.spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-radius: 50%;
animation: nprogress-spinner 400ms linear infinite;
border-top-color: var(--g-nprogress-color);
border-left-color: var(--g-nprogress-color);
}
pointer-events: none;
.bar {
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: var(--g-nprogress-color);
}
.peg {
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
opacity: 1;
transform: rotate(3deg) translate(0, -4px);
box-shadow: 0 0 10px var(--g-nprogress-color), 0 0 5px var(--g-nprogress-color);
}
.spinner {
display: block;
position: fixed;
z-index: 1031;
top: 11px;
right: 14px;
.spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-radius: 50%;
animation: nprogress-spinner 400ms linear infinite;
border-top-color: var(--g-nprogress-color);
border-left-color: var(--g-nprogress-color);
}
}
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
#nprogress .spinner,
#nprogress .bar {
position: absolute;
}
overflow: hidden;
position: relative;
#nprogress .spinner,
#nprogress .bar {
position: absolute;
}
}
@keyframes nprogress-spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes nprogress-spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
+38 -35
View File
@@ -1,50 +1,53 @@
// 文字超出隐藏,默认为单行超出隐藏,可设置多行
@mixin text-overflow($line: 1, $fixed-width: true) {
@if $line == 1 and $fixed-width == true {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@else {
/* stylelint-disable-next-line value-no-vendor-prefix */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $line;
overflow: hidden;
}
@if $line == 1 and $fixed-width == true {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
/* stylelint-disable-next-line value-no-vendor-prefix */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $line;
overflow: hidden;
}
}
// 定位居中,默认水平居中,可选择垂直居中,或者水平垂直都居中
@mixin position-center($type: x) {
position: absolute;
@if $type == x {
left: 50%;
transform: translateX(-50%);
}
@if $type == y {
top: 50%;
transform: translateY(-50%);
}
@if $type == xy {
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
}
position: absolute;
@if $type == x {
left: 50%;
transform: translateX(-50%);
}
@if $type == y {
top: 50%;
transform: translateY(-50%);
}
@if $type == xy {
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
}
}
// 文字两端对齐
%justify-align {
text-align: justify;
text-align-last: justify;
text-align: justify;
text-align-last: justify;
}
// 清除浮动
%clearfix {
zoom: 1;
&::before,
&::after {
content: "";
display: block;
clear: both;
}
zoom: 1;
&::before,
&::after {
content: "";
display: block;
clear: both;
}
}
+78 -78
View File
@@ -1,5 +1,67 @@
:root {
--g-box-shadow-color: rgb(0 0 0 / 12%);
--g-box-shadow-color: rgb(0 0 0 / 12%);
/* ----- 应用 ----- */
// 应用背景色
--g-app-bg: var(--el-bg-color);
// 主区域背景色
--g-main-bg: var(--el-bg-color-page);
/* ----- 顶部 ----- */
// 背景色
--g-header-bg: #222b45;
// 文字颜色
--g-header-color: #fff;
// 导航文字颜色
--g-header-menu-color: #fff;
// 导航鼠标悬浮文字颜色
--g-header-menu-hover-color: #fff;
// 导航鼠标悬浮背景色
--g-header-menu-hover-bg: #334067;
// 导航选中文字颜色
--g-header-menu-active-color: #fff;
// 导航选中背景色
--g-header-menu-active-bg: #334067;
/* ----- 主导航 ----- */
// 背景色
--g-main-sidebar-bg: #222b45;
// 菜单文字颜色
--g-main-sidebar-menu-color: #eee;
// 菜单鼠标悬浮文字颜色
--g-main-sidebar-menu-hover-color: #fff;
// 菜单鼠标悬浮背景色
--g-main-sidebar-menu-hover-bg: #334067;
// 菜单选中文字颜色
--g-main-sidebar-menu-active-color: #fff;
// 菜单选中背景色
--g-main-sidebar-menu-active-bg: #334067;
/* ----- 次导航 ----- */
// 背景色
--g-sub-sidebar-bg: #fff;
// 菜单背景色
--g-sub-sidebar-menu-bg: #e8e8e8;
// 菜单文字颜色
--g-sub-sidebar-menu-color: var(--el-text-color-primary);
// 菜单鼠标悬浮文字颜色
--g-sub-sidebar-menu-hover-color: var(--el-text-color-primary);
// 菜单鼠标悬浮背景色
--g-sub-sidebar-menu-hover-bg: #e1e1e1;
// 菜单选中文字颜色
--g-sub-sidebar-menu-active-color: #fff;
// 菜单选中背景色
--g-sub-sidebar-menu-active-bg: var(--el-color-primary);
/* ----- 顶栏 ----- */
--g-toolbar-bg: var(--el-bg-color);
/* ----- 进度条 ----- */
--g-nprogress-color: var(--el-color-primary);
/* ----- 暗黑模式 ----- */
&.dark {
--g-box-shadow-color: rgb(0 0 0 / 72%);
/* ----- 应用 ----- */
// 应用背景色
@@ -9,116 +71,54 @@
/* ----- 顶部 ----- */
// 背景色
--g-header-bg: #222b45;
--g-header-bg: var(--el-bg-color);
// 文字颜色
--g-header-color: #fff;
// 导航文字颜色
--g-header-menu-color: #fff;
--g-header-menu-color: #ccc;
// 导航鼠标悬浮文字颜色
--g-header-menu-hover-color: #fff;
// 导航鼠标悬浮背景色
--g-header-menu-hover-bg: #334067;
--g-header-menu-hover-bg: var(--el-color-primary-light-5);
// 导航选中文字颜色
--g-header-menu-active-color: #fff;
// 导航选中背景色
--g-header-menu-active-bg: #334067;
--g-header-menu-active-bg: var(--el-color-primary-light-3);
/* ----- 主导航 ----- */
// 背景色
--g-main-sidebar-bg: #222b45;
--g-main-sidebar-bg: var(--el-bg-color);
// 菜单文字颜色
--g-main-sidebar-menu-color: #eee;
--g-main-sidebar-menu-color: #ccc;
// 菜单鼠标悬浮文字颜色
--g-main-sidebar-menu-hover-color: #fff;
// 菜单鼠标悬浮背景色
--g-main-sidebar-menu-hover-bg: #334067;
--g-main-sidebar-menu-hover-bg: var(--el-color-primary-light-5);
// 菜单选中文字颜色
--g-main-sidebar-menu-active-color: #fff;
// 菜单选中背景色
--g-main-sidebar-menu-active-bg: #334067;
--g-main-sidebar-menu-active-bg: var(--el-color-primary-light-3);
/* ----- 次导航 ----- */
// 背景色
--g-sub-sidebar-bg: #fff;
--g-sub-sidebar-bg: var(--el-bg-color-page);
// 菜单背景色
--g-sub-sidebar-menu-bg: #e8e8e8;
--g-sub-sidebar-menu-bg: var(--el-bg-color);
// 菜单文字颜色
--g-sub-sidebar-menu-color: var(--el-text-color-primary);
--g-sub-sidebar-menu-color: var(--el-text-color-secondary);
// 菜单鼠标悬浮文字颜色
--g-sub-sidebar-menu-hover-color: var(--el-text-color-primary);
// 菜单鼠标悬浮背景色
--g-sub-sidebar-menu-hover-bg: #e1e1e1;
--g-sub-sidebar-menu-hover-bg: var(--el-color-primary-light-5);
// 菜单选中文字颜色
--g-sub-sidebar-menu-active-color: #fff;
--g-sub-sidebar-menu-active-color: var(--el-text-color-primary);
// 菜单选中背景色
--g-sub-sidebar-menu-active-bg: var(--el-color-primary);
--g-sub-sidebar-menu-active-bg: var(--el-color-primary-light-3);
/* ----- 顶栏 ----- */
--g-toolbar-bg: var(--el-bg-color);
/* ----- 进度条 ----- */
--g-nprogress-color: var(--el-color-primary);
/* ----- 暗黑模式 ----- */
&.dark {
--g-box-shadow-color: rgb(0 0 0 / 72%);
/* ----- 应用 ----- */
// 应用背景色
--g-app-bg: var(--el-bg-color);
// 主区域背景色
--g-main-bg: var(--el-bg-color-page);
/* ----- 顶部 ----- */
// 背景色
--g-header-bg: var(--el-bg-color);
// 文字颜色
--g-header-color: #fff;
// 导航文字颜色
--g-header-menu-color: #ccc;
// 导航鼠标悬浮文字颜色
--g-header-menu-hover-color: #fff;
// 导航鼠标悬浮背景色
--g-header-menu-hover-bg: var(--el-color-primary-light-5);
// 导航选中文字颜色
--g-header-menu-active-color: #fff;
// 导航选中背景色
--g-header-menu-active-bg: var(--el-color-primary-light-3);
/* ----- 主导航 ----- */
// 背景色
--g-main-sidebar-bg: var(--el-bg-color);
// 菜单文字颜色
--g-main-sidebar-menu-color: #ccc;
// 菜单鼠标悬浮文字颜色
--g-main-sidebar-menu-hover-color: #fff;
// 菜单鼠标悬浮背景色
--g-main-sidebar-menu-hover-bg: var(--el-color-primary-light-5);
// 菜单选中文字颜色
--g-main-sidebar-menu-active-color: #fff;
// 菜单选中背景色
--g-main-sidebar-menu-active-bg: var(--el-color-primary-light-3);
/* ----- 次导航 ----- */
// 背景色
--g-sub-sidebar-bg: var(--el-bg-color-page);
// 菜单背景色
--g-sub-sidebar-menu-bg: var(--el-bg-color);
// 菜单文字颜色
--g-sub-sidebar-menu-color: var(--el-text-color-secondary);
// 菜单鼠标悬浮文字颜色
--g-sub-sidebar-menu-hover-color: var(--el-text-color-primary);
// 菜单鼠标悬浮背景色
--g-sub-sidebar-menu-hover-bg: var(--el-color-primary-light-5);
// 菜单选中文字颜色
--g-sub-sidebar-menu-active-color: var(--el-text-color-primary);
// 菜单选中背景色
--g-sub-sidebar-menu-active-bg: var(--el-color-primary-light-3);
/* ----- 顶栏 ----- */
--g-toolbar-bg: var(--el-bg-color);
/* ----- 进度条 ----- */
--g-nprogress-color: var(--el-color-primary);
}
}
}
+9 -14
View File
@@ -1,21 +1,16 @@
<script setup name="Auth">
import { useAuth } from '@/utils/composables'
const props = defineProps({
value: {
type: [String, Array],
default: ''
}
})
<script lang="ts" setup name="Auth">
const props = defineProps<{
value: string | string[]
}>()
function check() {
return useAuth().auth(props.value)
return useAuth().auth(props.value)
}
</script>
<template>
<div>
<slot v-if="check()" />
<slot v-else name="no-auth" />
</div>
<div>
<slot v-if="check()" />
<slot v-else name="no-auth" />
</div>
</template>
+9 -14
View File
@@ -1,21 +1,16 @@
<script setup name="AuthAll">
import { useAuth } from '@/utils/composables'
const props = defineProps({
value: {
type: Array,
default: () => []
}
})
<script lang="ts" setup name="AuthAll">
const props = defineProps<{
value: string[]
}>()
function check() {
return useAuth().authAll(props.value)
return useAuth().authAll(props.value)
}
</script>
<template>
<div>
<slot v-if="check()" />
<slot v-else name="no-auth" />
</div>
<div>
<slot v-if="check()" />
<slot v-else name="no-auth" />
</div>
</template>
+60 -51
View File
@@ -1,73 +1,82 @@
<script setup name="BatchActionBar">
<script lang="ts" setup name="BatchActionBar">
const props = defineProps({
data: {
type: Array,
default: () => []
},
selectionData: {
type: Array,
default: () => []
}
data: {
type: Array,
default: () => [],
},
selectionData: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['check-all', 'check-null'])
const emit = defineEmits(['checkAll', 'checkNull'])
const checkAll = computed({
get: function() {
let flag = false
if (props.data.length != 0 && props.data.length == props.selectionData.length) {
flag = true
}
return flag
},
set: function(newVal) {
newVal ? emit('check-all') : emit('check-null')
get() {
let flag = false
if (props.data.length !== 0 && props.data.length === props.selectionData.length) {
flag = true
}
return flag
},
set(newVal) {
newVal ? emit('checkAll') : emit('checkNull')
},
})
const isIndeterminate = computed(() => {
let flag = false
if (props.selectionData.length > 0 && props.selectionData.length < props.data.length) {
flag = true
}
return flag
let flag = false
if (props.selectionData.length > 0 && props.selectionData.length < props.data.length) {
flag = true
}
return flag
})
</script>
<template>
<div class="batch-action-bar">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" :disabled="!data.length">当页全选</el-checkbox>
<div v-if="selectionData.length" class="tips">已选 {{ selectionData.length }} 项</div>
<el-form :disabled="!selectionData.length">
<slot />
</el-form>
<div class="batch-action-bar">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" :disabled="!data.length">
当页全选
</el-checkbox>
<div v-if="selectionData.length" class="tips">
已选 {{ selectionData.length }} 项
</div>
<el-form :disabled="!selectionData.length">
<slot />
</el-form>
</div>
</template>
<style lang="scss" scoped>
.batch-action-bar {
display: flex;
align-items: center;
margin: 20px 0;
padding: 0 10px;
&:first-child {
margin-top: 0;
display: flex;
align-items: center;
margin: 20px 0;
padding: 0 10px;
&:first-child {
margin-top: 0;
}
.el-checkbox {
margin-right: 20px;
}
.tips {
margin-right: 20px;
color: #909399;
font-size: 13px;
}
:deep(.el-form) {
> .el-button {
margin-right: 10px;
}
.el-checkbox {
margin-right: 20px;
}
.tips {
margin-right: 20px;
color: #909399;
font-size: 13px;
}
:deep(.el-form) {
> .el-button {
margin-right: 10px;
}
> .el-button-group {
margin-right: 10px;
}
> .el-button-group {
margin-right: 10px;
}
}
}
</style>
+37 -31
View File
@@ -1,44 +1,50 @@
<script setup name="Copyright">
<script lang="ts" setup name="Copyright">
import useSettingsStore from '@/store/modules/settings'
const route = useRoute()
const settingsStore = useSettingsStore()
</script>
<template>
<footer class="copyright">
<span>Copyright</span>
<span class="icon">©</span>
<span v-if="settingsStore.copyright.dates">{{ settingsStore.copyright.dates }}</span>
<template v-if="settingsStore.copyright.company">
<a v-if="settingsStore.copyright.website" :href="settingsStore.copyright.website" target="_blank" rel="noopener">{{ settingsStore.copyright.company }}</a>
<span v-else>{{ settingsStore.copyright.company }}</span>
</template>
<a v-if="settingsStore.copyright.beian" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ settingsStore.copyright.beian }}</a>
</footer>
<footer v-if="route.meta.copyright ?? settingsStore.settings.copyright.enable" class="copyright">
<span>Copyright</span>
<span class="icon">©</span>
<span v-if="settingsStore.settings.copyright.dates">{{ settingsStore.settings.copyright.dates }}</span>
<template v-if="settingsStore.settings.copyright.company">
<a v-if="settingsStore.settings.copyright.website" :href="settingsStore.settings.copyright.website" target="_blank" rel="noopener">{{ settingsStore.settings.copyright.company }}</a>
<span v-else>{{ settingsStore.settings.copyright.company }}</span>
</template>
<a v-if="settingsStore.settings.copyright.beian" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">{{ settingsStore.settings.copyright.beian }}</a>
</footer>
</template>
<style lang="scss" scoped>
footer {
display: flex;
align-items: center;
justify-content: center;
margin: 40px 0 20px;
display: flex;
align-items: center;
justify-content: center;
margin: 40px 0 20px;
color: var(--el-text-color-secondary);
font-size: 14px;
span,
a {
padding: 0 5px;
}
a {
text-decoration: none;
color: var(--el-text-color-secondary);
font-size: 14px;
span,
a {
padding: 0 5px;
}
a {
text-decoration: none;
color: var(--el-text-color-secondary);
transition: var(--el-transition-color);
&:hover {
color: var(--el-text-color-primary);
}
}
.icon {
padding: 0;
font-size: 18px;
transition: var(--el-transition-color);
&:hover {
color: var(--el-text-color-primary);
}
}
.icon {
padding: 0;
font-size: 18px;
}
}
</style>
+48 -48
View File
@@ -1,4 +1,4 @@
<script setup name="Editor">
<script lang="ts" setup name="Editor">
import tinymce from 'tinymce/tinymce'
import TinymceEditor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
@@ -20,78 +20,78 @@ import 'tinymce/plugins/searchreplace'
import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore()
const props = defineProps({
modelValue: {
type: String,
default: ''
},
setting: {
type: Object,
default: () => {}
},
disabled: {
type: Boolean,
default: false
}
modelValue: {
type: String,
default: '',
},
setting: {
type: Object,
default: () => {},
},
disabled: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue'])
const settingsStore = useSettingsStore()
const defaultSetting = ref({
language_url: 'tinymce/langs/zh-Hans.js',
language: 'zh-Hans',
skin_url: settingsStore.app.colorScheme === 'light' ? 'tinymce/skins/ui/oxide' : 'tinymce/skins/ui/oxide-dark',
content_css: settingsStore.app.colorScheme === 'light' ? 'tinymce/skins/content/default/content.min.css' : 'tinymce/skins/content/dark/content.min.css',
min_height: 250,
max_height: 600,
selector: 'textarea',
plugins: 'autolink autoresize fullscreen image insertdatetime link lists media preview table wordcount code searchreplace',
toolbar: 'undo redo | blocks | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | forecolor backcolor removeformat | link image media table insertdatetime searchreplace | preview code fullscreen',
branding: false,
menubar: false,
toolbar_mode: 'sliding',
insertdatetime_formats: [
'%Y年%m月%d日',
'%H点%M分%S秒',
'%Y-%m-%d',
'%H:%M:%S'
],
// https://www.tiny.cloud/docs/tinymce/6/file-image-upload/#images_upload_handler
images_upload_handler: blobInfo => new Promise(resolve => {
const img = `data:image/jpeg;base64,${blobInfo.base64()}`
resolve(img)
})
language_url: 'tinymce/langs/zh-Hans.js',
language: 'zh-Hans',
skin_url: settingsStore.settings.app.colorScheme === 'light' ? 'tinymce/skins/ui/oxide' : 'tinymce/skins/ui/oxide-dark',
content_css: settingsStore.settings.app.colorScheme === 'light' ? 'tinymce/skins/content/default/content.min.css' : 'tinymce/skins/content/dark/content.min.css',
min_height: 250,
max_height: 600,
selector: 'textarea',
plugins: 'autolink autoresize fullscreen image insertdatetime link lists media preview table wordcount code searchreplace',
toolbar: 'undo redo | blocks | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | forecolor backcolor removeformat | link image media table insertdatetime searchreplace | preview code fullscreen',
branding: false,
menubar: false,
toolbar_mode: 'sliding',
insertdatetime_formats: [
'%Y年%m月%d日',
'%H点%M分%S秒',
'%Y-%m-%d',
'%H:%M:%S',
],
// https://www.tiny.cloud/docs/tinymce/6/file-image-upload/#images_upload_handler
images_upload_handler: (blobInfo: any, progress: any) => new Promise((resolve, reject) => {
const img = `data:image/jpeg;base64,${blobInfo.base64()}`
resolve(img)
}),
})
const myValue = ref(props.modelValue)
const completeSetting = computed(() => {
return Object.assign(defaultSetting.value, props.setting)
return Object.assign(defaultSetting.value, props.setting)
})
watch(() => myValue.value, newValue => {
emit('update:modelValue', newValue)
watch(() => myValue.value, (newValue) => {
emit('update:modelValue', newValue)
})
watch(() => props.modelValue, newValue => {
myValue.value = newValue
watch(() => props.modelValue, (newValue) => {
myValue.value = newValue
})
onMounted(() => {
tinymce.init({})
tinymce.init({})
})
</script>
<template>
<div class="editor">
<TinymceEditor v-model="myValue" :init="completeSetting" :disabled="disabled" />
</div>
<div class="editor">
<TinymceEditor v-model="myValue" :init="completeSetting" :disabled="disabled" />
</div>
</template>
<style lang="scss" scoped>
:deep(.tox-tinymce) {
border-radius: 4px;
border-radius: 4px;
}
</style>
+100 -90
View File
@@ -1,107 +1,117 @@
<script setup name="FileUpload">
<script lang="ts" setup name="FileUpload">
import { ElMessage, UploadUserFile } from 'element-plus'
import type { UploadProps } from 'element-plus'
const props = defineProps({
action: {
type: String,
required: true
},
headers: {
type: Object,
default: () => {}
},
data: {
type: Object,
default: () => {}
},
name: {
type: String,
default: 'file'
},
size: {
type: Number,
default: 2
},
max: {
type: Number,
default: 3
},
files: {
type: Array,
default: () => []
},
notip: {
type: Boolean,
default: false
},
ext: {
type: Array,
default: () => ['zip', 'rar']
}
action: {
type: String,
required: true,
},
headers: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
name: {
type: String,
default: 'file',
},
size: {
type: Number,
default: 2,
},
max: {
type: Number,
default: 3,
},
files: {
type: Array,
default: () => [],
},
notip: {
type: Boolean,
default: false,
},
ext: {
type: Array,
default: () => ['zip', 'rar'],
},
})
const emit = defineEmits(['on-success'])
const emit = defineEmits(['onSuccess'])
function beforeUpload(file) {
const fileName = file.name.split('.')
const fileExt = fileName[fileName.length - 1]
const isTypeOk = props.ext.indexOf(fileExt) >= 0
const isSizeOk = file.size / 1024 / 1024 < props.size
if (!isTypeOk) {
ElMessage.error(`上传文件只支持 ${ props.ext.join(' / ') } 格式!`)
}
if (!isSizeOk) {
ElMessage.error(`上传文件大小不能超过 ${props.size}MB!`)
}
return isTypeOk && isSizeOk
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
const fileName = file.name.split('.')
const fileExt = fileName.at(-1)
const isTypeOk = props.ext.includes(fileExt)
const isSizeOk = file.size / 1024 / 1024 < props.size
if (!isTypeOk) {
ElMessage.error(`上传文件只支持 ${props.ext.join(' / ')} 格式!`)
}
if (!isSizeOk) {
ElMessage.error(`上传文件大小不能超过 ${props.size}MB!`)
}
return isTypeOk && isSizeOk
}
function onExceed() {
ElMessage.warning('文件上传超过限制')
const onExceed: UploadProps['onExceed'] = () => {
ElMessage.warning('文件上传超过限制')
}
function onSuccess(res, file, fileList) {
emit('on-success', res, file, fileList)
const onSuccess: UploadProps['onSuccess'] = (res, file, fileList) => {
emit('onSuccess', res, file, fileList)
}
</script>
<template>
<el-upload
:headers="headers"
:action="action"
:data="data"
:name="name"
:before-upload="beforeUpload"
:on-exceed="onExceed"
:on-success="onSuccess"
:file-list="files"
:limit="max"
drag
>
<div class="slot">
<el-icon class="el-icon--upload">
<svg-icon name="ep:upload-filled" />
</el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<el-upload
:headers="headers"
:action="action"
:data="data"
:name="name"
:before-upload="beforeUpload"
:on-exceed="onExceed"
:on-success="onSuccess"
:file-list="files as UploadUserFile[]"
:limit="max"
drag
>
<div class="slot">
<el-icon class="el-icon--upload">
<svg-icon name="ep:upload-filled" />
</el-icon>
<div class="el-upload__text">
将文件拖到此处,或<em>点击上传</em>
</div>
</div>
<template #tip>
<div v-if="!notip" class="el-upload__tip">
<div style="display: inline-block;">
<el-alert :title="`上传文件支持 ${ext.join(' / ')} 格式,单个文件大小不超过 ${size}MB,且文件数量不超过 ${max} 个`" type="info" show-icon :closable="false" />
</div>
<template #tip>
<div v-if="!notip" class="el-upload__tip">
<div style="display: inline-block;">
<el-alert :title="`上传文件支持 ${ ext.join(' / ') } 格式,单个文件大小不超过 ${ size }MB,且文件数量不超过 ${ max } 个`" type="info" show-icon :closable="false" />
</div>
</div>
</template>
</el-upload>
</div>
</template>
</el-upload>
</template>
<style lang="scss" scoped>
:deep(.el-upload.is-drag) {
display: inline-block;
.el-upload-dragger {
padding: 0;
}
&.is-dragover {
border-width: 1px;
}
.slot {
width: 300px;
padding: 40px 0;
}
display: inline-block;
.el-upload-dragger {
padding: 0;
}
&.is-dragover {
border-width: 1px;
}
.slot {
width: 300px;
padding: 40px 0;
}
}
</style>
+29 -33
View File
@@ -1,53 +1,49 @@
<script setup name="FixedActionBar">
const data = ref({
isBottom: true
})
<script lang="ts" setup name="FixedActionBar">
const isBottom = ref(false)
onMounted(() => {
onScroll()
window.addEventListener('scroll', onScroll)
onScroll()
window.addEventListener('scroll', onScroll)
})
onUnmounted(() => {
window.removeEventListener('scroll', onScroll)
window.removeEventListener('scroll', onScroll)
})
function onScroll() {
// 变量scrollTop是滚动条滚动时,滚动条上端距离顶部的距离
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop
// 变量windowHeight是可视区的高度
var windowHeight = document.documentElement.clientHeight || document.body.clientHeight
// 变量scrollHeight是滚动条的总高度(当前可滚动的页面的总高度)
var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
// 滚动条到底部
if (Math.ceil(scrollTop + windowHeight) >= scrollHeight) {
data.value.isBottom = true
} else {
data.value.isBottom = false
}
// 变量scrollTop是滚动条滚动时,滚动条上端距离顶部的距离
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
// 变量windowHeight是可视区的高度
const windowHeight = document.documentElement.clientHeight || document.body.clientHeight
// 变量scrollHeight是滚动条的总高度(当前可滚动的页面的总高度)
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
// 滚动条到底部
isBottom.value = Math.ceil(scrollTop + windowHeight) >= scrollHeight
}
</script>
<template>
<div class="actionbar" :class="{'shadow': !data.isBottom}" data-fixed-calc-width>
<slot />
</div>
<div class="actionbar" :class="{ shadow: !isBottom }" data-fixed-calc-width>
<slot />
</div>
</template>
<style lang="scss" scoped>
.actionbar {
z-index: 4;
bottom: 0;
padding: 20px;
text-align: center;
background-color: var(--g-app-bg);
box-shadow: 0 0 1px 0 var(--g-box-shadow-color);
transition: all 0.3s, var(--el-transition-box-shadow);
&.shadow {
box-shadow: 0 -10px 10px -10px var(--g-box-shadow-color);
}
z-index: 4;
bottom: 0;
padding: 20px;
text-align: center;
background-color: var(--g-app-bg);
box-shadow: 0 0 1px 0 var(--g-box-shadow-color);
transition: all 0.3s, var(--el-transition-box-shadow);
&.shadow {
box-shadow: 0 -10px 10px -10px var(--g-box-shadow-color);
}
}
:deep(.el-form-item) {
margin-bottom: 0;
margin-bottom: 0;
}
</style>
+46 -43
View File
@@ -1,61 +1,64 @@
<script setup name="ImagePreview">
<script lang="ts" setup name="ImagePreview">
const props = defineProps({
src: {
type: String,
required: true
},
width: {
type: [Number, String],
default: ''
},
height: {
type: [Number, String],
default: ''
}
src: {
type: String,
required: true,
},
width: {
type: [Number, String],
default: '',
},
height: {
type: [Number, String],
default: '',
},
})
const realWidth = computed(() => {
return typeof props.width == 'string' ? props.width : `${props.width}px`
return typeof props.width === 'string' ? props.width : `${props.width}px`
})
const realHeight = computed(() => {
return typeof props.height == 'string' ? props.height : `${props.height}px`
return typeof props.height === 'string' ? props.height : `${props.height}px`
})
</script>
<template>
<el-image :src="src" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="[src]">
<template #error>
<div class="image-slot">
<el-icon>
<svg-icon name="image-load-fail" />
</el-icon>
</div>
</template>
</el-image>
<el-image :src="src" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="[src]" preview-teleported>
<template #error>
<div class="image-slot">
<el-icon>
<svg-icon name="image-load-fail" />
</el-icon>
</div>
</template>
</el-image>
</template>
<style lang="scss" scoped>
.el-image {
border-radius: 5px;
background-color: var(--el-fill-color);
box-shadow: var(--el-box-shadow-light);
transition: background-color 0.3s, var(--el-transition-box-shadow);
:deep(.el-image__inner) {
transition: all 0.3s;
cursor: pointer;
&:hover {
transform: scale(1.2);
}
}
:deep(.image-slot) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: #909399;
font-size: 30px;
border-radius: 5px;
background-color: var(--el-fill-color);
box-shadow: var(--el-box-shadow-light);
transition: background-color 0.3s, var(--el-transition-box-shadow);
:deep(.el-image__inner) {
transition: all 0.3s;
cursor: pointer;
&:hover {
transform: scale(1.2);
}
}
:deep(.image-slot) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: #909399;
font-size: 30px;
}
}
</style>
+224 -202
View File
@@ -1,242 +1,264 @@
<script setup name="ImageUpload">
<script lang="ts" setup name="ImageUpload">
import type { UploadProps } from 'element-plus'
import { ElMessage } from 'element-plus'
const props = defineProps({
action: {
type: String,
required: true
},
headers: {
type: Object,
default: () => {}
},
data: {
type: Object,
default: () => {}
},
name: {
type: String,
default: 'file'
},
url: {
type: String,
default: ''
},
size: {
type: Number,
default: 2
},
width: {
type: Number,
default: 150
},
height: {
type: Number,
default: 150
},
placeholder: {
type: String,
default: ''
},
notip: {
type: Boolean,
default: false
},
ext: {
type: Array,
default: () => ['jpg', 'png', 'gif', 'bmp']
}
action: {
type: String,
required: true,
},
headers: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
name: {
type: String,
default: 'file',
},
url: {
type: String,
default: '',
},
size: {
type: Number,
default: 2,
},
width: {
type: Number,
default: 150,
},
height: {
type: Number,
default: 150,
},
placeholder: {
type: String,
default: '',
},
notip: {
type: Boolean,
default: false,
},
ext: {
type: Array,
default: () => ['jpg', 'png', 'gif', 'bmp'],
},
})
const emit = defineEmits(['update:url', 'on-success'])
const emit = defineEmits(['update:url', 'onSuccess'])
const uploadData = ref({
imageViewerVisible: false,
progress: {
preview: '',
percent: 0
}
imageViewerVisible: false,
progress: {
preview: '',
percent: 0,
},
})
// 预览
function preview() {
uploadData.value.imageViewerVisible = true
uploadData.value.imageViewerVisible = true
}
// 关闭预览
function previewClose() {
uploadData.value.imageViewerVisible = false
uploadData.value.imageViewerVisible = false
}
// 移除
function remove() {
emit('update:url', '')
emit('update:url', '')
}
function beforeUpload(file) {
const fileName = file.name.split('.')
const fileExt = fileName[fileName.length - 1]
const isTypeOk = props.ext.indexOf(fileExt) >= 0
const isSizeOk = file.size / 1024 / 1024 < props.size
if (!isTypeOk) {
ElMessage.error(`上传图片只支持 ${ props.ext.join(' / ') } 格式!`)
}
if (!isSizeOk) {
ElMessage.error(`上传图片大小不能超过 ${props.size}MB!`)
}
if (isTypeOk && isSizeOk) {
uploadData.value.progress.preview = URL.createObjectURL(file)
}
return isTypeOk && isSizeOk
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
const fileName = file.name.split('.')
const fileExt = fileName.at(-1)
const isTypeOk = props.ext.includes(fileExt)
const isSizeOk = file.size / 1024 / 1024 < props.size
if (!isTypeOk) {
ElMessage.error(`上传图片只支持 ${props.ext.join(' / ')} 格式!`)
}
if (!isSizeOk) {
ElMessage.error(`上传图片大小不能超过 ${props.size}MB!`)
}
if (isTypeOk && isSizeOk) {
uploadData.value.progress.preview = URL.createObjectURL(file)
}
return isTypeOk && isSizeOk
}
function onProgress(file) {
uploadData.value.progress.percent = ~~file.percent
const onProgress: UploadProps['onProgress'] = (file) => {
uploadData.value.progress.percent = ~~file.percent
}
function onSuccess(res) {
uploadData.value.progress.preview = ''
uploadData.value.progress.percent = 0
emit('on-success', res)
const onSuccess: UploadProps['onSuccess'] = (res) => {
uploadData.value.progress.preview = ''
uploadData.value.progress.percent = 0
emit('onSuccess', res)
}
</script>
<template>
<div class="upload-container">
<el-upload
:show-file-list="false"
:headers="headers"
:action="action"
:data="data"
:name="name"
:before-upload="beforeUpload"
:on-progress="onProgress"
:on-success="onSuccess"
drag
class="image-upload"
>
<el-image v-if="url === ''" :src="url === '' ? placeholder : url" :style="`width:${width}px;height:${height}px;`" fit="fill">
<template #error>
<div class="image-slot" :style="`width:${width}px;height:${height}px;`">
<el-icon>
<svg-icon name="ep:plus" />
</el-icon>
</div>
</template>
</el-image>
<div v-else class="image">
<el-image :src="url" :style="`width:${width}px;height:${height}px;`" fit="fill" />
<div class="mask">
<div class="actions">
<span title="预览" @click.stop="preview">
<el-icon>
<svg-icon name="ep:zoom-in" />
</el-icon>
</span>
<span title="移除" @click.stop="remove">
<el-icon>
<svg-icon name="ep:delete" />
</el-icon>
</span>
</div>
</div>
</div>
<div v-show="url === '' && uploadData.progress.percent" class="progress" :style="`width:${width}px;height:${height}px;`">
<el-image :src="uploadData.progress.preview" :style="`width:${width}px;height:${height}px;`" fit="fill" />
<el-progress type="circle" :width="Math.min(width, height) * 0.8" :percentage="uploadData.progress.percent" />
</div>
</el-upload>
<div v-if="!notip" class="el-upload__tip">
<div style="display: inline-block;">
<el-alert :title="`上传图片支持 ${ ext.join(' / ') } 格式,且图片大小不超过 ${ size }MB,建议图片尺寸为 ${width}*${height}`" type="info" show-icon :closable="false" />
</div>
<div class="upload-container">
<el-upload
:show-file-list="false"
:headers="headers"
:action="action"
:data="data"
:name="name"
:before-upload="beforeUpload"
:on-progress="onProgress"
:on-success="onSuccess"
drag
class="image-upload"
>
<el-image v-if="url === ''" :src="url === '' ? placeholder : url" :style="`width:${width}px;height:${height}px;`" fit="fill">
<template #error>
<div class="image-slot" :style="`width:${width}px;height:${height}px;`">
<el-icon>
<svg-icon name="ep:plus" />
</el-icon>
</div>
</template>
</el-image>
<div v-else class="image">
<el-image :src="url" :style="`width:${width}px;height:${height}px;`" fit="fill" />
<div class="mask">
<div class="actions">
<span title="预览" @click.stop="preview">
<el-icon>
<svg-icon name="ep:zoom-in" />
</el-icon>
</span>
<span title="移除" @click.stop="remove">
<el-icon>
<svg-icon name="ep:delete" />
</el-icon>
</span>
</div>
</div>
<el-image-viewer v-if="uploadData.imageViewerVisible" :url-list="[url]" @close="previewClose" />
</div>
<div v-show="url === '' && uploadData.progress.percent" class="progress" :style="`width:${width}px;height:${height}px;`">
<el-image :src="uploadData.progress.preview" :style="`width:${width}px;height:${height}px;`" fit="fill" />
<el-progress type="circle" :width="Math.min(width, height) * 0.8" :percentage="uploadData.progress.percent" />
</div>
</el-upload>
<div v-if="!notip" class="el-upload__tip">
<div style="display: inline-block;">
<el-alert :title="`上传图片支持 ${ext.join(' / ')} 格式,且图片大小不超过 ${size}MB,建议图片尺寸为 ${width}*${height}`" type="info" show-icon :closable="false" />
</div>
</div>
<el-image-viewer v-if="uploadData.imageViewerVisible" :url-list="[url]" teleported @close="previewClose" />
</div>
</template>
<style lang="scss" scoped>
.upload-container {
line-height: initial;
line-height: initial;
}
.el-image {
display: block;
display: block;
}
.image {
position: relative;
border-radius: 6px;
overflow: hidden;
.mask {
opacity: 0;
position: relative;
border-radius: 6px;
overflow: hidden;
.mask {
opacity: 0;
position: absolute;
top: 0;
width: 100%;
height: 100%;
background-color: var(--el-overlay-color-lighter);
transition: opacity 0.3s;
.actions {
width: 100px;
height: 100px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
@include position-center(xy);
span {
width: 50%;
text-align: center;
cursor: pointer;
color: var(--el-color-white);
transition: color 0.1s, transform 0.1s;
&:hover {
transform: scale(1.5);
}
.el-icon {
font-size: 24px;
}
}
}
}
&:hover .mask {
opacity: 1;
}
}
.image-upload {
display: inline-block;
vertical-align: top;
}
:deep(.el-upload) {
.el-upload-dragger {
display: inline-block;
padding: 0;
&.is-dragover {
border-width: 1px;
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: var(--el-text-color-placeholder);
background-color: transparent;
i {
font-size: 30px;
}
}
.progress {
position: absolute;
top: 0;
&::after {
content: "";
position: absolute;
top: 0;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: var(--el-overlay-color-lighter);
transition: opacity 0.3s;
.actions {
width: 100px;
height: 100px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
@include position-center(xy);
span {
width: 50%;
text-align: center;
cursor: pointer;
color: var(--el-color-white);
transition: color 0.1s, transform 0.1s;
&:hover {
transform: scale(1.5);
}
.el-icon {
font-size: 24px;
}
}
}
}
&:hover .mask {
opacity: 1;
}
}
.image-upload {
display: inline-block;
vertical-align: top;
}
:deep(.el-upload) {
.el-upload-dragger {
display: inline-block;
padding: 0;
&.is-dragover {
border-width: 1px;
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: var(--el-text-color-placeholder);
background-color: transparent;
i {
font-size: 30px;
}
}
.progress {
position: absolute;
top: 0;
&::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: var(--el-overlay-color-lighter);
}
.el-progress {
z-index: 1;
@include position-center(xy);
.el-progress__text {
color: var(--el-text-color-placeholder);
}
}
}
.el-progress {
z-index: 1;
@include position-center(xy);
.el-progress__text {
color: var(--el-text-color-placeholder);
}
}
}
}
}
</style>
+258 -234
View File
@@ -1,275 +1,299 @@
<script setup name="ImagesUpload">
<script lang="ts" setup name="ImagesUpload">
import type { UploadProps } from 'element-plus'
import { ElMessage } from 'element-plus'
const props = defineProps({
action: {
type: String,
required: true
},
headers: {
type: Object,
default: () => {}
},
data: {
type: Object,
default: () => {}
},
name: {
type: String,
default: 'file'
},
url: {
type: Array,
default: () => []
},
max: {
type: Number,
default: 3
},
size: {
type: Number,
default: 2
},
width: {
type: Number,
default: 150
},
height: {
type: Number,
default: 150
},
placeholder: {
type: String,
default: ''
},
notip: {
type: Boolean,
default: false
},
ext: {
type: Array,
default: () => ['jpg', 'png', 'gif', 'bmp']
}
action: {
type: String,
required: true,
},
headers: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
name: {
type: String,
default: 'file',
},
url: {
type: Array,
default: () => [],
},
max: {
type: Number,
default: 3,
},
size: {
type: Number,
default: 2,
},
width: {
type: Number,
default: 150,
},
height: {
type: Number,
default: 150,
},
placeholder: {
type: String,
default: '',
},
notip: {
type: Boolean,
default: false,
},
ext: {
type: Array,
default: () => ['jpg', 'png', 'gif', 'bmp'],
},
})
const emit = defineEmits(['update:url', 'on-success'])
const emit = defineEmits(['update:url', 'onSuccess'])
const uploadData = ref({
dialogImageIndex: 0,
imageViewerVisible: false,
progress: {
preview: '',
percent: 0
}
dialogImageIndex: 0,
imageViewerVisible: false,
progress: {
preview: '',
percent: 0,
},
})
// 预览
function preview(index) {
uploadData.value.dialogImageIndex = index
uploadData.value.imageViewerVisible = true
function preview(index: number) {
uploadData.value.dialogImageIndex = index
uploadData.value.imageViewerVisible = true
}
// 关闭预览
function previewClose() {
uploadData.value.imageViewerVisible = false
uploadData.value.imageViewerVisible = false
}
// 移除
function remove(index) {
let url = props.url
url.splice(index, 1)
emit('update:url', url)
function remove(index: number) {
const url = props.url
url.splice(index, 1)
emit('update:url', url)
}
// 移动
function move(index, type) {
let url = props.url
if (type == 'left' && index != 0) {
url[index] = url.splice(index - 1, 1, url[index])[0]
}
if (type == 'right' && index != url.length - 1) {
url[index] = url.splice(index + 1, 1, url[index])[0]
}
emit('update:url', url)
function move(index: number, type: 'left' | 'right') {
const url = props.url
if (type === 'left' && index !== 0) {
url[index] = url.splice(index - 1, 1, url[index])[0]
}
if (type === 'right' && index !== url.length - 1) {
url[index] = url.splice(index + 1, 1, url[index])[0]
}
emit('update:url', url)
}
function beforeUpload(file) {
const fileName = file.name.split('.')
const fileExt = fileName[fileName.length - 1]
const isTypeOk = props.ext.indexOf(fileExt) >= 0
const isSizeOk = file.size / 1024 / 1024 < props.size
if (!isTypeOk) {
ElMessage.error(`上传图片只支持 ${ props.ext.join(' / ') } 格式!`)
}
if (!isSizeOk) {
ElMessage.error(`上传图片大小不能超过 ${props.size}MB!`)
}
if (isTypeOk && isSizeOk) {
uploadData.value.progress.preview = URL.createObjectURL(file)
}
return isTypeOk && isSizeOk
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
const fileName = file.name.split('.')
const fileExt = fileName.at(-1)
const isTypeOk = props.ext.includes(fileExt)
const isSizeOk = file.size / 1024 / 1024 < props.size
if (!isTypeOk) {
ElMessage.error(`上传图片只支持 ${props.ext.join(' / ')} 格式!`)
}
if (!isSizeOk) {
ElMessage.error(`上传图片大小不能超过 ${props.size}MB!`)
}
if (isTypeOk && isSizeOk) {
uploadData.value.progress.preview = URL.createObjectURL(file)
}
return isTypeOk && isSizeOk
}
function onProgress(file) {
uploadData.value.progress.percent = ~~file.percent
const onProgress: UploadProps['onProgress'] = (file) => {
uploadData.value.progress.percent = ~~file.percent
}
function onSuccess(res) {
uploadData.value.progress.preview = ''
uploadData.value.progress.percent = 0
emit('on-success', res)
const onSuccess: UploadProps['onSuccess'] = (res) => {
uploadData.value.progress.preview = ''
uploadData.value.progress.percent = 0
emit('onSuccess', res)
}
</script>
<template>
<div class="upload-container">
<div v-for="(item, index) in url" :key="index" class="images">
<el-image v-if="index < max" :src="item" :style="`width:${width}px;height:${height}px;`" fit="cover" />
<div class="mask">
<div class="actions">
<span title="预览" @click="preview(index)">
<el-icon>
<svg-icon name="ep:zoom-in" />
</el-icon>
</span>
<span title="移除" @click="remove(index)">
<el-icon>
<svg-icon name="ep:delete" />
</el-icon>
</span>
<span v-show="url.length > 1" title="左移" :class="{'disabled': index == 0}" @click="move(index, 'left')">
<el-icon>
<svg-icon name="ep:back" />
</el-icon>
</span>
<span v-show="url.length > 1" title="右移" :class="{'disabled': index == url.length - 1}" @click="move(index, 'right')">
<el-icon>
<svg-icon name="ep:right" />
</el-icon>
</span>
</div>
</div>
<div class="upload-container">
<div v-for="(item, index) in (url as string[])" :key="index" class="images">
<el-image v-if="index < max" :src="item" :style="`width:${width}px;height:${height}px;`" fit="cover" />
<div class="mask">
<div class="actions">
<span title="预览" @click="preview(index)">
<el-icon>
<svg-icon name="ep:zoom-in" />
</el-icon>
</span>
<span title="移除" @click="remove(index)">
<el-icon>
<svg-icon name="ep:delete" />
</el-icon>
</span>
<span v-show="url.length > 1" title="左移" :class="{ disabled: index === 0 }" @click="move(index, 'left')">
<el-icon>
<svg-icon name="ep:back" />
</el-icon>
</span>
<span v-show="url.length > 1" title="右移" :class="{ disabled: index === url.length - 1 }" @click="move(index, 'right')">
<el-icon>
<svg-icon name="ep:right" />
</el-icon>
</span>
</div>
<el-upload
v-show="url.length < max"
:show-file-list="false"
:headers="headers"
:action="action"
:data="data"
:name="name"
:before-upload="beforeUpload"
:on-progress="onProgress"
:on-success="onSuccess"
drag
class="images-upload"
>
<div class="image-slot" :style="`width:${width}px;height:${height}px;`">
<el-icon>
<svg-icon name="ep:plus" />
</el-icon>
</div>
<div v-show="uploadData.progress.percent" class="progress" :style="`width:${width}px;height:${height}px;`">
<el-image :src="uploadData.progress.preview" :style="`width:${width}px;height:${height}px;`" fit="fill" />
<el-progress type="circle" :width="Math.min(width, height) * 0.8" :percentage="uploadData.progress.percent" />
</div>
</el-upload>
<div v-if="!notip" class="el-upload__tip">
<div style="display: inline-block;">
<el-alert :title="`上传图片支持 ${ ext.join(' / ') } 格式,单张图片大小不超过 ${ size }MB,建议图片尺寸为 ${width}*${height},且图片数量不超过 ${ max } 张`" type="info" show-icon :closable="false" />
</div>
</div>
<el-image-viewer v-if="uploadData.imageViewerVisible" :url-list="url" :initial-index="uploadData.dialogImageIndex" @close="previewClose" />
</div>
</div>
<el-upload
v-show="url.length < max"
:show-file-list="false"
:headers="headers"
:action="action"
:data="data"
:name="name"
:before-upload="beforeUpload"
:on-progress="onProgress"
:on-success="onSuccess"
drag
class="images-upload"
>
<div class="image-slot" :style="`width:${width}px;height:${height}px;`">
<el-icon>
<svg-icon name="ep:plus" />
</el-icon>
</div>
<div v-show="uploadData.progress.percent" class="progress" :style="`width:${width}px;height:${height}px;`">
<el-image :src="uploadData.progress.preview" :style="`width:${width}px;height:${height}px;`" fit="fill" />
<el-progress type="circle" :width="Math.min(width, height) * 0.8" :percentage="uploadData.progress.percent" />
</div>
</el-upload>
<div v-if="!notip" class="el-upload__tip">
<div style="display: inline-block;">
<el-alert :title="`上传图片支持 ${ext.join(' / ')} 格式,单张图片大小不超过 ${size}MB,建议图片尺寸为 ${width}*${height},且图片数量不超过 ${max} 张`" type="info" show-icon :closable="false" />
</div>
</div>
<el-image-viewer v-if="uploadData.imageViewerVisible" :url-list="url as string[]" :initial-index="uploadData.dialogImageIndex" teleported @close="previewClose" />
</div>
</template>
<style lang="scss" scoped>
.upload-container {
line-height: initial;
line-height: initial;
}
.el-image {
display: block;
display: block;
}
.images {
position: relative;
position: relative;
display: inline-block;
margin-right: 10px;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
overflow: hidden;
.mask {
opacity: 0;
position: absolute;
top: 0;
width: 100%;
height: 100%;
background-color: var(--el-overlay-color-lighter);
transition: opacity 0.3s;
.actions {
width: 100px;
height: 100px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
@include position-center(xy);
span {
width: 50%;
text-align: center;
cursor: pointer;
color: var(--el-color-white);
transition: color 0.1s, transform 0.1s;
&.disabled {
color: var(--el-text-color-disabled);
cursor: not-allowed;
}
&:hover:not(.disabled) {
transform: scale(1.5);
}
.el-icon {
font-size: 24px;
}
}
}
}
&:hover .mask {
opacity: 1;
}
}
.images-upload {
display: inline-block;
vertical-align: top;
}
:deep(.el-upload) {
.el-upload-dragger {
display: inline-block;
margin-right: 10px;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
overflow: hidden;
.mask {
opacity: 0;
padding: 0;
&.is-dragover {
border-width: 1px;
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: var(--el-text-color-placeholder);
background-color: transparent;
i {
font-size: 30px;
}
}
.progress {
position: absolute;
top: 0;
&::after {
content: "";
position: absolute;
top: 0;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: var(--el-overlay-color-lighter);
transition: opacity 0.3s;
.actions {
width: 100px;
height: 100px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
@include position-center(xy);
span {
width: 50%;
text-align: center;
cursor: pointer;
color: var(--el-color-white);
transition: color 0.1s, transform 0.1s;
&.disabled {
color: var(--el-text-color-disabled);
cursor: not-allowed;
}
&:hover:not(.disabled) {
transform: scale(1.5);
}
.el-icon {
font-size: 24px;
}
}
}
}
&:hover .mask {
opacity: 1;
}
}
.images-upload {
display: inline-block;
vertical-align: top;
}
:deep(.el-upload) {
.el-upload-dragger {
display: inline-block;
padding: 0;
&.is-dragover {
border-width: 1px;
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: var(--el-text-color-placeholder);
background-color: transparent;
i {
font-size: 30px;
}
}
.progress {
position: absolute;
top: 0;
&::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: var(--el-overlay-color-lighter);
}
.el-progress {
z-index: 1;
@include position-center(xy);
.el-progress__text {
color: var(--el-text-color-placeholder);
}
}
}
.el-progress {
z-index: 1;
@include position-center(xy);
.el-progress__text {
color: var(--el-text-color-placeholder);
}
}
}
}
}
</style>
+67 -45
View File
@@ -1,59 +1,81 @@
<script setup name="PageHeader">
<script lang="ts" setup name="PageHeader">
defineProps({
title: {
type: String,
required: true
},
content: {
type: String,
default: ''
}
title: {
type: String,
required: true,
},
content: {
type: String,
default: '',
},
})
const slots = useSlots()
</script>
<template>
<div class="header">
<div class="main">
<div class="title">{{ title }}</div>
<div class="content">
<slot name="content">{{ content }}</slot>
</div>
</div>
<div class="sub">
<slot />
</div>
<div class="header">
<div class="main">
<div class="title">
{{ title }}
</div>
<div class="content">
<slot name="content">
{{ content }}
</slot>
</div>
</div>
<div v-if="slots.default" class="sub">
<slot />
</div>
</div>
</template>
<style lang="scss" scoped>
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding: 16px 20px;
background-color: var(--g-app-bg);
transition: background-color 0.3s;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding: 16px 20px;
background-color: var(--g-app-bg);
transition: background-color 0.3s;
.main {
flex: 1;
margin-right: 20px;
.title {
font-size: 22px;
color: var(--el-text-color-primary);
transition: var(--el-transition-color);
}
.content {
margin-top: 10px;
font-size: 14px;
color: var(--el-text-color-secondary);
transition: var(--el-transition-color);
&:empty {
display: none;
}
}
}
.sub {
flex: none;
}
}
[data-mode="mobile"] {
.header {
flex-direction: column;
.main {
flex: 1;
margin-right: 20px;
.title {
font-size: 22px;
color: var(--el-text-color-primary);
transition: var(--el-transition-color);
}
.content {
margin-top: 10px;
font-size: 14px;
color: var(--el-text-color-secondary);
transition: var(--el-transition-color);
&:empty {
display: none;
}
}
}
.sub {
flex: none;
margin-right: auto;
margin-bottom: 20px;
}
}
}
</style>
+70 -65
View File
@@ -1,84 +1,89 @@
<script setup name="PageMain">
<script lang="ts" setup name="PageMain">
const props = defineProps({
title: {
type: String,
default: ''
},
collaspe: {
type: Boolean,
default: false
},
height: {
type: String,
default: ''
}
title: {
type: String,
default: '',
},
collaspe: {
type: Boolean,
default: false,
},
height: {
type: String,
default: '',
},
})
const titleSlot = !!useSlots().title
const collaspeData = ref(props.collaspe)
function unCollaspe() {
collaspeData.value = false
collaspeData.value = false
}
</script>
<template>
<div
:class="{
'page-main': true,
'is-collaspe': collaspeData
}" :style="{
'height': collaspeData ? height : ''
}"
>
<div v-if="titleSlot || title" class="title-container">
<slot v-if="titleSlot" name="title" />
<template v-else>{{ title }}</template>
</div>
<slot />
<div v-if="collaspeData" class="collaspe" title="展开" @click="unCollaspe">
<el-icon>
<svg-icon name="ep:arrow-down" />
</el-icon>
</div>
<div
class="page-main" :class="{
'is-collaspe': collaspeData,
}" :style="{
height: collaspeData ? height : '',
}"
>
<div v-if="titleSlot || title" class="title-container">
<slot v-if="titleSlot" name="title" />
<template v-else>
{{ title }}
</template>
</div>
<slot />
<div v-if="collaspeData" class="collaspe" title="展开" @click="unCollaspe">
<el-icon>
<svg-icon name="ep:arrow-down" />
</el-icon>
</div>
</div>
</template>
<style lang="scss" scoped>
.page-main {
position: relative;
margin: 20px;
padding: 20px;
background-color: var(--g-app-bg);
transition: background-color 0.3s;
&.is-collaspe {
overflow: hidden;
.collaspe {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
padding: 40px 0 10px;
text-align: center;
font-size: 24px;
color: var(--el-text-color-primary);
text-shadow: 0 0 1px var(--el-text-color-primary);
background: linear-gradient(to bottom, transparent, var(--g-app-bg));
transition: background 0.3s, var(--el-transition-color);
cursor: pointer;
&:hover {
color: var(--el-text-color-secondary);
}
}
}
.title-container {
width: calc(100% + 40px);
padding: 14px 20px;
margin-left: -20px;
margin-top: -20px;
margin-bottom: 20px;
border-bottom: 1px solid var(--el-border-color-lighter);
transition: var(--el-transition-border);
position: relative;
margin: 20px;
padding: 20px;
background-color: var(--g-app-bg);
transition: background-color 0.3s;
&.is-collaspe {
overflow: hidden;
.collaspe {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
padding: 40px 0 10px;
text-align: center;
font-size: 24px;
color: var(--el-text-color-primary);
text-shadow: 0 0 1px var(--el-text-color-primary);
background: linear-gradient(to bottom, transparent, var(--g-app-bg));
transition: background 0.3s, var(--el-transition-color);
cursor: pointer;
&:hover {
color: var(--el-text-color-secondary);
}
}
}
.title-container {
width: calc(100% + 40px);
padding: 14px 20px;
margin-left: -20px;
margin-top: -20px;
margin-bottom: 20px;
border-bottom: 1px solid var(--el-border-color-lighter);
transition: var(--el-transition-border);
}
}
</style>
+116 -107
View File
@@ -1,131 +1,140 @@
<script setup name="PcasCascader">
<script lang="ts" setup name="PcasCascader">
// 行政区划数据来源于 https://github.com/modood/Administrative-divisions-of-China
import pcasRaw from './pcas-code.json'
const props = defineProps({
modelValue: {
type: Array,
default: () => []
},
disabled: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'pca'
},
format: {
type: String,
default: 'code'
}
modelValue: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
type: {
type: String as () => 'pc' | 'pca' | 'pcas',
default: 'pca',
},
format: {
type: String as () => 'code' | 'name' | 'both',
default: 'code',
},
})
const emit = defineEmits(['update:modelValue'])
interface pcasItem {
code: string
name: string
children?: pcasItem[]
}
const pcasData = computed(() => {
const data = []
// 省份
pcasRaw.forEach(p => {
const tempP = {
code: p.code,
name: p.name
}
const tempChildrenC = []
// 城市
p.children.forEach(c => {
const tempC = {
code: c.code,
name: c.name
}
if (['pca', 'pcas'].includes(props.type)) {
const tempChildrenA = []
// 区县
c.children.forEach(a => {
const tempA = {
code: a.code,
name: a.name
}
if (props.type === 'pcas') {
const tempChildrenS = []
// 街道
a.children.forEach(s => {
const tempS = {
code: s.code,
name: s.name
}
tempChildrenS.push(tempS)
})
tempA.children = tempChildrenS
}
tempChildrenA.push(tempA)
})
tempC.children = tempChildrenA
}
tempChildrenC.push(tempC)
const data: pcasItem[] = []
// 省份
pcasRaw.forEach((p) => {
const tempP: pcasItem = {
code: p.code,
name: p.name,
}
const tempChildrenC: pcasItem[] = []
// 城市
p.children.forEach((c) => {
const tempC: pcasItem = {
code: c.code,
name: c.name,
}
if (['pca', 'pcas'].includes(props.type)) {
const tempChildrenA: pcasItem[] = []
// 区县
c.children.forEach((a) => {
const tempA: pcasItem = {
code: a.code,
name: a.name,
}
if (props.type === 'pcas') {
const tempChildrenS: pcasItem[] = []
// 街道
a.children.forEach((s) => {
const tempS: pcasItem = {
code: s.code,
name: s.name,
}
tempChildrenS.push(tempS)
})
tempA.children = tempChildrenS
}
tempChildrenA.push(tempA)
})
tempP.children = tempChildrenC
data.push(tempP)
tempC.children = tempChildrenA
}
tempChildrenC.push(tempC)
})
return data
tempP.children = tempChildrenC
data.push(tempP)
})
return data
})
const myValue = computed({
// 将入参数据转成 code 码
get: () => {
return anyToCode(props.modelValue)
},
// 将 code 码转成出参数据
set: value => {
emit('update:modelValue', value ? codeToAny(value) : [])
}
// 将入参数据转成 code 码
get: () => {
return anyToCode(props.modelValue)
},
// 将 code 码转成出参数据
set: (value) => {
emit('update:modelValue', value ? codeToAny(value) : [])
},
})
function anyToCode(value, dictionarie = pcasData.value) {
const input = []
if (value.length > 0) {
const findItem = dictionarie.find(item => {
if (props.format === 'code') {
return item.code === value[0]
} else if (props.format === 'name') {
return item.name === value[0]
} else {
return item.name === value[0].name && item.code === value[0].code
}
})
input.push(findItem.code)
if (findItem.children) {
input.push(...anyToCode(value.slice(1 - value.length), findItem.children))
}
function anyToCode(value: any[], dictionarie: any[] = pcasData.value) {
const input: string[] = []
if (value.length > 0) {
const findItem = dictionarie.find((item) => {
if (props.format === 'code') {
return item.code === value[0]
}
else if (props.format === 'name') {
return item.name === value[0]
}
else {
return item.name === value[0].name && item.code === value[0].code
}
})
input.push(findItem.code)
if (findItem.children) {
input.push(...anyToCode(value.slice(1 - value.length), findItem.children))
}
return input
}
return input
}
function codeToAny(codes, dictionarie = pcasData.value) {
const output = []
const findItem = dictionarie.find(item => item.code === codes[0])
if (findItem) {
switch (props.format) {
case 'code':
output.push(findItem.code)
break
case 'name':
output.push(findItem.name)
break
case 'both':
output.push({
code: findItem.code,
name: findItem.name
})
}
const newCodes = codes.slice(1 - codes.length)
if (newCodes.length > 0 && findItem.children) {
output.push(...codeToAny(newCodes, findItem.children))
}
function codeToAny(codes: string[], dictionarie: any[] = pcasData.value): any {
const output = []
const findItem = dictionarie.find(item => item.code === codes[0])
if (findItem) {
switch (props.format) {
case 'code':
output.push(findItem.code)
break
case 'name':
output.push(findItem.name)
break
case 'both':
output.push({
code: findItem.code,
name: findItem.name,
})
}
return output
const newCodes = codes.slice(1 - codes.length)
if (newCodes.length > 0 && findItem.children) {
output.push(...codeToAny(newCodes, findItem.children))
}
}
return output
}
</script>
<template>
<el-cascader v-model="myValue" :options="pcasData" :props="{ value: 'code', label: 'name' }" :disabled="disabled" clearable filterable />
<el-cascader v-model="myValue" :options="pcasData as any[]" :props="{ value: 'code', label: 'name' }" :disabled="disabled" clearable filterable />
</template>
+54 -48
View File
@@ -1,13 +1,13 @@
<script setup name="SearchBar">
<script lang="ts" setup name="SearchBar">
const props = defineProps({
showMore: {
type: Boolean,
default: false
},
unfold: {
type: Boolean,
default: false
}
showMore: {
type: Boolean,
default: false,
},
unfold: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['toggle'])
@@ -15,57 +15,63 @@ const emit = defineEmits(['toggle'])
const isUnfold = ref(!props.unfold)
watch(() => props.unfold, () => toggle(), {
immediate: true
immediate: true,
})
function toggle() {
isUnfold.value = !isUnfold.value
emit('toggle', isUnfold.value)
isUnfold.value = !isUnfold.value
emit('toggle', isUnfold.value)
}
</script>
<template>
<div class="search-container">
<slot />
<div v-if="showMore" class="more">
<el-button text size="small" @click="toggle">
<template #icon>
<el-icon>
<svg-icon :name="isUnfold ? 'ep:caret-top' : 'ep:caret-bottom'" />
</el-icon>
</template>
{{ isUnfold ? '收起' : '展开' }}
</el-button>
</div>
<div class="search-container">
<slot />
<div v-if="showMore" class="more">
<el-button text size="small" @click="toggle">
<template #icon>
<el-icon>
<svg-icon :name="isUnfold ? 'ep:caret-top' : 'ep:caret-bottom'" />
</el-icon>
</template>
{{ isUnfold ? '收起' : '展开' }}
</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
.search-container {
position: relative;
margin: 20px 0;
padding: 20px;
background-color: var(--el-fill-color-lighter);
transition: background-color 0.3s;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
:deep(.el-form) {
margin-bottom: -10px;
.el-select {
width: 100%;
}
.el-date-editor {
width: 100%;
}
}
.more {
position: relative;
margin: 20px 0;
padding: 20px;
background-color: var(--el-fill-color);
transition: background-color 0.3s;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
:deep(.el-form) {
margin-bottom: -10px;
.el-select {
width: 100%;
}
.el-date-editor {
width: 100%;
}
}
.more {
position: relative;
text-align: center;
margin-bottom: -10px;
}
text-align: center;
margin-bottom: -10px;
}
}
</style>
+35 -41
View File
@@ -1,53 +1,47 @@
<script setup name="SvgIcon">
<script lang="ts" setup name="SvgIcon">
import { Icon } from '@iconify/vue'
const props = defineProps({
name: {
type: String,
required: true
},
flip: {
type: String,
validator(value) {
return ['', 'horizontal', 'vertical', 'both'].includes(value)
},
default: ''
},
rotate: {
type: Number,
validator(value) {
return value >= 0 && value <= 360
},
default: 0
}
name: {
type: String,
required: true,
},
flip: {
type: String as () => 'horizontal' | 'vertical' | 'both' | '',
default: '',
},
rotate: {
type: Number,
default: 0,
},
})
const transformStyle = computed(() => {
let style = []
if (props.flip != '') {
switch (props.flip) {
case 'horizontal':
style.push('rotateY(180deg)')
break
case 'vertical':
style.push('rotateX(180deg)')
break
case 'both':
style.push('rotateX(180deg)')
style.push('rotateY(180deg)')
break
}
const style = []
if (props.flip !== '') {
switch (props.flip) {
case 'horizontal':
style.push('rotateY(180deg)')
break
case 'vertical':
style.push('rotateX(180deg)')
break
case 'both':
style.push('rotateX(180deg)')
style.push('rotateY(180deg)')
break
}
if (props.rotate != 0) {
style.push(`rotate(${props.rotate}deg)`)
}
return `transform: ${style.join(' ')};`
}
if (props.rotate !== 0) {
style.push(`rotate(${props.rotate}deg)`)
}
return `transform: ${style.join(' ')};`
})
</script>
<template>
<Icon v-if="name.indexOf('ep:') === 0" :icon="name" :style="transformStyle" />
<svg v-else :style="transformStyle" aria-hidden="true">
<use :xlink:href="`#icon-${name}`" />
</svg>
<Icon v-if="name.indexOf('ep:') === 0" :icon="name" :style="transformStyle" />
<svg v-else :style="transformStyle" aria-hidden="true">
<use :xlink:href="`#icon-${name}`" />
</svg>
</template>
+51
View File
@@ -0,0 +1,51 @@
<script setup lang="ts">
import eventBus from '@/utils/eventBus'
const isShow = ref(false)
const { pkg, lastBuildTime } = __SYSTEM_INFO__
onMounted(() => {
eventBus.on('global-system-info-toggle', () => {
isShow.value = !isShow.value
})
})
</script>
<template>
<div>
<el-drawer v-model="isShow" title="系统信息" direction="rtl" :size="360">
<el-descriptions direction="vertical" :column="1" border>
<el-descriptions-item label="版本号" align="center">
{{ pkg.version }}
</el-descriptions-item>
<el-descriptions-item label="最后编译时间" align="center">
{{ lastBuildTime }}
</el-descriptions-item>
</el-descriptions>
<el-descriptions title="生产环境依赖" :column="1" size="small" border>
<el-descriptions-item v-for="(val, key) in (pkg.dependencies as object)" :key="key" :label="key">
{{ val }}
</el-descriptions-item>
</el-descriptions>
<el-descriptions title="开发环境依赖" :column="1" size="small" border>
<el-descriptions-item v-for="(val, key) in (pkg.devDependencies as object)" :key="key" :label="key">
{{ val }}
</el-descriptions-item>
</el-descriptions>
</el-drawer>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-drawer__header) {
margin-bottom: initial;
padding-bottom: 20px;
border-bottom: 1px solid var(--el-border-color);
transition: var(--el-transition-border);
}
:deep(.el-descriptions) {
margin-bottom: 20px;
}
</style>
+52 -49
View File
@@ -1,65 +1,68 @@
<script setup name="Trend">
<script lang="ts" setup name="Trend">
const props = defineProps({
value: {
type: String,
required: true
},
type: {
type: String,
validator: val => ['up', 'down'].includes(val),
default: 'up'
},
prefix: {
type: String,
default: ''
},
suffix: {
type: String,
default: ''
},
reverse: {
type: Boolean,
default: false
}
value: {
type: String,
required: true,
},
type: {
type: String as () => 'up' | 'down',
default: 'up',
},
prefix: {
type: String,
default: '',
},
suffix: {
type: String,
default: '',
},
reverse: {
type: Boolean,
default: false,
},
})
const isUp = computed(() => {
let isUp = props.type === 'up'
if (props.reverse) {
isUp = !isUp
}
return isUp
let isUp = props.type === 'up'
if (props.reverse) {
isUp = !isUp
}
return isUp
})
</script>
<template>
<div :class="`trend ${isUp ? 'up' : 'down'}`">
<span v-if="prefix" class="prefix">{{ prefix }}</span>
<span class="text">{{ value }}</span>
<span v-if="suffix" class="suffix">{{ suffix }}</span>
<el-icon>
<svg-icon name="ep:caret-top" />
</el-icon>
</div>
<div :class="`trend ${isUp ? 'up' : 'down'}`">
<span v-if="prefix" class="prefix">{{ prefix }}</span>
<span class="text">{{ value }}</span>
<span v-if="suffix" class="suffix">{{ suffix }}</span>
<el-icon>
<svg-icon name="ep:caret-top" />
</el-icon>
</div>
</template>
<style lang="scss" scoped>
.trend {
display: flex;
align-items: center;
transition: var(--el-transition-color);
&.up {
color: var(--el-color-success);
}
&.down {
color: var(--el-color-danger);
.el-icon {
transform: rotate(180deg);
}
}
display: flex;
align-items: center;
transition: var(--el-transition-color);
&.up {
color: var(--el-color-success);
}
&.down {
color: var(--el-color-danger);
.el-icon {
margin-left: 5px;
transition: transform 0.3s;
transform: rotate(180deg);
}
}
.el-icon {
margin-left: 5px;
transition: transform 0.3s;
}
}
</style>
-6
View File
@@ -1,6 +0,0 @@
import { addCollection } from '@iconify/vue'
export async function downloadAndInstall() {
const data = Object.freeze(await fetch('./icons/icons.json').then(r => r.json()))
addCollection(data)
}
+6
View File
@@ -0,0 +1,6 @@
import { addCollection } from '@iconify/vue'
export async function downloadAndInstall() {
const data = Object.freeze(await fetch('./icons/icons.json').then(r => r.json()))
addCollection(data)
}
-457
View File
@@ -1,457 +0,0 @@
<script setup name="AppSetting">
import { useClipboard } from '@vueuse/core'
import eventBus from '@/utils/eventBus'
import useSettingsStore from '@/store/modules/settings'
import useMenuStore from '@/store/modules/menu'
import globalSettings from '@/settings'
const route = useRoute()
const settingsStore = useSettingsStore()
const menuStore = useMenuStore()
const isShow = ref(false)
const settings = ref(globalSettings)
watch(() => settings, () => {
settingsStore.updateThemeSetting(settings.value)
menuStore.setActived(0)
settings.value.menu.menuMode !== 'single' && menuStore.setActived(route.fullPath)
}, {
deep: true
})
onMounted(() => {
eventBus.on('global-theme-toggle', () => {
isShow.value = !isShow.value
})
})
const { copy, copied, isSupported } = useClipboard()
watch(copied, val => {
if (val) {
ElMessage.success('复制成功,请粘贴到 src/settings.custom.json 文件中!')
}
})
function handleCopy() {
copy(JSON.stringify(settings.value, null, 4))
}
</script>
<template>
<div>
<el-drawer v-model="isShow" title="应用配置" direction="rtl" :size="360">
<el-alert title="应用配置可实时预览效果,但仅是临时生效,要想真正作用于项目,可以点击下方的“复制配置”按钮,将配置粘贴到 src/settings.custom.json 中即可,或者也可在 src/settings.js 中直接修改默认配置。同时建议在生产环境隐藏应用配置功能。" type="error" :closable="false" />
<el-divider>颜色主题</el-divider>
<div class="color-scheme">
<div class="switch" :class="settings.app.colorScheme" @click="settings.app.colorScheme = settings.app.colorScheme === 'dark' ? 'light' : 'dark'">
<el-icon class="icon">
<svg-icon v-show="settings.app.colorScheme === 'light'" name="ep:sunny" />
<svg-icon v-show="settings.app.colorScheme === 'dark'" name="ep:moon" />
</el-icon>
</div>
</div>
<el-divider v-if="settingsStore.mode === 'pc'">导航栏模式</el-divider>
<div v-if="settingsStore.mode === 'pc'" class="menu-mode">
<el-tooltip content="侧边栏模式(含主导航)" placement="top" :show-after="500">
<div class="mode mode-side" :class="{'active': settings.menu.menuMode === 'side'}" @click="settings.menu.menuMode = 'side'">
<div class="mode-container" />
<el-icon>
<svg-icon name="ep:check" />
</el-icon>
</div>
</el-tooltip>
<el-tooltip content="顶部模式" placement="top" :show-after="500">
<div class="mode mode-head" :class="{'active': settings.menu.menuMode === 'head'}" @click="settings.menu.menuMode = 'head'">
<div class="mode-container" />
<el-icon>
<svg-icon name="ep:check" />
</el-icon>
</div>
</el-tooltip>
<el-tooltip content="侧边栏模式(不含主导航)" placement="top" :show-after="500">
<div class="mode mode-single" :class="{'active': settings.menu.menuMode === 'single'}" @click="settings.menu.menuMode = 'single'">
<div class="mode-container" />
<el-icon>
<svg-icon name="ep:check" />
</el-icon>
</div>
</el-tooltip>
</div>
<el-divider>导航栏</el-divider>
<div class="setting-item">
<div class="label">是否折叠</div>
<el-switch v-model="settings.menu.subMenuCollapse" />
</div>
<div class="setting-item">
<div class="label">
切换跳转
<el-tooltip content="开启该功能后,切换侧边栏时,页面自动跳转至该侧边栏导航下第一个路由地址" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.menu.switchMainMenuAndPageJump" :disabled="['single'].includes(settings.menu.menuMode)" />
</div>
<div class="setting-item">
<div class="label">
保持展开一个
<el-tooltip content="开启该功能后,侧边栏只保持一个子菜单的展开" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.menu.subMenuUniqueOpened" />
</div>
<el-divider>顶栏</el-divider>
<div class="setting-item">
<div class="label">是否固定</div>
<el-switch v-model="settings.topbar.fixed" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">折叠按钮</div>
<el-switch v-model="settings.topbar.enableSidebarCollapse" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">面包屑导航</div>
<el-switch v-model="settings.topbar.enableBreadcrumb" />
</div>
<div class="setting-item">
<div class="label">
导航栏搜索
<el-tooltip content="对导航栏进行快捷搜索" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.topbar.enableNavSearch" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">
全屏
<el-tooltip content="该功能使用场景极少,用户习惯于通过窗口“最大化”功能来扩大显示区域,以显示更多内容,并且使用 F11 键也可以进入全屏效果" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.topbar.enableFullscreen" />
</div>
<div class="setting-item">
<div class="label">
页面刷新
<el-tooltip content="开启时会阻止原生 F5 键刷新功能,并采用框架提供的刷新模式进行页面刷新" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.topbar.enablePageReload" />
</div>
<div class="setting-item">
<div class="label">
颜色主题
<el-tooltip content="开启后可在明亮/暗黑模式中切换" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.topbar.enableColorScheme" />
</div>
<el-divider>底部版权</el-divider>
<div class="setting-item">
<div class="label">是否启用</div>
<el-switch v-model="settings.copyright.enable" />
</div>
<div class="setting-item">
<div class="label">日期</div>
<el-input v-model="settings.copyright.dates" size="small" :disabled="!settings.copyright.enable" />
</div>
<div class="setting-item">
<div class="label">公司</div>
<el-input v-model="settings.copyright.company" size="small" :disabled="!settings.copyright.enable" />
</div>
<div class="setting-item">
<div class="label">网址</div>
<el-input v-model="settings.copyright.website" size="small" :disabled="!settings.copyright.enable" />
</div>
<div class="setting-item">
<div class="label">备案</div>
<el-input v-model="settings.copyright.beian" size="small" :disabled="!settings.copyright.enable" />
</div>
<el-divider>控制台</el-divider>
<div class="setting-item">
<div class="label">
是否开启
<el-tooltip content="该功能开启时,登录成功默认进入控制台页面,反之则默认进入导航栏里第一个导航页面" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.dashboard.enable" />
</div>
<div class="setting-item">
<div class="label">控制台名称</div>
<el-input v-model="settings.dashboard.title" size="small" />
</div>
<el-divider>其它</el-divider>
<div class="setting-item">
<div class="label">
组件尺寸
<el-tooltip content="全局设置 Element Plus 组件的默认尺寸大小" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-radio-group v-model="settings.app.elementSize" size="small">
<el-radio-button label="large">较大</el-radio-button>
<el-radio-button label="default">默认</el-radio-button>
<el-radio-button label="small">稍小</el-radio-button>
</el-radio-group>
</div>
<div class="setting-item">
<div class="label">是否启用权限</div>
<el-switch v-model="settings.app.enablePermission" />
</div>
<div class="setting-item">
<div class="label">
载入进度条
<el-tooltip content="该功能开启时,跳转路由会看到页面顶部有进度条" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.app.enableProgress" />
</div>
<div class="setting-item">
<div class="label">
动态标题
<el-tooltip content="该功能开启时,页面标题会显示当前路由标题,格式为“页面标题 - 网站名称”;关闭时则显示网站名称,网站名称在项目根目录下 .env.* 文件里配置" placement="top">
<el-icon>
<svg-icon name="ep:question-filled" />
</el-icon>
</el-tooltip>
</div>
<el-switch v-model="settings.app.enableDynamicTitle" />
</div>
<template v-if="isSupported" #footer>
<el-button type="primary" @click="handleCopy">
<template #icon>
<el-icon>
<svg-icon name="ep:document-copy" />
</el-icon>
</template>
复制配置
</el-button>
</template>
</el-drawer>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-drawer__header) {
margin-bottom: initial;
padding-bottom: 20px;
border-bottom: 1px solid var(--el-border-color);
}
:deep(.el-drawer__footer) {
padding: 20px;
border-top: 1px solid var(--el-border-color);
transition: var(--el-transition-border);
.el-button {
width: 100%;
}
}
:deep(.el-divider) {
margin: 36px 0 24px;
}
.color-scheme {
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 10px;
$width: 50px;
.switch {
width: $width;
height: 30px;
border-radius: 15px;
cursor: pointer;
background-color: var(--el-fill-color-darker);
transition: background-color 0.3s;
&.dark {
.icon {
margin-left: calc($width - 24px - 3px);
}
}
.icon {
margin: 3px;
padding: 5px;
font-size: 24px;
border-radius: 50%;
background-color: var(--el-fill-color-lighter);
transition: margin-left 0.3s, background-color 0.3s;
}
}
}
.menu-mode {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
padding-bottom: 10px;
.mode {
position: relative;
width: 80px;
height: 55px;
margin: 10px;
border-radius: 5px;
overflow: hidden;
cursor: pointer;
background-color: var(--g-app-bg);
box-shadow: 0 0 5px 1px var(--el-border-color-lighter);
transition: 0.2s;
&:hover {
box-shadow: 0 0 5px 1px var(--el-border-color-darker);
}
&.active {
box-shadow: 0 0 0 2px var(--el-color-primary);
}
&::before,
&::after,
.mode-container {
pointer-events: none;
position: absolute;
border-radius: 3px;
}
.mode-container::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
background-color: var(--g-sub-sidebar-menu-active-bg);
opacity: 0.2;
}
&-side {
&::before {
content: "";
top: 5px;
left: 5px;
bottom: 5px;
width: 10px;
background-color: var(--g-sub-sidebar-menu-active-bg);
}
&::after {
content: "";
top: 5px;
left: 20px;
bottom: 5px;
width: 15px;
background-color: var(--g-sub-sidebar-menu-active-bg);
opacity: 0.5;
}
.mode-container {
top: 5px;
left: 40px;
right: 5px;
bottom: 5px;
border: 1px dashed var(--g-sub-sidebar-menu-active-bg);
}
}
&-head {
&::before {
content: "";
top: 5px;
left: 5px;
right: 5px;
height: 10px;
background-color: var(--g-sub-sidebar-menu-active-bg);
}
&::after {
content: "";
top: 20px;
left: 5px;
bottom: 5px;
width: 15px;
background-color: var(--g-sub-sidebar-menu-active-bg);
opacity: 0.5;
}
.mode-container {
top: 20px;
left: 25px;
right: 5px;
bottom: 5px;
border: 1px dashed var(--g-sub-sidebar-menu-active-bg);
}
}
&-single {
&::before {
content: "";
position: absolute;
top: 5px;
left: 5px;
bottom: 5px;
width: 15px;
background-color: var(--g-sub-sidebar-menu-active-bg);
opacity: 0.5;
}
.mode-container {
top: 5px;
left: 25px;
right: 5px;
bottom: 5px;
border: 1px dashed var(--g-sub-sidebar-menu-active-bg);
}
}
i {
position: absolute;
right: 10px;
bottom: 10px;
display: none;
}
&.active i {
display: block;
color: var(--el-color-primary);
}
}
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 5px 0;
padding: 5px 10px;
border-radius: 5px;
transition: all 0.3s;
&:hover {
background: var(--el-fill-color);
}
.label {
font-size: 14px;
color: var(--el-text-color-regular);
display: flex;
align-items: center;
i {
margin-left: 4px;
font-size: 17px;
color: var(--el-color-warning);
cursor: help;
}
}
.el-switch {
height: auto;
}
.el-input {
width: 150px;
}
}
</style>
-170
View File
@@ -1,170 +0,0 @@
<script setup name="Header">
import Logo from '../Logo/index.vue'
import Tools from '../Tools/index.vue'
import useSettingsStore from '@/store/modules/settings'
import useMenuStore from '@/store/modules/menu'
import { useMenu } from '@/utils/composables'
const settingsStore = useSettingsStore()
const menuStore = useMenuStore()
const navRef = ref()
// 顶部模式鼠标滚动
function handlerMouserScroll(event) {
navRef.value.scrollBy({
left: (event.deltaY || event.detail) > 0 ? 50 : -50
})
}
</script>
<template>
<transition name="header">
<header v-if="settingsStore.mode === 'pc' && settingsStore.menu.menuMode === 'head'">
<div class="header-container">
<div class="main">
<Logo />
<!-- 顶部模式 -->
<div ref="navRef" class="nav" @wheel.prevent="handlerMouserScroll">
<template v-for="(item, index) in menuStore.allMenus" :key="index">
<div v-if="item.children && item.children.length !== 0" class="item-container" :class="{'active': index == menuStore.actived}">
<div class="item" @click="useMenu().switchTo(index)">
<el-icon>
<svg-icon v-if="item.meta.icon" :name="item.meta.icon" />
</el-icon>
<span v-if="item.meta.title">{{ item.meta.title }}</span>
</div>
</div>
</template>
</div>
</div>
<Tools />
</div>
</header>
</transition>
</template>
<style lang="scss" scoped>
header {
position: fixed;
z-index: 1000;
top: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 0 20px;
height: var(--g-header-height);
color: var(--g-header-color);
background-color: var(--g-header-bg);
transition: background-color 0.3s, var(--el-transition-color);
.header-container {
width: var(--g-header-width);
height: 100%;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
.main {
flex: 1;
display: flex;
flex-wrap: wrap;
align-items: center;
height: 100%;
}
}
@media screen and (max-width: var(--g-header-width)) {
.header-container {
width: 100%;
}
}
:deep(.title) {
position: relative;
width: inherit;
height: inherit;
padding: inherit;
background-color: inherit;
.logo {
width: 50px;
height: 50px;
}
span {
font-size: 24px;
letter-spacing: 1px;
color: var(--g-header-color);
}
}
.nav {
flex: 1;
display: flex;
width: 0;
height: 100%;
margin: 0 30px;
padding: 0 20px;
overflow-x: auto;
mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent);
-webkit-mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent);
// firefox隐藏滚动条
scrollbar-width: none;
// chrome隐藏滚动条
&::-webkit-scrollbar {
display: none;
}
.item-container {
position: relative;
display: flex;
width: initial;
.item {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0 5px;
width: 80px;
height: 100%;
cursor: pointer;
color: var(--g-header-menu-color);
background-color: var(--g-header-bg);
transition: background-color 0.3s, var(--el-transition-color);
&:hover {
color: var(--g-header-menu-hover-color);
background-color: var(--g-header-menu-hover-bg);
}
.el-icon {
font-size: 24px;
vertical-align: middle;
}
span {
text-align: center;
vertical-align: middle;
word-break: break-all;
@include text-overflow(1, false);
}
}
&.active .item {
color: var(--g-header-menu-active-color);
background-color: var(--g-header-menu-active-bg);
}
}
}
:deep(.tools) {
padding: 0;
.buttons .item .el-icon {
color: var(--g-header-color);
}
.user-container {
font-size: 16px;
color: var(--g-header-color);
}
}
}
// 头部动画
.header-enter-active,
.header-leave-active {
transition: transform 0.3s;
}
.header-enter-from,
.header-leave-to {
transform: translateY(calc(var(--g-header-height) * -1));
}
</style>
-68
View File
@@ -1,68 +0,0 @@
<script setup name="Logo">
import imgLogo from '@/assets/images/logo.png'
import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore()
defineProps({
showLogo: {
type: Boolean,
default: true
},
showTitle: {
type: Boolean,
default: true
}
})
const title = ref(import.meta.env.VITE_APP_TITLE)
const logo = ref(imgLogo)
const to = computed(() => {
let rtn = {}
if (settingsStore.dashboard.enable) {
rtn.name = 'dashboard'
}
return rtn
})
</script>
<template>
<router-link :to="to" class="title" :class="{'is-link': settingsStore.dashboard.enable}" :title="title">
<img v-if="showLogo" :src="logo" class="logo">
<span v-if="showTitle">{{ title }}</span>
</router-link>
</template>
<style lang="scss" scoped>
.title {
position: fixed;
z-index: 1000;
top: 0;
width: inherit;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: center;
height: var(--g-sidebar-logo-height);
text-align: center;
overflow: hidden;
text-decoration: none;
&.is-link {
cursor: pointer;
}
.logo {
width: 30px;
height: 30px;
& + span {
margin-left: 10px;
}
}
span {
display: block;
font-weight: bold;
color: #fff;
@include text-overflow;
}
}
</style>
-101
View File
@@ -1,101 +0,0 @@
<script setup name="MainSidebar">
import Logo from '../Logo/index.vue'
import useSettingsStore from '@/store/modules/settings'
import useMenuStore from '@/store/modules/menu'
import { useMenu } from '@/utils/composables'
const settingsStore = useSettingsStore()
const menuStore = useMenuStore()
</script>
<template>
<transition name="main-sidebar">
<div v-if="settingsStore.menu.menuMode === 'side' || (settingsStore.mode === 'mobile' && settingsStore.menu.menuMode !== 'single')" class="main-sidebar-container">
<Logo :show-title="false" class="sidebar-logo" />
<!-- 侧边栏模式(含主导航) -->
<div class="nav">
<template v-for="(item, index) in menuStore.allMenus">
<div
v-if="item.children && item.children.length !== 0" :key="index" :class="{
'item': true,
'active': index === menuStore.actived
}" :title="item.meta.title" @click="useMenu().switchTo(index)"
>
<el-icon>
<svg-icon v-if="item.meta.icon" :name="item.meta.icon" />
</el-icon>
<span>{{ item.meta.title }}</span>
</div>
</template>
</div>
</div>
</transition>
</template>
<style lang="scss" scoped>
.main-sidebar-container {
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
// firefox隐藏滚动条
scrollbar-width: none;
// chrome隐藏滚动条
&::-webkit-scrollbar {
display: none;
}
position: relative;
z-index: 1;
width: var(--g-main-sidebar-width);
color: var(--g-main-sidebar-menu-color);
background-color: var(--g-main-sidebar-bg);
transition: background-color 0.3s, var(--el-transition-color);
.sidebar-logo {
transition: 0.3s;
background-color: var(--g-main-sidebar-bg);
}
.nav {
width: inherit;
padding-top: var(--g-sidebar-logo-height);
.item {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
height: 60px;
padding: 0 5px;
cursor: pointer;
color: var(--g-main-sidebar-menu-color);
background-color: var(--g-main-sidebar-bg);
transition: background-color 0.3s, var(--el-transition-color);
&:hover {
color: var(--g-main-sidebar-menu-hover-color);
background-color: var(--g-main-sidebar-menu-hover-bg);
}
&.active {
color: var(--g-main-sidebar-menu-active-color);
background-color: var(--g-main-sidebar-menu-active-bg);
}
.el-icon {
margin: 0 auto;
font-size: 24px;
}
span {
text-align: center;
font-size: 14px;
@include text-overflow(1, false);
}
}
}
}
// 主侧边栏动画
.main-sidebar-enter-active,
.main-sidebar-leave-active {
transition: transform 0.3s;
}
.main-sidebar-enter-from,
.main-sidebar-leave-to {
transform: translateX(calc(var(--g-main-sidebar-width) * -1));
}
</style>
-398
View File
@@ -1,398 +0,0 @@
<script setup name="Search">
import hotkeys from 'hotkeys-js'
import { deepClone, isExternalLink } from '@/utils'
import eventBus from '@/utils/eventBus'
import useSettingsStore from '@/store/modules/settings'
import useRouteStore from '@/store/modules/route'
import useMenuStore from '@/store/modules/menu'
const settingsStore = useSettingsStore()
const routeStore = useRouteStore()
const menuStore = useMenuStore()
const isShow = ref(false)
const searchInput = ref('')
const sourceList = ref([])
const actived = ref(-1)
const inputRef = ref()
const resultRef = ref()
const resultItemRef = ref([])
const setResultItemRef = el => resultItemRef.value.push(el)
onBeforeUpdate(() => {
resultItemRef.value = []
})
const resultList = computed(() => {
let result = []
result = sourceList.value.filter(item => {
let flag = false
if (item.title.indexOf(searchInput.value) >= 0) {
flag = true
}
if (item.path.indexOf(searchInput.value) >= 0) {
flag = true
}
if (item.breadcrumb.some(b => b.indexOf(searchInput.value) >= 0)) {
flag = true
}
return flag
})
return result
})
watch(() => isShow.value, val => {
if (val) {
document.querySelector('body').classList.add('hidden')
resultRef.value.scrollTop = 0
// 当搜索显示的时候绑定上、下、回车快捷键,隐藏的时候再解绑。另外当 input 处于 focus 状态时,采用 vue 来绑定键盘事件
hotkeys('up', keyUp)
hotkeys('down', keyDown)
hotkeys('enter', keyEnter)
setTimeout(() => {
inputRef.value.focus()
}, 500)
} else {
document.querySelector('body').classList.remove('hidden')
hotkeys.unbind('up', keyUp)
hotkeys.unbind('down', keyDown)
hotkeys.unbind('enter', keyEnter)
setTimeout(() => {
searchInput.value = ''
actived.value = -1
}, 500)
}
})
watch(() => resultList.value, () => {
actived.value = -1
handleScroll()
})
onMounted(() => {
eventBus.on('global-search-toggle', () => {
isShow.value = !isShow.value
})
hotkeys('alt+s', e => {
if (settingsStore.topbar.enableNavSearch) {
e.preventDefault()
isShow.value = true
}
})
hotkeys('esc', e => {
if (settingsStore.topbar.enableNavSearch) {
e.preventDefault()
isShow.value = false
}
})
if (settingsStore.app.routeBaseOn !== 'filesystem') {
routeStore.routes.map(item => {
getSourceList(item.children)
})
} else {
menuStore.menus.map(item => {
getSourceList(item.children)
})
}
})
function hasChildren(item) {
let flag = true
if (item.children) {
if (item.children.every(i => i.meta.sidebar === false)) {
flag = false
}
} else {
flag = false
}
return flag
}
function getSourceList(arr) {
arr.map(item => {
if (item.meta.sidebar !== false) {
if (hasChildren(item)) {
let baseBreadcrumb = item.meta.baseBreadcrumb ? deepClone(item.meta.baseBreadcrumb) : []
baseBreadcrumb.push(item.meta.title)
let child = deepClone(item.children)
child.map(c => {
c.meta.baseIcon = item.meta.icon || item.meta.baseIcon
c.meta.baseBreadcrumb = baseBreadcrumb
c.meta.basePath = item.meta.basePath ? [item.meta.basePath, item.path].join('/') : item.path
})
getSourceList(child)
} else {
let breadcrumb = []
if (item.meta.baseBreadcrumb) {
breadcrumb = deepClone(item.meta.baseBreadcrumb)
}
breadcrumb.push(item.meta.title)
let path = ''
if (isExternalLink(item.path)) {
path = item.path
} else {
path = item.meta.basePath ? [item.meta.basePath, item.path].join('/') : item.path
}
sourceList.value.push({
icon: item.meta.icon || item.meta.baseIcon,
title: item.meta.title,
i18n: item.meta.i18n,
breadcrumb: breadcrumb,
path: path
})
}
}
})
}
function keyUp() {
if (resultList.value.length) {
actived.value -= 1
if (actived.value < 0) {
actived.value = resultList.value.length - 1
}
handleScroll()
}
}
function keyDown() {
if (resultList.value.length) {
actived.value += 1
if (actived.value > resultList.value.length - 1) {
actived.value = 0
}
handleScroll()
}
}
function keyEnter() {
if (actived.value !== -1) {
resultItemRef.value[actived.value].click()
}
}
function handleScroll() {
let scrollTo = 0
if (actived.value !== -1) {
scrollTo = resultRef.value.scrollTop
const activedOffsetTop = resultItemRef.value[actived.value].offsetTop
const activedClientHeight = resultItemRef.value[actived.value].clientHeight
const searchScrollTop = resultRef.value.scrollTop
const searchClientHeight = resultRef.value.clientHeight
if (activedOffsetTop + activedClientHeight > searchScrollTop + searchClientHeight) {
scrollTo = activedOffsetTop + activedClientHeight - searchClientHeight
} else if (activedOffsetTop <= searchScrollTop) {
scrollTo = activedOffsetTop
}
}
resultRef.value.scrollTo({
top: scrollTo
})
}
</script>
<template>
<div id="search" :class="{'searching': isShow}" @click="isShow && eventBus.emit('global-search-toggle')">
<div class="container">
<div class="search-box" @click.stop>
<el-input ref="inputRef" v-model="searchInput" placeholder="搜索页面,支持标题、URL模糊查询" clearable @keydown.esc="eventBus.emit('global-search-toggle')" @keydown.up.prevent="keyUp" @keydown.down.prevent="keyDown" @keydown.enter.prevent="keyEnter">
<template #prefix>
<el-icon>
<svg-icon name="ep:search" />
</el-icon>
</template>
</el-input>
<div v-if="settingsStore.mode === 'pc'" class="tips">
<div class="tip">
<span>Alt</span>+<span>S</span>
唤醒搜索面板
</div>
<div class="tip">
<span>
<el-icon>
<svg-icon name="search-up" />
</el-icon>
</span>
<span>
<el-icon>
<svg-icon name="search-down" />
</el-icon>
</span>
切换搜索结果
</div>
<div class="tip">
<span>
<el-icon>
<svg-icon name="search-enter" />
</el-icon>
</span>
访问页面
</div>
<div class="tip">
<span>ESC</span>
退出
</div>
</div>
</div>
<div ref="resultRef" class="result" :class="{'mobile': settingsStore.mode === 'mobile'}">
<router-link v-for="(item, index) in resultList" :key="item.path" v-slot="{ href, navigate }" custom :to="isShow ? item.path : ''">
<a :ref="setResultItemRef" :href="isExternalLink(item.path) ? item.path : href" class="item" :class="{'actived': index === actived}" :target="isExternalLink(item.path) ? '_blank' : '_self'" @click="navigate" @mouseover="actived = index">
<el-icon class="icon">
<svg-icon v-if="item.icon" :name="item.icon" />
</el-icon>
<div class="info">
<div class="title">
{{ item.title }}
</div>
<div class="breadcrumb">
<span v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex">
{{ bc }}
<el-icon>
<svg-icon name="ep:arrow-right" />
</el-icon>
</span>
</div>
<div class="path">{{ item.path }}</div>
</div>
</a>
</router-link>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
#search {
position: fixed;
z-index: 2000;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--el-overlay-color-lighter);
backdrop-filter: blur(10px);
transition: all 0.2s;
transform: translateZ(0);
opacity: 0;
visibility: hidden;
&.searching {
opacity: 1;
visibility: visible;
.container {
transform: initial;
filter: initial;
}
}
.container {
display: flex;
flex-direction: column;
max-width: 800px;
height: 100%;
margin: 0 auto;
transition: all 0.2s;
transform: scale(1.1);
filter: blur(10px);
.search-box {
margin: 50px 20px 40px;
:deep(.el-input__inner) {
height: 52px;
line-height: 52px;
}
:deep(.el-input__icon) {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.tips {
display: flex;
align-items: center;
justify-content: space-evenly;
margin-top: 20px;
line-height: 24px;
font-size: 14px;
color: #fff;
span {
margin: 0 5px;
padding: 3px 8px 5px;
border-radius: 5px;
font-size: 12px;
font-weight: bold;
color: var(--el-text-color-primary);
background-color: var(--el-fill-color);
box-shadow: inset 0 -2px var(--el-fill-color-lighter), inset 0 0 1px 1px var(--el-fill-color-darker);
}
}
}
.result {
position: relative;
margin: 0 20px;
max-height: calc(100% - 250px);
border-radius: 5px;
overflow: auto;
background-color: var(--el-bg-color);
box-shadow: 0 0 0 1px var(--el-border-color-darker);
&.mobile {
max-height: calc(100% - 200px);
}
.item {
display: flex;
align-items: center;
text-decoration: none;
cursor: pointer;
transition: all 0.3s;
&.actived {
background-color: var(--el-bg-color-page);
.icon {
color: var(--el-color-primary);
transform: scale(1.2);
}
.info {
border-left-color: var(--el-border-color);
.title {
color: var(--el-text-color-primary);
}
.breadcrumb,
.path {
color: var(--el-text-color-regular);
}
}
}
.icon {
flex: 0 0 66px;
text-align: center;
color: var(--el-color-info);
font-size: 20px;
transition: all 0.3s;
}
.info {
flex: 1;
height: 70px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-left: 1px solid var(--el-border-color-lighter);
padding: 5px 10px 7px;
transition: all 0.3s;
@include text-overflow(1, true);
.title {
font-size: 18px;
font-weight: bold;
color: var(--el-text-color-regular);
@include text-overflow(1, true);
}
.breadcrumb,
.path {
font-size: 12px;
color: var(--el-text-color-secondary);
transition: all 0.3s;
@include text-overflow(1, true);
}
.breadcrumb {
span {
margin-right: 5px;
&:last-child i {
display: none;
}
}
}
}
}
}
}
}
</style>
-147
View File
@@ -1,147 +0,0 @@
<script setup name="SidebarItem">
import SidebarItem from './index.vue'
import { isExternalLink, resolveRoutePath } from '@/utils'
const props = defineProps({
item: {
type: Object,
required: true
},
basePath: {
type: String,
default: ''
}
})
const hasChildren = computed(() => {
let flag = true
if (props.item.children) {
if (props.item.children.every(item => item.meta.sidebar === false)) {
flag = false
}
} else {
flag = false
}
return flag
})
</script>
<template>
<div class="sidebar-item">
<el-sub-menu v-if="item.path == undefined" :title="item.meta.title" :index="JSON.stringify(item)">
<template #title>
<el-icon v-if="item.meta.icon" class="title-icon">
<svg-icon :name="item.meta.icon" />
</el-icon>
<span class="title">{{ item.meta.title }}</span>
</template>
<template v-for="route in item.children">
<SidebarItem v-if="route.meta.sidebar !== false" :key="route.path" :item="route" />
</template>
</el-sub-menu>
<router-link v-else-if="!hasChildren" v-slot="{ href, navigate, isActive, isExactActive }" custom :to="resolveRoutePath(basePath, item.path)">
<a :href="isExternalLink(resolveRoutePath(basePath, item.path)) ? resolveRoutePath(basePath, item.path) : href" :class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']" :target="isExternalLink(resolveRoutePath(basePath, item.path)) ? '_blank' : '_self'" @click="navigate">
<el-menu-item :title="item.meta.title" :index="resolveRoutePath(basePath, item.path)">
<el-icon v-if="item.meta.icon" class="title-icon">
<svg-icon :name="item.meta.icon" />
</el-icon>
<span class="title">{{ item.meta.title }}</span>
</el-menu-item>
</a>
</router-link>
<el-sub-menu v-else :title="item.meta.title" :index="resolveRoutePath(basePath, item.path)">
<template #title>
<el-icon v-if="item.meta.icon" class="title-icon">
<svg-icon :name="item.meta.icon" />
</el-icon>
<span class="title">{{ item.meta.title }}</span>
</template>
<template v-for="route in item.children">
<SidebarItem v-if="route.meta.sidebar !== false" :key="route.path" :item="route" :base-path="resolveRoutePath(basePath, item.path)" />
</template>
</el-sub-menu>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-menu-item),
:deep(.el-menu-item span.title),
:deep(.el-sub-menu__title),
:deep(.el-sub-menu__title span.title) {
flex: 1;
vertical-align: inherit;
@include text-overflow;
}
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
display: flex;
align-items: center;
justify-content: center;
}
:deep(.el-sub-menu),
:deep(.el-menu-item) {
.title-icon {
width: 20px;
font-size: 20px;
vertical-align: -0.25em;
transition: transform 0.3s;
color: unset;
}
.title-icon + .title {
margin-left: 10px;
}
&:hover > .title-icon,
.el-sub-menu__title:hover > .title-icon {
transform: scale(1.2);
}
}
a {
cursor: pointer;
color: inherit;
text-decoration: none;
}
.el-sub-menu__title {
> .badge {
&-dot {
right: 40px;
}
&-text {
right: 40px;
}
}
}
</style>
<style lang="scss">
.el-menu--inline {
background-color: var(--g-sub-sidebar-menu-bg) !important;
.el-menu-item,
.el-sub-menu > .el-sub-menu__title {
color: var(--g-sub-sidebar-menu-color);
background-color: var(--g-sub-sidebar-menu-bg) !important;
&:hover {
color: var(--g-sub-sidebar-menu-hover-color) !important;
background-color: var(--g-sub-sidebar-menu-hover-bg) !important;
}
}
}
.el-menu-item,
.el-sub-menu__title {
color: var(--g-sub-sidebar-menu-color) !important;
background: transparent !important;
&:hover {
color: var(--g-sub-sidebar-menu-hover-color) !important;
background-color: var(--g-sub-sidebar-menu-hover-bg) !important;
}
}
.el-menu-item.is-active,
.el-menu--collapse .el-sub-menu.is-active > .el-sub-menu__title,
.el-sub-menu .el-menu--inline .el-menu-item.is-active {
color: var(--g-sub-sidebar-menu-active-color) !important;
background-color: var(--g-sub-sidebar-menu-active-bg) !important;
.icon,
.el-sub-menu__icon-arrow {
color: unset;
}
}
</style>
-140
View File
@@ -1,140 +0,0 @@
<script setup name="SubSidebar">
import Logo from '../Logo/index.vue'
import SidebarItem from '../SidebarItem/index.vue'
import useSettingsStore from '@/store/modules/settings'
import useMenuStore from '@/store/modules/menu'
const settingsStore = useSettingsStore()
const menuStore = useMenuStore()
const sidebarScrollTop = ref(0)
function onSidebarScroll(e) {
sidebarScrollTop.value = e.target.scrollTop
}
</script>
<template>
<div v-if="['side', 'head', 'single'].includes(settingsStore.menu.menuMode) || settingsStore.mode === 'mobile'" class="sub-sidebar-container" :class="{'is-collapse': settingsStore.mode === 'pc' && settingsStore.menu.subMenuCollapse}" @scroll="onSidebarScroll">
<Logo
:show-logo="settingsStore.menu.menuMode === 'single'" :class="{
'sidebar-logo': true,
'sidebar-logo-bg': settingsStore.menu.menuMode === 'single',
'shadow': sidebarScrollTop
}"
/>
<!-- 侧边栏模式(无主导航) -->
<el-menu
:unique-opened="settingsStore.menu.subMenuUniqueOpened" :default-openeds="menuStore.defaultOpenedPaths" :default-active="$route.meta.activeMenu || $route.path" :collapse="settingsStore.mode === 'pc' && settingsStore.menu.subMenuCollapse" :collapse-transition="false" :class="{
'is-collapse-without-logo': settingsStore.menu.menuMode !== 'single' && settingsStore.menu.subMenuCollapse
}"
>
<transition-group name="sub-sidebar">
<template v-for="(route, index) in menuStore.sidebarMenus">
<SidebarItem v-if="route.meta.sidebar !== false" :key="route.path || index" :item="route" :base-path="route.path" />
</template>
</transition-group>
</el-menu>
</div>
</template>
<style lang="scss" scoped>
.sub-sidebar-container {
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
// firefox隐藏滚动条
scrollbar-width: none;
// chrome隐藏滚动条
&::-webkit-scrollbar {
display: none;
}
width: var(--g-sub-sidebar-width);
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: var(--g-sub-sidebar-bg);
box-shadow: 10px 0 10px -10px var(--g-box-shadow-color);
transition: background-color 0.3s, var(--el-transition-box-shadow), left 0.3s, width 0.3s;
&.is-collapse {
width: 64px;
.sidebar-logo {
&:not(.sidebar-logo-bg) {
display: none;
}
:deep(span) {
display: none;
}
}
}
.sidebar-logo {
transition: box-shadow 0.2s, background-color 0.3s, color 0.3s;
background-color: var(--g-sub-sidebar-bg);
&:not(.sidebar-logo-bg) {
:deep(span) {
color: var(--g-sub-sidebar-menu-color);
}
}
&.sidebar-logo-bg {
background-color: var(--g-main-sidebar-bg);
}
&.shadow {
box-shadow: 0 10px 10px -10px var(--g-box-shadow-color);
}
}
.el-menu {
border-right: 0;
padding-top: var(--g-sidebar-logo-height);
transition: border-color 0.3s, background-color 0.3s, color 0.3s, padding-top 0.3s;
background-color: var(--g-sub-sidebar-bg);
&:not(.el-menu--collapse) {
width: inherit;
}
&.is-collapse-without-logo {
padding-top: 0;
}
&.el-menu--collapse {
:deep(.title-icon) {
margin-right: 0;
}
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
span,
.el-sub-menu__icon-arrow {
display: none;
}
}
}
&.menu-radius:not(.el-menu--collapse) {
.sidebar-item {
padding: 0 10px;
&:first-child {
padding-top: 10px;
}
&:last-child {
padding-bottom: 10px;
}
}
:deep(.el-menu--inline),
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
border-radius: 10px;
}
}
}
}
// 次侧边栏动画
.sub-sidebar-enter-active {
transition: opacity 0.3s, transform 0.3s;
}
.sub-sidebar-enter-from,
.sub-sidebar-leave-active {
opacity: 0;
transform: translateY(30px) skewY(10deg);
}
.sub-sidebar-leave-active {
position: absolute;
}
</style>
-168
View File
@@ -1,168 +0,0 @@
<script setup name="Tools">
import { useFullscreen } from '@vueuse/core'
import eventBus from '@/utils/eventBus'
import { useMainPage } from '@/utils/composables'
import useSettingsStore from '@/store/modules/settings'
import useUserStore from '@/store/modules/user'
const router = useRouter()
const settingsStore = useSettingsStore()
const userStore = useUserStore()
const { isFullscreen, toggle } = useFullscreen()
function userCommand(command) {
switch (command) {
case 'dashboard':
router.push({
name: 'dashboard'
})
break
case 'setting':
router.push({
name: 'personalSetting'
})
break
case 'logout':
userStore.logout().then(() => {
router.push({
name: 'login'
})
})
break
}
}
function pro() {
window.open('https://hooray.gitee.io/fantastic-admin-pro-example/', 'top')
}
</script>
<template>
<div class="tools">
<div class="buttons">
<span v-if="settingsStore.mode == 'pc'" class="item item-pro" @click="pro">
<el-icon>
<svg-icon name="pro" />
</el-icon>
<span class="title">查看专业版</span>
</span>
<span v-if="settingsStore.topbar.enableNavSearch" class="item" @click="eventBus.emit('global-search-toggle')">
<el-icon>
<svg-icon name="ep:search" />
</el-icon>
</span>
<span v-if="settingsStore.mode === 'pc' && settingsStore.topbar.enableFullscreen" class="item" @click="toggle">
<el-icon>
<svg-icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" />
</el-icon>
</span>
<span v-if="settingsStore.topbar.enablePageReload" class="item" @click="useMainPage().reload()">
<el-icon>
<svg-icon name="ep:refresh-right" />
</el-icon>
</span>
<span v-if="settingsStore.topbar.enableColorScheme" class="item" @click="settingsStore.setColorScheme(settingsStore.app.colorScheme === 'dark' ? 'light' : 'dark')">
<el-icon>
<svg-icon v-show="settingsStore.app.colorScheme === 'light'" name="ep:sunny" />
<svg-icon v-show="settingsStore.app.colorScheme === 'dark'" name="ep:moon" />
</el-icon>
</span>
<span v-if="settingsStore.topbar.enableAppSetting" class="item" @click="eventBus.emit('global-theme-toggle')">
<el-icon>
<svg-icon name="ep:setting" />
</el-icon>
</span>
</div>
<el-dropdown class="user-container" size="default" @command="userCommand">
<div class="user-wrapper">
<el-avatar size="small">
<el-icon>
<svg-icon name="ep:user-filled" />
</el-icon>
</el-avatar>
{{ userStore.account }}
<el-icon>
<svg-icon name="ep:caret-bottom" />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu class="user-dropdown">
<el-dropdown-item v-if="settingsStore.dashboard.enable" command="dashboard">控制台</el-dropdown-item>
<el-dropdown-item command="setting">个人设置</el-dropdown-item>
<el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<style lang="scss" scoped>
.tools {
display: flex;
align-items: center;
padding: 0 20px;
white-space: nowrap;
.buttons {
margin-right: 20px;
.item {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
width: 34px;
cursor: pointer;
vertical-align: middle;
.el-icon {
color: var(--el-text-color-primary);
transition: var(--el-transition-color);
}
}
.item-pro {
display: inline-block;
width: auto;
padding: 0 10px;
transform-origin: right center;
animation: pro-text 3s ease-out infinite;
@keyframes pro-text {
0%,
20% {
transform: scale(1);
}
50%,
70% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
.el-icon {
vertical-align: middle;
}
.title {
padding-left: 5px;
font-weight: bold;
font-size: 14px;
background-image: linear-gradient(to right, #ffa237, #fc455d);
/* stylelint-disable-next-line property-no-vendor-prefix */
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
}
}
:deep(.user-container) {
display: inline-block;
height: 24px;
line-height: 24px;
cursor: pointer;
.user-wrapper {
.el-avatar {
vertical-align: middle;
margin-top: -2px;
margin-right: 4px;
}
}
}
</style>
-142
View File
@@ -1,142 +0,0 @@
<script setup name="Topbar">
import { compile } from 'path-to-regexp'
import useSettingsStore from '@/store/modules/settings'
import Tools from '../Tools/index.vue'
const route = useRoute()
const settingsStore = useSettingsStore()
const enableSidebarCollapse = computed(() => {
return settingsStore.mode === 'mobile' || (
['side', 'head', 'single'].includes(settingsStore.menu.menuMode) &&
settingsStore.topbar.enableSidebarCollapse
)
})
const breadcrumbList = computed(() => {
let breadcrumbList = []
if (settingsStore.dashboard.enable) {
breadcrumbList.push({
path: '/dashboard',
title: settingsStore.dashboard.title
})
}
if (route.meta.breadcrumbNeste) {
breadcrumbList.push(...route.meta.breadcrumbNeste.filter(item => item.hide === false))
}
return breadcrumbList
})
const scrollTop = ref(0)
onMounted(() => {
window.addEventListener('scroll', onScroll)
})
onUnmounted(() => {
window.removeEventListener('scroll', onScroll)
})
function onScroll() {
scrollTop.value = document.documentElement.scrollTop || document.body.scrollTop
}
function pathCompile(path) {
let toPath = compile(path)
return toPath(route.params)
}
</script>
<template>
<div
class="topbar-container" :class="{
'fixed': settingsStore.topbar.fixed,
'shadow': scrollTop
}" data-fixed-calc-width
>
<div class="left-box">
<div v-if="enableSidebarCollapse" class="sidebar-collapse" :class="{'is-collapse': settingsStore.menu.subMenuCollapse}" @click="settingsStore.toggleSidebarCollapse()">
<el-icon>
<svg-icon name="toolbar-collapse" />
</el-icon>
</div>
<el-breadcrumb v-if="settingsStore.topbar.enableBreadcrumb && settingsStore.mode === 'pc' && settingsStore.app.routeBaseOn !== 'filesystem'">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="item.path" :to="index < breadcrumbList.length - 1 ? pathCompile(item.path) : ''">
{{ item.title }}
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
<Tools />
</div>
</template>
<style lang="scss" scoped>
.topbar-container {
position: absolute;
z-index: 999;
top: 0;
display: flex;
align-items: center;
justify-content: space-between;
height: var(--g-topbar-height);
background-color: var(--g-toolbar-bg);
transition: width 0.3s, top 0.3s, transform 0.3s, background-color 0.3s, var(--el-transition-box-shadow);
&.fixed {
position: fixed;
&.shadow {
box-shadow: 0 10px 10px -10px var(--g-box-shadow-color);
}
}
.left-box {
display: flex;
align-items: center;
padding-right: 50px;
overflow: hidden;
-webkit-mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 50px), transparent);
mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 50px), transparent);
.sidebar-collapse {
display: flex;
align-items: center;
padding: 0 20px;
height: 50px;
cursor: pointer;
.el-icon {
color: var(--el-text-color-primary);
transition: var(--el-transition-color), var(--el-transition-md-fade);
}
&:hover .el-icon {
color: var(--el-color-primary);
}
&.is-collapse .el-icon {
transform: rotateZ(-180deg);
}
& + .el-breadcrumb {
margin-left: 0;
}
}
:deep(.el-breadcrumb) {
margin-left: 20px;
white-space: nowrap;
.el-breadcrumb__item {
display: inline-block;
float: none;
span {
font-weight: normal;
}
&:last-child span {
color: #97a8be;
}
}
}
}
}
// 面包屑动画
.breadcrumb-enter-active {
transition: all 0.25s;
}
.breadcrumb-enter-from,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(30px) skewX(-50deg);
}
</style>

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