init
|
@ -0,0 +1,15 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
# 表示所有文件适用
|
||||
[*]
|
||||
charset = utf-8 # 设置文件字符集为 utf-8
|
||||
end_of_line = lf # 控制换行类型(lf | cr | crlf)
|
||||
indent_style = space # 缩进风格(tab | space)
|
||||
indent_size = 2 # 缩进大小
|
||||
insert_final_newline = true # 始终在文件末尾插入一个新行
|
||||
|
||||
# 表示仅 md 文件适用以下规则
|
||||
[*.md]
|
||||
max_line_length = off # 关闭最大行长度限制
|
||||
trim_trailing_whitespace = false # 关闭末尾空格修剪
|
|
@ -0,0 +1,13 @@
|
|||
# 应用端口
|
||||
VITE_APP_PORT = 3000
|
||||
|
||||
# 代理前缀
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
VITE_APP_API_URL = http://vapi.youlai.tech
|
||||
# 线上接口地址
|
||||
# VITE_APP_API_URL = http://62.234.3.186
|
||||
# 开发接口地址
|
||||
# VITE_APP_API_URL = http://localhost:8989
|
||||
|
||||
# 是否启用 Mock 服务
|
||||
VITE_MOCK_DEV_SERVER = false
|
|
@ -0,0 +1,3 @@
|
|||
# 代理前缀
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
|
||||
.eslintrc.cjs
|
||||
.prettierrc.cjs
|
||||
.stylelintrc.cjs
|
|
@ -0,0 +1,284 @@
|
|||
{
|
||||
"globals": {
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"EffectScope": true,
|
||||
"ElMessage": true,
|
||||
"ElMessageBox": true,
|
||||
"ElNotification": true,
|
||||
"InjectionKey": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"VNode": true,
|
||||
"asyncComputed": true,
|
||||
"autoResetRef": true,
|
||||
"computed": true,
|
||||
"computedAsync": true,
|
||||
"computedEager": true,
|
||||
"computedInject": true,
|
||||
"computedWithControl": true,
|
||||
"controlledComputed": true,
|
||||
"controlledRef": true,
|
||||
"createApp": true,
|
||||
"createEventHook": true,
|
||||
"createGlobalState": true,
|
||||
"createInjectionState": true,
|
||||
"createReactiveFn": true,
|
||||
"createReusableTemplate": true,
|
||||
"createSharedComposable": true,
|
||||
"createTemplatePromise": true,
|
||||
"createUnrefFn": true,
|
||||
"customRef": true,
|
||||
"debouncedRef": true,
|
||||
"debouncedWatch": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"eagerComputed": true,
|
||||
"effectScope": true,
|
||||
"extendRef": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"ignorableWatch": true,
|
||||
"inject": true,
|
||||
"isDefined": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"makeDestructurable": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onClickOutside": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onKeyStroke": true,
|
||||
"onLongPress": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onStartTyping": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"pausableWatch": true,
|
||||
"provide": true,
|
||||
"reactify": true,
|
||||
"reactifyObject": true,
|
||||
"reactive": true,
|
||||
"reactiveComputed": true,
|
||||
"reactiveOmit": true,
|
||||
"reactivePick": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"refAutoReset": true,
|
||||
"refDebounced": true,
|
||||
"refDefault": true,
|
||||
"refThrottled": true,
|
||||
"refWithControl": true,
|
||||
"resolveComponent": true,
|
||||
"resolveRef": true,
|
||||
"resolveUnref": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"syncRef": true,
|
||||
"syncRefs": true,
|
||||
"templateRef": true,
|
||||
"throttledRef": true,
|
||||
"throttledWatch": true,
|
||||
"toRaw": true,
|
||||
"toReactive": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"toValue": true,
|
||||
"triggerRef": true,
|
||||
"tryOnBeforeMount": true,
|
||||
"tryOnBeforeUnmount": true,
|
||||
"tryOnMounted": true,
|
||||
"tryOnScopeDispose": true,
|
||||
"tryOnUnmounted": true,
|
||||
"unref": true,
|
||||
"unrefElement": true,
|
||||
"until": true,
|
||||
"useActiveElement": true,
|
||||
"useAnimate": true,
|
||||
"useArrayDifference": true,
|
||||
"useArrayEvery": true,
|
||||
"useArrayFilter": true,
|
||||
"useArrayFind": true,
|
||||
"useArrayFindIndex": true,
|
||||
"useArrayFindLast": true,
|
||||
"useArrayIncludes": true,
|
||||
"useArrayJoin": true,
|
||||
"useArrayMap": true,
|
||||
"useArrayReduce": true,
|
||||
"useArraySome": true,
|
||||
"useArrayUnique": true,
|
||||
"useAsyncQueue": true,
|
||||
"useAsyncState": true,
|
||||
"useAttrs": true,
|
||||
"useBase64": true,
|
||||
"useBattery": true,
|
||||
"useBluetooth": true,
|
||||
"useBreakpoints": true,
|
||||
"useBroadcastChannel": true,
|
||||
"useBrowserLocation": true,
|
||||
"useCached": true,
|
||||
"useClipboard": true,
|
||||
"useCloned": true,
|
||||
"useColorMode": true,
|
||||
"useConfirmDialog": true,
|
||||
"useCounter": true,
|
||||
"useCssModule": true,
|
||||
"useCssVar": true,
|
||||
"useCssVars": true,
|
||||
"useCurrentElement": true,
|
||||
"useCycleList": true,
|
||||
"useDark": true,
|
||||
"useDateFormat": true,
|
||||
"useDebounce": true,
|
||||
"useDebounceFn": true,
|
||||
"useDebouncedRefHistory": true,
|
||||
"useDeviceMotion": true,
|
||||
"useDeviceOrientation": true,
|
||||
"useDevicePixelRatio": true,
|
||||
"useDevicesList": true,
|
||||
"useDisplayMedia": true,
|
||||
"useDocumentVisibility": true,
|
||||
"useDraggable": true,
|
||||
"useDropZone": true,
|
||||
"useElementBounding": true,
|
||||
"useElementByPoint": true,
|
||||
"useElementHover": true,
|
||||
"useElementSize": true,
|
||||
"useElementVisibility": true,
|
||||
"useEventBus": true,
|
||||
"useEventListener": true,
|
||||
"useEventSource": true,
|
||||
"useEyeDropper": true,
|
||||
"useFavicon": true,
|
||||
"useFetch": true,
|
||||
"useFileDialog": true,
|
||||
"useFileSystemAccess": true,
|
||||
"useFocus": true,
|
||||
"useFocusWithin": true,
|
||||
"useFps": true,
|
||||
"useFullscreen": true,
|
||||
"useGamepad": true,
|
||||
"useGeolocation": true,
|
||||
"useIdle": true,
|
||||
"useImage": true,
|
||||
"useInfiniteScroll": true,
|
||||
"useIntersectionObserver": true,
|
||||
"useInterval": true,
|
||||
"useIntervalFn": true,
|
||||
"useKeyModifier": true,
|
||||
"useLastChanged": true,
|
||||
"useLocalStorage": true,
|
||||
"useMagicKeys": true,
|
||||
"useManualRefHistory": true,
|
||||
"useMediaControls": true,
|
||||
"useMediaQuery": true,
|
||||
"useMemoize": true,
|
||||
"useMemory": true,
|
||||
"useMounted": true,
|
||||
"useMouse": true,
|
||||
"useMouseInElement": true,
|
||||
"useMousePressed": true,
|
||||
"useMutationObserver": true,
|
||||
"useNavigatorLanguage": true,
|
||||
"useNetwork": true,
|
||||
"useNow": true,
|
||||
"useObjectUrl": true,
|
||||
"useOffsetPagination": true,
|
||||
"useOnline": true,
|
||||
"usePageLeave": true,
|
||||
"useParallax": true,
|
||||
"useParentElement": true,
|
||||
"usePerformanceObserver": true,
|
||||
"usePermission": true,
|
||||
"usePointer": true,
|
||||
"usePointerLock": true,
|
||||
"usePointerSwipe": true,
|
||||
"usePreferredColorScheme": true,
|
||||
"usePreferredContrast": true,
|
||||
"usePreferredDark": true,
|
||||
"usePreferredLanguages": true,
|
||||
"usePreferredReducedMotion": true,
|
||||
"usePrevious": true,
|
||||
"useRafFn": true,
|
||||
"useRefHistory": true,
|
||||
"useResizeObserver": true,
|
||||
"useScreenOrientation": true,
|
||||
"useScreenSafeArea": true,
|
||||
"useScriptTag": true,
|
||||
"useScroll": true,
|
||||
"useScrollLock": true,
|
||||
"useSessionStorage": true,
|
||||
"useShare": true,
|
||||
"useSlots": true,
|
||||
"useSorted": true,
|
||||
"useSpeechRecognition": true,
|
||||
"useSpeechSynthesis": true,
|
||||
"useStepper": true,
|
||||
"useStorage": true,
|
||||
"useStorageAsync": true,
|
||||
"useStyleTag": true,
|
||||
"useSupported": true,
|
||||
"useSwipe": true,
|
||||
"useTemplateRefsList": true,
|
||||
"useTextDirection": true,
|
||||
"useTextSelection": true,
|
||||
"useTextareaAutosize": true,
|
||||
"useThrottle": true,
|
||||
"useThrottleFn": true,
|
||||
"useThrottledRefHistory": true,
|
||||
"useTimeAgo": true,
|
||||
"useTimeout": true,
|
||||
"useTimeoutFn": true,
|
||||
"useTimeoutPoll": true,
|
||||
"useTimestamp": true,
|
||||
"useTitle": true,
|
||||
"useToNumber": true,
|
||||
"useToString": true,
|
||||
"useToggle": true,
|
||||
"useTransition": true,
|
||||
"useUrlSearchParams": true,
|
||||
"useUserMedia": true,
|
||||
"useVModel": true,
|
||||
"useVModels": true,
|
||||
"useVibrate": true,
|
||||
"useVirtualList": true,
|
||||
"useWakeLock": true,
|
||||
"useWebNotification": true,
|
||||
"useWebSocket": true,
|
||||
"useWebWorker": true,
|
||||
"useWebWorkerFn": true,
|
||||
"useWindowFocus": true,
|
||||
"useWindowScroll": true,
|
||||
"useWindowSize": true,
|
||||
"watch": true,
|
||||
"watchArray": true,
|
||||
"watchAtMost": true,
|
||||
"watchDebounced": true,
|
||||
"watchDeep": true,
|
||||
"watchEffect": true,
|
||||
"watchIgnorable": true,
|
||||
"watchImmediate": true,
|
||||
"watchOnce": true,
|
||||
"watchPausable": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true,
|
||||
"watchThrottled": true,
|
||||
"watchTriggerable": true,
|
||||
"watchWithFilter": true,
|
||||
"whenever": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
parser: "vue-eslint-parser",
|
||||
extends: [
|
||||
// https://eslint.vuejs.org/user-guide/#usage
|
||||
"plugin:vue/vue3-recommended",
|
||||
"./.eslintrc-auto-import.json",
|
||||
"prettier",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
parser: "@typescript-eslint/parser",
|
||||
project: "./tsconfig.*?.json",
|
||||
createDefaultProgram: false,
|
||||
extraFileExtensions: [".vue"],
|
||||
},
|
||||
plugins: ["vue", "@typescript-eslint"],
|
||||
rules: {
|
||||
// https://eslint.vuejs.org/rules/#priority-a-essential-error-prevention
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-v-model-argument": "off",
|
||||
"vue/script-setup-uses-vars": "error",
|
||||
"vue/no-reserved-component-names": "off",
|
||||
"vue/custom-event-name-casing": "off",
|
||||
"vue/attributes-order": "off",
|
||||
"vue/one-component-per-file": "off",
|
||||
"vue/html-closing-bracket-newline": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/multiline-html-element-content-newline": "off",
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/attribute-hyphenation": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/require-explicit-emits": "off",
|
||||
"vue/html-self-closing": [
|
||||
"error",
|
||||
{
|
||||
html: {
|
||||
void: "always",
|
||||
normal: "never",
|
||||
component: "always",
|
||||
},
|
||||
svg: "always",
|
||||
math: "always",
|
||||
},
|
||||
],
|
||||
|
||||
"@typescript-eslint/no-empty-function": "off", // 关闭空方法检查
|
||||
"@typescript-eslint/no-explicit-any": "off", // 关闭any类型的警告
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
useTabs: false, // 不使用制表符
|
||||
},
|
||||
],
|
||||
},
|
||||
// eslint不能对html文件生效
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.html"],
|
||||
processor: "vue/.vue",
|
||||
},
|
||||
],
|
||||
// https://eslint.org/docs/latest/use/configure/language-options#specifying-globals
|
||||
globals: {
|
||||
OptionType: "readonly",
|
||||
},
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.history
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.local
|
||||
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
stats.html
|
|
@ -0,0 +1,11 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -0,0 +1,46 @@
|
|||
module.exports = {
|
||||
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
|
||||
arrowParens: "always",
|
||||
// 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
|
||||
bracketSameLine: false,
|
||||
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
|
||||
bracketSpacing: true,
|
||||
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
|
||||
embeddedLanguageFormatting: "auto",
|
||||
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
|
||||
htmlWhitespaceSensitivity: "css",
|
||||
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false
|
||||
insertPragma: false,
|
||||
// 在 JSX 中使用单引号替代双引号,默认false
|
||||
jsxSingleQuote: false,
|
||||
// 每行最多字符数量,超出换行(默认80)
|
||||
printWidth: 80,
|
||||
// 超出打印宽度 (always | never | preserve )
|
||||
proseWrap: "preserve",
|
||||
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
|
||||
quoteProps: "as-needed",
|
||||
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false
|
||||
requirePragma: false,
|
||||
// 结尾添加分号
|
||||
semi: true,
|
||||
// 使用单引号 (true:单引号;false:双引号)
|
||||
singleQuote: false,
|
||||
// 缩进空格数,默认2个空格
|
||||
tabWidth: 2,
|
||||
// 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号
|
||||
trailingComma: "es5",
|
||||
// 指定缩进方式,空格或tab,默认false,即使用空格
|
||||
useTabs: false,
|
||||
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
|
||||
vueIndentScriptAndStyle: false,
|
||||
|
||||
endOfLine: "auto",
|
||||
overrides: [
|
||||
{
|
||||
files: "*.html",
|
||||
options: {
|
||||
parser: "html",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -0,0 +1,51 @@
|
|||
module.exports = {
|
||||
// 继承推荐规范配置
|
||||
extends: [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-recommended-scss",
|
||||
"stylelint-config-recommended-vue/scss",
|
||||
"stylelint-config-html/vue",
|
||||
"stylelint-config-recess-order",
|
||||
],
|
||||
// 指定不同文件对应的解析器
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/*.{vue,html}"],
|
||||
customSyntax: "postcss-html",
|
||||
},
|
||||
{
|
||||
files: ["**/*.{css,scss}"],
|
||||
customSyntax: "postcss-scss",
|
||||
},
|
||||
],
|
||||
// 自定义规则
|
||||
rules: {
|
||||
"import-notation": "string", // 指定导入CSS文件的方式("string"|"url")
|
||||
"selector-class-pattern": null, // 选择器类名命名规则
|
||||
"custom-property-pattern": null, // 自定义属性命名规则
|
||||
"keyframes-name-pattern": null, // 动画帧节点样式命名规则
|
||||
"no-descending-specificity": null, // 允许无降序特异性
|
||||
"no-empty-source": null, // 允许空样式
|
||||
// 允许 global 、export 、deep伪类
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ["global", "export", "deep"],
|
||||
},
|
||||
],
|
||||
// 允许未知属性
|
||||
"property-no-unknown": [
|
||||
true,
|
||||
{
|
||||
ignoreProperties: [],
|
||||
},
|
||||
],
|
||||
// 允许未知规则
|
||||
"at-rule-no-unknown": [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: ["apply", "use"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
module.exports = {
|
||||
// 继承的规则
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
// 自定义规则
|
||||
rules: {
|
||||
// @see https://commitlint.js.org/#/reference-rules
|
||||
|
||||
// 提交类型枚举,git提交type必须是以下类型
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"feat", // 新增功能
|
||||
"fix", // 修复缺陷
|
||||
"docs", // 文档变更
|
||||
"style", // 代码格式(不影响功能,例如空格、分号等格式修正)
|
||||
"refactor", // 代码重构(不包括 bug 修复、功能新增)
|
||||
"perf", // 性能优化
|
||||
"test", // 添加疏漏测试或已有测试改动
|
||||
"build", // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
|
||||
"ci", // 修改 CI 配置、脚本
|
||||
"revert", // 回滚 commit
|
||||
"chore", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
|
||||
],
|
||||
],
|
||||
"subject-case": [0], // subject大小写不做校验
|
||||
},
|
||||
|
||||
prompt: {
|
||||
messages: {
|
||||
type: "选择你要提交的类型 :",
|
||||
scope: "选择一个提交范围(可选):",
|
||||
customScope: "请输入自定义的提交范围 :",
|
||||
subject: "填写简短精炼的变更描述 :\n",
|
||||
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
|
||||
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
|
||||
footerPrefixesSelect: "选择关联issue前缀(可选):",
|
||||
customFooterPrefix: "输入自定义issue前缀 :",
|
||||
footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
|
||||
generatingByAI: "正在通过 AI 生成你的提交简短描述...",
|
||||
generatedSelectByAI: "选择一个 AI 生成的简短描述:",
|
||||
confirmCommit: "是否提交或修改commit ?",
|
||||
},
|
||||
// prettier-ignore
|
||||
types: [
|
||||
{ value: "feat", name: "特性: ✨ 新增功能", emoji: ":sparkles:" },
|
||||
{ value: "fix", name: "修复: 🐛 修复缺陷", emoji: ":bug:" },
|
||||
{ value: "docs", name: "文档: 📝 文档变更(更新README文件,或者注释)", emoji: ":memo:" },
|
||||
{ value: "style", name: "格式: 🌈 代码格式(空格、格式化、缺失的分号等)", emoji: ":lipstick:" },
|
||||
{ value: "refactor", name: "重构: 🔄 代码重构(不修复错误也不添加特性的代码更改)", emoji: ":recycle:" },
|
||||
{ value: "perf", name: "性能: 🚀 性能优化", emoji: ":zap:" },
|
||||
{ value: "test", name: "测试: 🧪 添加疏漏测试或已有测试改动", emoji: ":white_check_mark:"},
|
||||
{ value: "build", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)", emoji: ":package:"},
|
||||
{ value: "ci", name: "集成: ⚙️ 修改 CI 配置、脚本", emoji: ":ferris_wheel:"},
|
||||
{ value: "revert", name: "回退: ↩️ 回滚 commit",emoji: ":rewind:"},
|
||||
{ value: "chore", name: "其他: 🛠️ 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: ":hammer:"},
|
||||
],
|
||||
useEmoji: true,
|
||||
emojiAlign: "center",
|
||||
useAI: false,
|
||||
aiNumber: 1,
|
||||
themeColorCode: "",
|
||||
scopes: [],
|
||||
allowCustomScopes: true,
|
||||
allowEmptyScopes: true,
|
||||
customScopesAlign: "bottom",
|
||||
customScopesAlias: "custom",
|
||||
emptyScopesAlias: "empty",
|
||||
upperCaseSubject: false,
|
||||
markBreakingChangeMode: false,
|
||||
allowBreakingChanges: ["feat", "fix"],
|
||||
breaklineNumber: 100,
|
||||
breaklineChar: "|",
|
||||
skipQuestions: [],
|
||||
issuePrefixes: [
|
||||
{ value: "closed", name: "closed: ISSUES has been processed" },
|
||||
],
|
||||
customIssuePrefixAlign: "top",
|
||||
emptyIssuePrefixAlias: "skip",
|
||||
customIssuePrefixAlias: "custom",
|
||||
allowCustomIssuePrefix: true,
|
||||
allowEmptyIssuePrefix: true,
|
||||
confirmColorize: true,
|
||||
maxHeaderLength: Infinity,
|
||||
maxSubjectLength: Infinity,
|
||||
minSubjectLength: 0,
|
||||
scopeOverrides: undefined,
|
||||
defaultBody: "",
|
||||
defaultIssues: "",
|
||||
defaultScope: "",
|
||||
defaultSubject: "",
|
||||
},
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Vue3 + Vite5 + TypeScript5 + Element-Plus 的后台管理模板,配套接口文档和后端源码,vue-element-admin 的 Vue3 版本"
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="vue,element-plus,typescript,vue-element-admin,vue3-element-admin"
|
||||
/>
|
||||
<title>vue3-element-admin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
aspect-ratio: 0.577;
|
||||
overflow: hidden;
|
||||
clip-path: polygon(0 0, 100% 100%, 0 100%, 100% 0);
|
||||
animation: l19 2s infinite linear;
|
||||
}
|
||||
|
||||
.loader::before {
|
||||
position: absolute;
|
||||
inset: -150%;
|
||||
content: "";
|
||||
background: repeating-conic-gradient(
|
||||
from 30deg,
|
||||
#ffabab 0 60deg,
|
||||
#abe4ff 0 120deg,
|
||||
#ff7373 0 180deg
|
||||
);
|
||||
animation: inherit;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
|
||||
@keyframes l19 {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</html>
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"name": "aimanage",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit & vite build",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint:eslint": "eslint --fix --ext .ts,.js,.vue ./src ",
|
||||
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix",
|
||||
"lint:lint-staged": "lint-staged",
|
||||
"prepare": "husky",
|
||||
"commit": "git-cz"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-git"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{cjs,json}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{vue,html}": [
|
||||
"eslint --fix",
|
||||
"prettier --write",
|
||||
"stylelint --fix"
|
||||
],
|
||||
"*.{scss,css}": [
|
||||
"stylelint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.md": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "5.1.10",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.6.8",
|
||||
"color": "^4.2.3",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "^2.7.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"net": "^1.0.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.2",
|
||||
"pinia": "^2.1.7",
|
||||
"sockjs-client": "1.6.1",
|
||||
"sortablejs": "^1.15.2",
|
||||
"stompjs": "^2.3.3",
|
||||
"vue": "^3.4.26",
|
||||
"vue-i18n": "9.9.1",
|
||||
"vue-router": "^4.3.2",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^18.6.1",
|
||||
"@commitlint/config-conventional": "^18.6.3",
|
||||
"@iconify-json/ep": "^1.1.15",
|
||||
"@types/color": "^3.0.6",
|
||||
"@types/lodash": "^4.17.1",
|
||||
"@types/node": "^20.12.8",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/path-browserify": "^1.0.2",
|
||||
"@types/sockjs-client": "^1.5.4",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@types/stompjs": "^2.3.9",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"commitizen": "^4.3.0",
|
||||
"cz-git": "^1.9.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.25.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-html": "^1.6.0",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.76.0",
|
||||
"stylelint": "^16.5.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recess-order": "^4.6.0",
|
||||
"stylelint-config-recommended-scss": "^14.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.5.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
"terser": "^5.31.0",
|
||||
"typescript": "^5.4.5",
|
||||
"unocss": "^0.58.9",
|
||||
"unplugin-auto-import": "^0.17.5",
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.2.11",
|
||||
"vite-plugin-mock-dev-server": "^1.5.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-devtools": "^7.1.3",
|
||||
"vue-tsc": "^2.0.16"
|
||||
},
|
||||
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
|
||||
"author": "有来开源组织",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<el-config-provider :locale="locale" :size="size">
|
||||
<!-- 开启水印 -->
|
||||
<el-watermark
|
||||
v-if="watermarkEnabled"
|
||||
:font="{ color: fontColor }"
|
||||
:content="defaultSettings.watermarkContent"
|
||||
class="wh-full"
|
||||
>
|
||||
<router-view />
|
||||
</el-watermark>
|
||||
<!-- 关闭水印 -->
|
||||
<router-view v-else />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore, useSettingsStore } from "@/store";
|
||||
import defaultSettings from "@/settings";
|
||||
import { ThemeEnum } from "@/enums/ThemeEnum";
|
||||
import { SizeEnum } from "@/enums/SizeEnum";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const locale = computed(() => appStore.locale);
|
||||
const size = computed(() => appStore.size as SizeEnum);
|
||||
const watermarkEnabled = computed(() => settingsStore.watermarkEnabled);
|
||||
|
||||
// 明亮/暗黑主题水印字体颜色适配
|
||||
const fontColor = computed(() => {
|
||||
return settingsStore.theme === ThemeEnum.DARK
|
||||
? "rgba(255, 255, 255, .15)"
|
||||
: "rgba(0, 0, 0, .15)";
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,66 @@
|
|||
import request from "@/utils/request";
|
||||
import { CaptchaResult, LoginData, LoginResult } from "./model";
|
||||
|
||||
class AuthAPI {
|
||||
/**
|
||||
* 登录API
|
||||
*
|
||||
* @param data {LoginData}
|
||||
* @returns
|
||||
*/
|
||||
static login(data: LoginData) {
|
||||
/*
|
||||
const formData = new FormData();
|
||||
formData.append("username", data.username);
|
||||
formData.append("password", data.password);
|
||||
formData.append("captchaKey", data.captchaKey || "");
|
||||
formData.append("captchaCode", data.captchaCode || "");
|
||||
return request<any, LoginResult>({
|
||||
url: "/api/v1/auth/login",
|
||||
method: "post",
|
||||
data: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});*/
|
||||
return new Promise((resolve,reject)=>{
|
||||
if(data.username!='admin' || data.password!='123456'){
|
||||
reject("用户名或密码错误");
|
||||
}else{
|
||||
resolve( {
|
||||
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImRlcHRJZCI6MSwiZGF0YVNjb3BlIjoxLCJleHAiOjE3MTYyMjY0OTksInVzZXJJZCI6MiwiaWF0IjoxNzE2MjE5Mjk5LCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjVmMWIwNTExNmZjZDQxZmE5YTVlNjU4ZjQ5YmRmNmEyIn0.BBCWh3-bffEtgT0zhxSQ0ncithh9iIGDtbg1DPN0TvA",
|
||||
"tokenType": "Bearer",
|
||||
"refreshToken": null,
|
||||
"expires": null
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销API
|
||||
*/
|
||||
static logout() {
|
||||
return new Promise(r=>{
|
||||
r({});
|
||||
});
|
||||
/*
|
||||
return request({
|
||||
url: "/api/v1/auth/logout",
|
||||
method: "delete",
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
static getCaptcha() {
|
||||
return request<any, CaptchaResult>({
|
||||
url: "/api/v1/auth/captcha",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthAPI;
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* 登录请求参数
|
||||
*/
|
||||
export interface LoginData {
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
password: string;
|
||||
|
||||
/**
|
||||
* 验证码缓存key
|
||||
*/
|
||||
captchaKey?: string;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
captchaCode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录响应
|
||||
*/
|
||||
export interface LoginResult {
|
||||
/**
|
||||
* 访问token
|
||||
*/
|
||||
accessToken?: string;
|
||||
/**
|
||||
* 过期时间(单位:毫秒)
|
||||
*/
|
||||
expires?: number;
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
refreshToken?: string;
|
||||
/**
|
||||
* token 类型
|
||||
*/
|
||||
tokenType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码响应
|
||||
*/
|
||||
export interface CaptchaResult {
|
||||
/**
|
||||
* 验证码缓存key
|
||||
*/
|
||||
captchaKey: string;
|
||||
/**
|
||||
* 验证码图片Base64字符串
|
||||
*/
|
||||
captchaBase64: string;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import request from "@/utils/request";
|
||||
import { DeptForm, DeptQuery, DeptVO } from "./model";
|
||||
|
||||
class DeptAPI {
|
||||
/**
|
||||
* 部门树形表格
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static getList(queryParams?: DeptQuery) {
|
||||
return request<any, DeptVO[]>({
|
||||
url: "/api/v1/dept",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门下拉列表
|
||||
*/
|
||||
static getOptions() {
|
||||
return request<any, OptionType[]>({
|
||||
url: "/api/v1/dept/options",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门详情
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
static getFormData(id: number) {
|
||||
return request<any, DeptForm>({
|
||||
url: "/api/v1/dept/" + id + "/form",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
static add(data: DeptForm) {
|
||||
return request({
|
||||
url: "/api/v1/dept",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门
|
||||
*
|
||||
* @param id
|
||||
* @param data
|
||||
*/
|
||||
static update(id: number, data: DeptForm) {
|
||||
return request({
|
||||
url: "/api/v1/dept/" + id,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
static deleteByIds(ids: string) {
|
||||
return request({
|
||||
url: "/api/v1/dept/" + ids,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DeptAPI;
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* 部门查询参数
|
||||
*/
|
||||
export interface DeptQuery {
|
||||
keywords?: string;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门类型
|
||||
*/
|
||||
export interface DeptVO {
|
||||
/**
|
||||
* 子部门
|
||||
*/
|
||||
children?: DeptVO[];
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: Date;
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 父部门ID
|
||||
*/
|
||||
parentId?: number;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 状态(1:启用;0:禁用)
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
updateTime?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门表单类型
|
||||
*/
|
||||
export interface DeptForm {
|
||||
/**
|
||||
* 部门ID(新增不填)
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 父部门ID
|
||||
*/
|
||||
parentId: number;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 状态(1:启用;0:禁用)
|
||||
*/
|
||||
status?: number;
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
import request from "@/utils/request";
|
||||
import {
|
||||
DictTypeQuery,
|
||||
DictTypePageResult,
|
||||
DictTypeForm,
|
||||
DictQuery,
|
||||
DictForm,
|
||||
DictPageResult,
|
||||
} from "./model";
|
||||
|
||||
class DictAPI {
|
||||
/**
|
||||
* 字典类型分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static getDictTypePage(queryParams: DictTypeQuery) {
|
||||
return request<any, DictTypePageResult>({
|
||||
url: "/api/v1/dict/types/page",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典类型表单数据
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
static getDictTypeForm(id: number) {
|
||||
return request<any, ResponseData<DictTypeForm>>({
|
||||
url: "/api/v1/dict/types/" + id + "/form",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典类型
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
static addDictType(data: DictTypeForm) {
|
||||
return request({
|
||||
url: "/api/v1/dict/types",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典类型
|
||||
*
|
||||
* @param id
|
||||
* @param data
|
||||
*/
|
||||
static updateDictType(id: number, data: DictTypeForm) {
|
||||
return request({
|
||||
url: "/api/v1/dict/types/" + id,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典类型
|
||||
*/
|
||||
static deleteDictTypes(ids: string) {
|
||||
return request({
|
||||
url: "/api/v1/dict/types/" + ids,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典类型的数据项
|
||||
*
|
||||
* @param typeCode 字典类型编码
|
||||
*/
|
||||
static getDictOptions(typeCode: string) {
|
||||
return request<any, OptionType[]>({
|
||||
url: "/api/v1/dict/" + typeCode + "/options",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典分页列表
|
||||
*/
|
||||
static getDictPage(queryParams: DictQuery) {
|
||||
return request<any, DictPageResult>({
|
||||
url: "/api/v1/dict/page",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典表单数据
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
static getDictFormData(id: number) {
|
||||
return request<any, DictForm>({
|
||||
url: "/api/v1/dict/" + id + "/form",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
static addDict(data: DictForm) {
|
||||
return request({
|
||||
url: "/api/v1/dict",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典项
|
||||
*
|
||||
* @param id
|
||||
* @param data
|
||||
*/
|
||||
static updateDict(id: number, data: DictForm) {
|
||||
return request({
|
||||
url: "/api/v1/dict/" + id,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典
|
||||
*
|
||||
* @param ids 字典项ID,多个以英文逗号(,)分割
|
||||
*/
|
||||
static deleteDictByIds(ids: string) {
|
||||
return request({
|
||||
url: "/api/v1/dict/" + ids,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DictAPI;
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* 字典类型查询参数
|
||||
*/
|
||||
export interface DictTypeQuery extends PageQuery {
|
||||
/**
|
||||
* 关键字(字典类型名称/编码)
|
||||
*/
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典类型分页对象
|
||||
*/
|
||||
export interface DictTypePageVO {
|
||||
/**
|
||||
* 字典类型ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* 类型编码
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* 类型名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 状态(1:启用;0:禁用)
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典分页项类型声明
|
||||
*/
|
||||
export type DictTypePageResult = PageResult<DictTypePageVO[]>;
|
||||
|
||||
/**
|
||||
* 字典表单类型声明
|
||||
*/
|
||||
export interface DictTypeForm {
|
||||
/**
|
||||
* 字典类型ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 类型名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 类型编码
|
||||
*/
|
||||
code?: string;
|
||||
/**
|
||||
* 类型状态:1:启用;0:禁用
|
||||
*/
|
||||
status: number;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典查询参数
|
||||
*/
|
||||
export interface DictQuery extends PageQuery {
|
||||
/**
|
||||
* 字典项名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 字典类型编码
|
||||
*/
|
||||
typeCode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典分页对象
|
||||
*/
|
||||
export interface DictPageVO {
|
||||
/**
|
||||
* 字典ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 状态(1:启用;0:禁用)
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 字典值
|
||||
*/
|
||||
value?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典分页
|
||||
*/
|
||||
export type DictPageResult = PageResult<DictPageVO[]>;
|
||||
|
||||
/**
|
||||
* 字典表单
|
||||
*/
|
||||
export interface DictForm {
|
||||
/**
|
||||
* 字典ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 状态(1:启用;0:禁用)
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 类型编码
|
||||
*/
|
||||
typeCode?: string;
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
value?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import request from "@/utils/request";
|
||||
import { FileInfo } from "./model";
|
||||
|
||||
class FileAPI {
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
static upload(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return request<any, FileInfo>({
|
||||
url: "/api/v1/files",
|
||||
method: "post",
|
||||
data: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filePath 文件完整路径
|
||||
*/
|
||||
static deleteByPath(filePath?: string) {
|
||||
return request({
|
||||
url: "/api/v1/files",
|
||||
method: "delete",
|
||||
params: { filePath: filePath },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default FileAPI;
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* 文件API类型声明
|
||||
*/
|
||||
export interface FileInfo {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
|
@ -0,0 +1,382 @@
|
|||
import request from "@/utils/request";
|
||||
import { MenuQuery, MenuVO, MenuForm, RouteVO } from "./model";
|
||||
|
||||
class MenuAPI {
|
||||
/**
|
||||
* 获取路由列表
|
||||
*/
|
||||
static getRoutes() {
|
||||
return new Promise((resolve) => {
|
||||
resolve([
|
||||
{
|
||||
path: "/split",
|
||||
component: "Layout",
|
||||
redirect: "/split/index",
|
||||
name: "/split",
|
||||
meta: {
|
||||
title: "算法分割",
|
||||
icon: "project",
|
||||
hidden: false,
|
||||
roles: ["GUEST", "ADMIN", "ADMIN6"],
|
||||
alwaysShow: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: "system/user/index",
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "算法分割1",
|
||||
icon: "user",
|
||||
hidden: false,
|
||||
roles: ["ADMIN", "GUEST"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: "system/role/index",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "算法分割2",
|
||||
icon: "role",
|
||||
hidden: false,
|
||||
roles: ["ADMIN6", "GUEST", "ADMIN"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/internetProtocol",
|
||||
component: "Layout",
|
||||
redirect: "/internetProtocol/index",
|
||||
name: "/internetProtocol",
|
||||
meta: {
|
||||
title: "互联协议",
|
||||
icon: "api",
|
||||
hidden: false,
|
||||
roles: ["GUEST", "ADMIN", "ADMIN6"],
|
||||
alwaysShow: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: "system/user/index",
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "互联协议1",
|
||||
icon: "user",
|
||||
hidden: false,
|
||||
roles: ["ADMIN", "GUEST"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: "system/role/index",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "互联协议2",
|
||||
icon: "role",
|
||||
hidden: false,
|
||||
roles: ["ADMIN6", "GUEST", "ADMIN"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/simulationEvaluation",
|
||||
component: "Layout",
|
||||
redirect: "/simulationEvaluation/index",
|
||||
name: "/simulationEvaluation",
|
||||
meta: {
|
||||
title: "仿真评估",
|
||||
icon: "client",
|
||||
hidden: false,
|
||||
roles: ["GUEST", "ADMIN", "ADMIN6"],
|
||||
alwaysShow: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: "system/user/index",
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "仿真评估1",
|
||||
icon: "user",
|
||||
hidden: false,
|
||||
roles: ["ADMIN", "GUEST"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: "system/role/index",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "仿真评估2",
|
||||
icon: "role",
|
||||
hidden: false,
|
||||
roles: ["ADMIN6", "GUEST", "ADMIN"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/operatorLibrary",
|
||||
component: "Layout",
|
||||
redirect: "/operatorLibrary/index",
|
||||
name: "/operatorLibrary",
|
||||
meta: {
|
||||
title: "算子库管理",
|
||||
icon: "dict",
|
||||
hidden: false,
|
||||
roles: ["GUEST", "ADMIN", "ADMIN6"],
|
||||
alwaysShow: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: "system/user/index",
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "算子库管理1",
|
||||
icon: "user",
|
||||
hidden: false,
|
||||
roles: ["ADMIN", "GUEST"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: "system/role/index",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "算子库管理2",
|
||||
icon: "role",
|
||||
hidden: false,
|
||||
roles: ["ADMIN6", "GUEST", "ADMIN"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/dataMgr",
|
||||
component: "Layout",
|
||||
redirect: "/dataMgr/user",
|
||||
name: "/dataMgr",
|
||||
meta: {
|
||||
title: "数据管理",
|
||||
icon: "edit",
|
||||
hidden: false,
|
||||
roles: ["GUEST", "ADMIN", "ADMIN6"],
|
||||
alwaysShow: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: "system/user/index",
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "数据管理1",
|
||||
icon: "user",
|
||||
hidden: false,
|
||||
roles: ["ADMIN", "GUEST"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: "system/role/index",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "数据管理2",
|
||||
icon: "role",
|
||||
hidden: false,
|
||||
roles: ["ADMIN6", "GUEST", "ADMIN"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/tester",
|
||||
component: "Layout",
|
||||
redirect: "/tester/index",
|
||||
name: "/tester",
|
||||
meta: {
|
||||
title: "算法测试仪管理",
|
||||
icon: "link",
|
||||
hidden: false,
|
||||
roles: ["GUEST", "ADMIN", "ADMIN6"],
|
||||
alwaysShow: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: "system/user/index",
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "算法测试仪管理1",
|
||||
icon: "user",
|
||||
hidden: false,
|
||||
roles: ["ADMIN", "GUEST"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: "system/role/index",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "算法算法测试仪管理2",
|
||||
icon: "role",
|
||||
hidden: false,
|
||||
roles: ["ADMIN6", "GUEST", "ADMIN"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/system",
|
||||
component: "Layout",
|
||||
redirect: "/system/index",
|
||||
name: "/system",
|
||||
meta: {
|
||||
title: "系统管理",
|
||||
icon: "menu",
|
||||
hidden: false,
|
||||
roles: ["GUEST", "ADMIN", "ADMIN6"],
|
||||
alwaysShow: false,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: "system/user/index",
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "系统管理1",
|
||||
icon: "user",
|
||||
hidden: false,
|
||||
roles: ["ADMIN", "GUEST"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: "system/role/index",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "系统管理2",
|
||||
icon: "role",
|
||||
hidden: false,
|
||||
roles: ["ADMIN6", "GUEST", "ADMIN"],
|
||||
keepAlive: true,
|
||||
alwaysShow: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
/*
|
||||
return request<any, RouteVO[]>({
|
||||
url: "/api/v1/menus/routes",
|
||||
method: "get",
|
||||
});*/
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单树形列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static getList(queryParams: MenuQuery) {
|
||||
return request<any, MenuVO[]>({
|
||||
url: "/api/v1/menus",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单下拉数据源
|
||||
*/
|
||||
static getOptions() {
|
||||
return request<any, OptionType[]>({
|
||||
url: "/api/v1/menus/options",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单表单数据
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
static getFormData(id: number) {
|
||||
return request<any, MenuForm>({
|
||||
url: "/api/v1/menus/" + id + "/form",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加菜单
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
static add(data: MenuForm) {
|
||||
return request({
|
||||
url: "/api/v1/menus",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改菜单
|
||||
*
|
||||
* @param id
|
||||
* @param data
|
||||
*/
|
||||
static update(id: string, data: MenuForm) {
|
||||
return request({
|
||||
url: "/api/v1/menus/" + id,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
*
|
||||
* @param id 菜单ID
|
||||
*/
|
||||
static deleteById(id: number) {
|
||||
return request({
|
||||
url: "/api/v1/menus/" + id,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default MenuAPI;
|
|
@ -0,0 +1,181 @@
|
|||
import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
|
||||
|
||||
/**
|
||||
* 菜单查询参数类型
|
||||
*/
|
||||
export interface MenuQuery {
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单视图对象类型
|
||||
*/
|
||||
export interface MenuVO {
|
||||
/**
|
||||
* 子菜单
|
||||
*/
|
||||
children?: MenuVO[];
|
||||
/**
|
||||
* 组件路径
|
||||
*/
|
||||
component?: string;
|
||||
/**
|
||||
* ICON
|
||||
*/
|
||||
icon?: string;
|
||||
/**
|
||||
* 菜单ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 父菜单ID
|
||||
*/
|
||||
parentId?: number;
|
||||
/**
|
||||
* 按钮权限标识
|
||||
*/
|
||||
perm?: string;
|
||||
/**
|
||||
* 跳转路径
|
||||
*/
|
||||
redirect?: string;
|
||||
/**
|
||||
* 路由名称
|
||||
*/
|
||||
routeName?: string;
|
||||
/**
|
||||
* 路由相对路径
|
||||
*/
|
||||
routePath?: string;
|
||||
/**
|
||||
* 菜单排序(数字越小排名越靠前)
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
type?: MenuTypeEnum;
|
||||
/**
|
||||
* 菜单是否可见(1:显示;0:隐藏)
|
||||
*/
|
||||
visible?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单表单对象类型
|
||||
*/
|
||||
export interface MenuForm {
|
||||
/**
|
||||
* 菜单ID
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* 父菜单ID
|
||||
*/
|
||||
parentId?: number;
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 菜单是否可见(1:是;0:否;)
|
||||
*/
|
||||
visible: number;
|
||||
icon?: string;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort: number;
|
||||
/**
|
||||
* 组件路径
|
||||
*/
|
||||
component?: string;
|
||||
/**
|
||||
* 路由路径
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* 跳转路由路径
|
||||
*/
|
||||
redirect?: string;
|
||||
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
type: MenuTypeEnum;
|
||||
|
||||
/**
|
||||
* 权限标识
|
||||
*/
|
||||
perm?: string;
|
||||
/**
|
||||
* 【菜单】是否开启页面缓存
|
||||
*/
|
||||
keepAlive?: number;
|
||||
|
||||
/**
|
||||
* 【目录】只有一个子路由是否始终显示
|
||||
*/
|
||||
alwaysShow?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RouteVO,路由对象
|
||||
*/
|
||||
export interface RouteVO {
|
||||
/**
|
||||
* 子路由列表
|
||||
*/
|
||||
children: RouteVO[];
|
||||
/**
|
||||
* 组件路径
|
||||
*/
|
||||
component?: string;
|
||||
meta?: Meta;
|
||||
/**
|
||||
* 路由名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 路由路径
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* 跳转链接
|
||||
*/
|
||||
redirect?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Meta,路由属性类型
|
||||
*/
|
||||
export interface Meta {
|
||||
/**
|
||||
* 【目录】只有一个子路由是否始终显示
|
||||
*/
|
||||
alwaysShow?: boolean;
|
||||
/**
|
||||
* 是否隐藏(true-是 false-否)
|
||||
*/
|
||||
hidden?: boolean;
|
||||
/**
|
||||
* ICON
|
||||
*/
|
||||
icon?: string;
|
||||
/**
|
||||
* 【菜单】是否开启页面缓存
|
||||
*/
|
||||
keepAlive?: boolean;
|
||||
/**
|
||||
* 拥有路由权限的角色编码
|
||||
*/
|
||||
roles?: string[];
|
||||
/**
|
||||
* 路由title
|
||||
*/
|
||||
title?: string;
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import request from "@/utils/request";
|
||||
import { RoleQuery, RolePageResult, RoleForm } from "./model";
|
||||
|
||||
class RoleAPI {
|
||||
/**
|
||||
* 获取角色分页数据
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static getPage(queryParams?: RoleQuery) {
|
||||
return request<any, RolePageResult>({
|
||||
url: "/api/v1/roles/page",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色下拉数据源
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static getOptions(queryParams?: RoleQuery) {
|
||||
return request<any, OptionType[]>({
|
||||
url: "/api/v1/roles/options",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的菜单ID集合
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static getRoleMenuIds(roleId: number) {
|
||||
return request<any, number[]>({
|
||||
url: "/api/v1/roles/" + roleId + "/menuIds",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配菜单权限给角色
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static updateRoleMenus(roleId: number, data: number[]) {
|
||||
return request({
|
||||
url: "/api/v1/roles/" + roleId + "/menus",
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色表单数据
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
static getFormData(id: number) {
|
||||
return request<any, RoleForm>({
|
||||
url: "/api/v1/roles/" + id + "/form",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加角色
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
static add(data: RoleForm) {
|
||||
return request({
|
||||
url: "/api/v1/roles",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*
|
||||
* @param id
|
||||
* @param data
|
||||
*/
|
||||
static update(id: number, data: RoleForm) {
|
||||
return request({
|
||||
url: "/api/v1/roles/" + id,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除角色,多个以英文逗号(,)分割
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
static deleteByIds(ids: string) {
|
||||
return request({
|
||||
url: "/api/v1/roles/" + ids,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default RoleAPI;
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* 角色查询参数
|
||||
*/
|
||||
export interface RoleQuery extends PageQuery {
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色分页对象
|
||||
*/
|
||||
export interface RolePageVO {
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
code?: string;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 角色状态
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: Date;
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
updateTime?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色分页
|
||||
*/
|
||||
export type RolePageResult = PageResult<RolePageVO[]>;
|
||||
|
||||
/**
|
||||
* 角色表单对象
|
||||
*/
|
||||
export interface RoleForm {
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
id?: number;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* 数据权限
|
||||
*/
|
||||
dataScope?: number;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 角色状态(1-正常;0-停用)
|
||||
*/
|
||||
status?: number;
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
import request from "@/utils/request";
|
||||
import { UserForm, UserInfo, UserPageVO, UserQuery } from "./model";
|
||||
|
||||
class UserAPI {
|
||||
/**
|
||||
* 登录成功后获取用户信息(昵称、头像、权限集合和角色集合)
|
||||
*/
|
||||
static getInfo() {
|
||||
return new Promise((resolve)=>{
|
||||
resolve({
|
||||
"userId": 2,
|
||||
"username": "admin",
|
||||
"nickname": "系统管理员",
|
||||
"avatar": "https://oss.youlai.tech/youlai-boot/2023/05/16/811270ef31f548af9cffc026dfc3777b.gif",
|
||||
"roles": [
|
||||
"ADMIN"
|
||||
],
|
||||
"perms": []
|
||||
})
|
||||
})
|
||||
/*
|
||||
return request<any, UserInfo>({
|
||||
url: "/api/v1/users/me",
|
||||
method: "get",
|
||||
});*/
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
static getPage(queryParams: UserQuery) {
|
||||
return request<any, PageResult<UserPageVO[]>>({
|
||||
url: "/api/v1/users/page",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户表单详情
|
||||
*
|
||||
* @param userId
|
||||
*/
|
||||
static getFormData(userId: number) {
|
||||
return request<any, UserForm>({
|
||||
url: "/api/v1/users/" + userId + "/form",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
static add(data: UserForm) {
|
||||
return request({
|
||||
url: "/api/v1/users",
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户
|
||||
*
|
||||
* @param id
|
||||
* @param data
|
||||
*/
|
||||
static update(id: number, data: UserForm) {
|
||||
return request({
|
||||
url: "/api/v1/users/" + id,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户密码
|
||||
*
|
||||
* @param id
|
||||
* @param password
|
||||
*/
|
||||
static updatePassword(id: number, password: string) {
|
||||
return request({
|
||||
url: "/api/v1/users/" + id + "/password",
|
||||
method: "patch",
|
||||
params: { password: password },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
static deleteByIds(ids: string) {
|
||||
return request({
|
||||
url: "/api/v1/users/" + ids,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载用户导入模板
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
static downloadTemplate() {
|
||||
return request({
|
||||
url: "/api/v1/users/template",
|
||||
method: "get",
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用户
|
||||
*
|
||||
* @param queryParams
|
||||
* @returns
|
||||
*/
|
||||
static export(queryParams: UserQuery) {
|
||||
return request({
|
||||
url: "/api/v1/users/export",
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入用户
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
static import(deptId: number, file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return request({
|
||||
url: "/api/v1/users/import",
|
||||
method: "post",
|
||||
params: { deptId: deptId },
|
||||
data: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UserAPI;
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* 登录用户信息
|
||||
*/
|
||||
export interface UserInfo {
|
||||
userId?: number;
|
||||
username?: string;
|
||||
nickname?: string;
|
||||
avatar?: string;
|
||||
roles: string[];
|
||||
perms: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户查询对象类型
|
||||
*/
|
||||
export interface UserQuery extends PageQuery {
|
||||
keywords?: string;
|
||||
status?: number;
|
||||
deptId?: number;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户分页对象
|
||||
*/
|
||||
export interface UserPageVO {
|
||||
/**
|
||||
* 用户头像地址
|
||||
*/
|
||||
avatar?: string;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: Date;
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName?: string;
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
email?: string;
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
genderLabel?: string;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
mobile?: string;
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickname?: string;
|
||||
/**
|
||||
* 角色名称,多个使用英文逗号(,)分割
|
||||
*/
|
||||
roleNames?: string;
|
||||
/**
|
||||
* 用户状态(1:启用;0:禁用)
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户表单类型
|
||||
*/
|
||||
export interface UserForm {
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
avatar?: string;
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
email?: string;
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
gender?: number;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
id?: number;
|
||||
mobile?: string;
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
nickname?: string;
|
||||
/**
|
||||
* 角色ID集合
|
||||
*/
|
||||
roleIds?: number[];
|
||||
/**
|
||||
* 用户状态(1:正常;0:禁用)
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
username?: string;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M499.2 671.232v-261.12h102.4c16.384 0 28.672 1.024 37.888 2.56 13.312 2.048 24.576 6.656 34.816 13.312 9.728 6.656 17.92 16.384 23.552 28.16 6.144 12.288 8.704 25.6 8.192 38.4 0 23.552-7.68 44.032-23.04 59.904-15.36 16.896-40.96 25.088-78.848 25.088h-43.52v93.184l-61.44.512zm281.6 0h-61.952v-261.12H780.8v261.12zm-287.744 0h-69.12L396.8 601.6h-73.728l-25.088 69.632h-66.56l100.352-261.12h54.272l107.008 261.12zM343.552 545.28h32.256l-15.872-42.496c0-.512-.512-1.024-.512-1.536l-15.872 44.032zm217.6-26.112h43.52c20.48 0 28.16-4.608 31.232-7.168 4.608-4.096 7.168-10.752 7.168-18.944 0-6.656-1.536-11.776-4.096-15.36-2.56-3.584-6.144-6.144-10.752-7.68-1.536-.512-6.656-1.536-24.064-1.536h-43.008v50.688z"/><path d="M747.52 842.752H512c-8.704 0-16.384-3.584-22.016-9.728-6.144-6.144-9.216-14.336-8.704-22.528.512-16.896 14.336-30.72 31.232-31.232H747.52c115.712 0 209.408-94.208 209.408-209.408 0-104.96-78.848-194.56-183.296-207.872l-22.528-3.072-4.608-22.016C724.992 231.936 631.808 156.16 524.288 156.16c-124.928 0-226.304 101.376-226.304 226.304v8.704l1.536 36.352-36.352-4.096c-6.144-1.024-12.288-1.024-18.432-1.024-98.304 0-178.176 79.872-178.176 178.176 0 98.304 79.872 178.176 178.176 178.176h63.488c8.704 0 16.384 3.584 22.016 9.728 6.144 6.144 9.216 14.336 8.704 22.528-.512 16.896-14.336 30.72-31.232 31.232h-64c-64 0-123.904-25.088-169.472-70.144C28.16 726.528 3.072 665.6 3.072 601.088c0-129.536 103.936-236.544 232.448-241.152 12.288-157.184 149.504-276.48 307.2-266.24 59.904 3.584 118.784 27.136 165.888 65.536 45.568 37.376 77.824 87.04 94.208 143.872 125.952 26.112 217.088 137.728 217.088 266.752.512 151.04-121.856 272.896-272.384 272.896z"/><path d="M572.416 930.816c-8.192 0-15.872-3.072-21.504-8.704L431.616 812.544l113.152-117.76c6.144-6.144 13.824-9.216 22.528-9.216 8.704 0 16.384 3.072 22.528 9.216 11.776 11.776 12.288 31.232 1.024 44.032l-68.608 70.656 71.68 66.048c6.144 5.632 9.728 13.312 10.24 22.016.512 8.704-2.56 16.384-8.192 23.04-6.656 6.656-14.848 10.24-23.552 10.24z"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M917.6 267.2c-36.1-2.5-72.4-9.3-103.6-19.3-10.1-3-20.2-6.4-30.3-10-21.4-6.3-50.5-18.8-83.6-36.6-.4-.2-.7-.4-1.1-.6-7.8-4.2-15.7-8.7-23.8-13.4-10.9-6.3-21.7-12.9-32.5-19.9-.4-.3-.8-.5-1.2-.8-7.7-5-15.5-10.2-23.1-15.5-5-3.4-10-7.1-15-10.7-3.8-2.8-7.5-5.3-11.3-8.2-27.4-20.5-54.5-43.5-79.9-68.3-25.4 24.8-52.5 47.8-79.9 68.3-3.7 2.8-7.5 5.4-11.3 8.2-5 3.6-10 7.3-15 10.7-7.7 5.4-15.4 10.5-23.1 15.5-.4.3-.8.5-1.2.8-10.8 6.9-21.6 13.6-32.5 19.9-8.1 4.7-16 9.2-23.8 13.4-.3.2-.7.4-1 .6-33 17.8-62.2 30.3-83.6 36.6-10.1 3.6-20.2 7-30.3 10-31.1 10-67.4 16.8-103.6 19.3h.1c1.1 16.2 2.1 37.7 3.4 60.9h.7c6.1 86.8 23.5 210.2 49.7 282.8 1.2 3.2 2.2 6.5 3.3 9.6.6 1.5 1.2 2.8 1.8 4.3 62.8 162.1 171.9 280.1 303 323.4v.4c17.3 5.7 31.9 9.3 43.5 11.5 11.5-2.2 26.1-5.8 43.5-11.5v-.4C687 905 796.1 787 858.9 624.8c.6-1.5 1.2-2.8 1.8-4.3 1.2-3.1 2.2-6.4 3.3-9.6 26.2-72.5 43.6-196 49.7-282.8h.7c1.1-23.3 2.2-44.7 3.2-60.9zm-47.4 41.9-.5 9.5c-.5 2.2-.9 4.4-1 6.6C863 406 847 525.7 821.3 596.7c-.7 1.9-1.4 3.9-2 5.8-.4 1.2-.8 2.5-1.4 4.1-.5 1.2-1 2.5-1.4 3.4C758.1 760.8 657.7 869.3 541 907.8c-1.9.6-3.7 1.4-5.5 2.2-7.9 2.5-15.7 4.6-23.2 6.3-7.5-1.7-15.2-3.8-23.1-6.3-1.8-.9-3.6-1.6-5.5-2.2-116.7-38.5-217.1-147-275.4-297.5-.5-1.2-.9-2.4-1.7-4.1-.4-1.2-.8-2.4-1.3-3.6-.7-2-1.3-3.9-1.9-5.6-25.8-71.2-41.7-191-47.4-271.7-.2-2.3-.5-4.5-1-6.6l-.5-9.3c-.1-1.5-.2-3-.2-4.5 24.6-3.8 48.4-9.3 70-16.2 10.1-3 20.4-6.4 31.4-10.4 25.2-7.6 56.5-21.2 90.5-39.6.6-.3 1.2-.6 1.7-.9 8.2-4.4 16.7-9.2 24.8-14 10.7-6.1 22-13 34.5-21.1.4-.2 1-.6 1.3-.8 8.2-5.3 16.4-10.8 24.1-16.2 4.5-3.1 9.1-6.4 13.7-9.7l2.4-1.8 4-2.9c2.6-1.9 5.2-3.7 7.5-5.5 17.9-13.4 35.3-27.5 52-42.1 16.7 14.7 34 28.7 51.8 42 2.6 1.9 5.1 3.8 7.7 5.6l4.3 3.1 1.5 1.1c4.8 3.5 9.6 6.9 14 9.9 8.1 5.7 16.3 11.2 23.7 16l2.1 1.3c12.4 8 23.7 14.9 34.1 20.8 8.6 5 17 9.8 25 14.1.4.2 1 .5 1.5.8 34.2 18.4 65.6 32.1 90.9 39.7 11 3.9 21.3 7.3 30.6 10.1 22.1 7.1 46.1 12.6 70.8 16.5.1 1.5.1 3 0 4.4z"/><path d="M710.6 411.2 476.1 651.6l-120-123c-8.3-8.5-21.8-8.5-30.1 0s-8.3 22.3 0 30.9L461.1 698c4.2 4.3 9.6 6.4 15.1 6.4 5.4 0 10.9-2.1 15-6.4l249.5-255.7c8.3-8.5 8.3-22.3 0-30.9-8.3-8.7-21.8-8.7-30.1-.2z"/></svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M832.128 768c33.195 0 60.501 25.173 63.573 57.813L896 832a64 64 0 0 1-63.872 64H533.205a63.787 63.787 0 0 1-63.872-64 64 64 0 0 1 63.872-64h298.923zM213.333 874.667c-23.722 0-42.666-19.072-42.666-42.624V362.667A42.667 42.667 0 0 1 213.333 320l4.992.299C239.66 322.73 256 340.779 256 362.624l-.043 128.043h128.299c21.248 0 39.595 16.469 42.112 37.674l.299 4.992-.299 4.992A42.368 42.368 0 0 1 384.256 576H256l.043 213.333h128.256c22.869 0 42.41 19.115 42.41 42.667l-.298 4.992a42.368 42.368 0 0 1-42.112 37.675zm618.795-405.334c33.195 0 60.501 25.174 63.573 57.814l.299 6.186a64 64 0 0 1-63.872 64H533.205a63.787 63.787 0 0 1-63.872-64 64 64 0 0 1 63.872-64h298.923zM576.171 128c33.194 0 60.458 25.173 63.573 57.813L640 192c0 35.328-29.013 64-63.83 64H191.83A63.744 63.744 0 0 1 128 192c0-35.328 29.013-64 63.83-64h384.34z"/></svg>
|
After Width: | Height: | Size: 941 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M962.184 55.874H61.818C27.732 55.874 0 83.606 0 117.692v621.64c0 34.086 27.732 61.818 61.818 61.818h308.52v44.98c0 41.234-33.547 74.782-74.781 74.782h-67.995c-13.036 0-23.606 10.568-23.606 23.606 0 13.038 10.57 23.606 23.606 23.606h568.874c13.036 0 23.606-10.568 23.606-23.606 0-13.038-10.57-23.606-23.606-23.606h-67.997c-41.234 0-74.782-33.548-74.782-74.782v-44.978h308.52c34.087 0 61.821-27.732 61.821-61.819v-621.64c.004-34.087-27.728-61.819-61.814-61.819zM391.84 920.916c16.092-20.672 25.714-46.616 25.714-74.782v-44.98h188.894v44.98c0 28.166 9.622 54.112 25.714 74.782H391.841zm584.95-181.583c0 8.054-6.552 14.608-14.608 14.608H61.818c-8.054 0-14.608-6.552-14.608-14.608V615.267h929.58v124.066zm0-171.28H47.212v-450.36c0-8.055 6.552-14.609 14.608-14.609h900.362c8.054 0 14.61 6.552 14.61 14.608v450.361z"/><path d="M486.531 684.611a25.476 25.476 0 1 0 50.952 0 25.476 25.476 0 1 0-50.952 0zm65.946-466.103c-9.22-9.218-24.162-9.218-33.386 0L352.263 385.337c-9.218 9.218-9.218 24.166 0 33.386a23.534 23.534 0 0 0 16.694 6.914 23.526 23.526 0 0 0 16.692-6.914l166.828-166.829c9.218-9.218 9.218-24.166 0-33.386zm98.88 96.679c-9.216-9.218-24.158-9.218-33.384-.002l-66.46 66.456c-9.218 9.22-9.218 24.168 0 33.386a23.53 23.53 0 0 0 16.692 6.914c6.04 0 12.082-2.304 16.692-6.914l66.46-66.456c9.218-9.218 9.218-24.166 0-33.384z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="1em" height="1em" viewBox="0 0 36 36"><path d="m19.41 18 8.29-8.29a1 1 0 0 0-1.41-1.41L18 16.59l-8.29-8.3a1 1 0 0 0-1.42 1.42l8.3 8.29-8.3 8.29A1 1 0 1 0 9.7 27.7l8.3-8.29 8.29 8.29a1 1 0 0 0 1.41-1.41z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 297 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="1em" height="1em" viewBox="0 0 36 36"><path d="M26 17H10a1 1 0 0 0 0 2h16a1 1 0 0 0 0-2z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 183 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="m7 12 7 7m-7-7 7-7" stroke-linejoin="round"/><path d="M21 12H7.5"/><path d="M3 3v18" stroke-linejoin="round"/></g></svg>
|
After Width: | Height: | Size: 310 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20"><path d="M3 5h14V3H3v2zm12 8V7H5v6h10zM3 17h14v-2H3v2z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 187 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="m17 12-7 7m7-7-7-7" stroke-linejoin="round"/><path d="M3 12h13.5"/><path d="M21 3v18" stroke-linejoin="round"/></g></svg>
|
After Width: | Height: | Size: 311 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M449.6 116.2H303.8c-14.2 0-25.7-11.5-25.7-25.7s11.5-25.7 25.7-25.7h145.8c14.2 0 25.7 11.5 25.7 25.7s-11.5 25.7-25.7 25.7zm0 0"/><path d="M160.1 859.3c-14.2 0-25.7-11.5-25.7-25.7V167.4c0-56.6 46-102.6 102.6-102.6h66.8c14.2 0 25.7 11.5 25.7 25.7s-11.5 25.7-25.7 25.7H237c-28.2 0-51.1 22.9-51.1 51.1v666.2c-.1 14.3-11.6 25.8-25.8 25.8zm373.5-512.6c-6.3 0-12.4-1.3-17.6-3.5-13.5-5.8-21.9-17.9-21.9-31.6v-221c0-14.2 11.5-25.7 25.7-25.7s25.7 11.5 25.7 25.7v189l27.7-26.6c14.1-13.5 36.1-13.5 50.1 0l22.1 21.3V90.5c0-14.2 11.5-25.7 25.7-25.7s25.7 11.5 25.7 25.7v219.6c0 14.5-8.6 27.5-22 33.2-13.3 5.7-28.7 2.9-39.2-7.2l-37.5-36-37.5 36c-7.6 7.6-17.5 10.6-27 10.6zm0 0"/><path d="M846.1 958.9H236.9c-56.6 0-102.6-46-102.6-102.6v-22.8c0-14.2 11.5-25.7 25.7-25.7s25.7 11.5 25.7 25.7v22.8c0 28.2 22.9 51.1 51.1 51.1H846c14.2 0 25.7 11.5 25.7 25.7.1 14.3-11.4 25.8-25.6 25.8zm0 0"/><path d="M160.1 876h-.9c-14.2-.5-25.3-12.4-24.8-26.6 1-28.2 6.3-48.5 16.7-63.6 13.8-20.1 35.4-30.3 64.3-30.3h615c3.2-2.7 6.4-6.1 8.6-8.6V133.1c-1.8-5.1-11.7-15-16.8-16.8H449.6c-14.2 0-25.7-11.5-25.7-25.7s11.5-25.7 25.7-25.7h373.6c19.8 0 36.7 13.9 45 22.2 8.3 8.3 22.2 25.2 22.2 45v621.6c0 10.8-6.2 19.6-12.3 26.7-4.6 5.4-10.3 11-15.6 15.4-1 .9-2.1 1.7-3.2 2.5-5.4 4.1-12.9 8.8-22.3 8.8H215.3c-15 0-28 0-29.5 44.2-.5 13.8-11.9 24.7-25.7 24.7zm0 0"/><path d="M284.4 806.4c-14.2 0-25.7-11.5-25.7-25.7V90.5c0-14.2 11.5-25.7 25.7-25.7s25.7 11.5 25.7 25.7v690.1c0 14.3-11.5 25.8-25.7 25.8zM844.9 959h-1.6c-6.6-.3-30-2.3-52.2-16.9-19.5-12.7-42.6-38-42.6-86.3 0-62.3 35.7-101 93.1-101 14.2 0 25.7 11.5 25.7 25.7s-11.5 25.7-25.7 25.7c-12.5 0-41.7 0-41.7 49.6 0 21 6.6 35.3 20.1 43.8 10.6 6.6 22.1 7.8 25 8 1.4-.1 2.9 0 4.4.2 13.7 1.7 23.6 14 22.5 27.7-.9 9.5-8.8 23.5-27 23.5zm-1.8-51.3c-1.1.1-2.3.3-3.4.6 1.1-.3 2.2-.5 3.4-.6zm0 0"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M832.1 185.1H609.4l-17.1-62c-9.6-34.6-40.5-58.8-75.3-58.8H196c-43.2 0-78.3 36.4-78.3 81.1V897c0 35.3 28.7 64 64 64H832c35.3 0 64-28.7 64-64V249c.1-35.2-28.6-63.9-63.9-63.9zm-644.4-39.7c0-6.6 4.4-11.1 8.3-11.1h321c3.4 0 6.6 3.1 7.8 7.4l12 43.4H187.7v-39.7zm638.4 745.8H187.7V255.1h638.4v636.1z"/><path d="M311.1 415.1a35 35 0 1 0 70 0 35 35 0 1 0-70 0zm151.2-35h257.8v70H462.3zM311.1 582.3a35 35 0 1 0 70 0 35 35 0 1 0-70 0zm151.2-35h257.8v70H462.3zM311.1 749.5a35 35 0 1 0 70 0 35 35 0 1 0-70 0zm151.2-35h257.8v70H462.3z"/></svg>
|
After Width: | Height: | Size: 640 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M624 706.3h-74.1V464c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v242.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.7c3.2 4.1 9.4 4.1 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9z"/><path d="M811.4 366.7C765.6 245.9 648.9 160 512.2 160S258.8 245.8 213 366.6C127.3 389.1 64 467.2 64 560c0 110.5 89.5 200 199.9 200H304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8h-40.1c-33.7 0-65.4-13.4-89-37.7-23.5-24.2-36-56.8-34.9-90.6.9-26.4 9.9-51.2 26.2-72.1 16.7-21.3 40.1-36.8 66.1-43.7l37.9-9.9 13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4 14.9-19.2 32.6-35.9 52.4-49.9 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9 15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5 37.8 10C846.1 454.5 884 503.8 884 560c0 33.1-12.9 64.3-36.3 87.7-23.4 23.4-54.5 36.3-87.6 36.3H720c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h40.1C870.5 760 960 670.5 960 560c0-92.7-63.1-170.7-148.6-193.3z"/></svg>
|
After Width: | Height: | Size: 962 B |
|
@ -0,0 +1 @@
|
|||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373 114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>
|
After Width: | Height: | Size: 817 B |
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 944 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"/></svg>
|
After Width: | Height: | Size: 175 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M8 3v2H4v4H2V3h6zM2 21v-6h2v4h4v2H2zm20 0h-6v-2h4v-4h2v6zm0-12h-2V5h-4V3h6v6z"/></svg>
|
After Width: | Height: | Size: 175 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M511.543 14.057C228.914 13.943 0 242.743 0 525.143 0 748.457 143.2 938.286 342.629 1008c26.857 6.743 22.742-12.343 22.742-25.371v-88.572C210.286 912.23 204 809.6 193.6 792.457c-21.029-35.886-70.743-45.028-55.886-62.171 35.315-18.172 71.315 4.571 113.029 66.171 30.171 44.686 89.028 37.143 118.857 29.714 6.514-26.857 20.457-50.857 39.657-69.485C248.571 727.886 181.6 629.829 181.6 513.257c0-56.571 18.629-108.571 55.2-150.514-23.314-69.143 2.171-128.343 5.6-137.143 66.4-5.943 135.429 47.543 140.8 51.771C420.914 267.2 464 261.83 512.229 261.83c48.457 0 91.657 5.6 129.714 15.885 12.914-9.828 76.914-55.771 138.628-50.171 3.315 8.8 28.229 66.628 6.286 134.857 37.029 42.057 55.886 94.514 55.886 151.2 0 116.8-67.429 214.971-228.572 243.314a145.714 145.714 0 0 1 43.543 104v128.572c.915 10.285 0 20.457 17.143 20.457 202.4-68.229 348.114-259.429 348.114-484.686 0-282.514-229.028-511.2-511.428-511.2z"/></svg>
|
After Width: | Height: | Size: 1019 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M958.401 451.55a20.01 20.01 0 0 0-6.966-14.972L524.345 69.511c-7.499-6.446-18.581-6.446-26.08 0L309.583 231.676V129.657c0-11.05-8.902-19.533-19.952-19.533h-88.034c-11.048 0-19.928 8.482-19.928 19.533v211.954L71.176 436.578a20.003 20.003 0 0 0-6.968 15.174v105.5a20.007 20.007 0 0 0 33.052 15.172l53.298-45.826V850.7c0 60.678 49.364 110.042 110.042 110.042h504.192c60.678 0 110.043-49.364 110.043-110.042V527.026l51.586 44.336a20.001 20.001 0 0 0 21.48 2.966 20.006 20.006 0 0 0 11.566-18.343l-1.066-104.436zM221.579 150.033h48.095v115.942l-48.095 41.336V150.034zm349.14 770.692H436.665V700.642c0-11.03 8.977-20.007 20.008-20.007h94.036c11.03 0 20.007 8.976 20.007 20.007v220.084zm264.1-424.83v354.803c0 38.612-31.415 70.027-70.028 70.027H610.733V700.642c0-33.096-26.927-60.023-60.023-60.023h-94.036c-33.097 0-60.023 26.927-60.023 60.023v220.085H260.599c-38.612 0-70.027-31.415-70.027-70.027V495.895a20.07 20.07 0 0 0-.315-3.432L512.37 215.504l322.703 277.349a20.158 20.158 0 0 0-.255 3.042zM525.41 173.947c-7.502-6.446-18.587-6.447-26.086.003l-395.1 339.714v-52.727l407.081-349.87 407.177 349.952.522 51.205L525.41 173.948z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M3 4h18v2H3V4zm0 15h18v2H3v-2zm8-5h10v2H11v-2zm0-5h10v2H11V9zm-8 3.5L7 9v7l-4-3.5z"/></svg>
|
After Width: | Height: | Size: 180 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#FD8E66"/><path d="M377.745 354.306h63.05l-78.638 315.388h-63.05l78.638-315.388zm140.642 0h103.519c69.926 0 117.527 24.3 98.942 98.896-17.958 72.017-80.148 104.401-147.89 104.401H530.77L502.8 669.694h-63.05l78.637-315.388zm62.702 153.443c43.489 0 68.927-18.329 77.964-54.547 9.153-36.659-10.779-49.018-54.222-49.018h-35.823l-25.833 103.565h37.914z" fill="#FFF"/></svg>
|
After Width: | Height: | Size: 535 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="m18.5 10 4.4 11h-2.155l-1.201-3h-4.09l-1.199 3h-2.154L16.5 10h2zM10 2v2h6v2h-1.968a18.221 18.221 0 0 1-3.62 6.301 14.865 14.865 0 0 0 2.335 1.707l-.75 1.878A17.016 17.016 0 0 1 9 13.725a16.677 16.677 0 0 1-6.201 3.548l-.536-1.929a14.7 14.7 0 0 0 5.327-3.042A18.078 18.078 0 0 1 4.767 8h2.24A16.031 16.031 0 0 0 9 10.877a16.165 16.165 0 0 0 2.91-4.876L2 6V4h6V2h2zm7.5 10.885L16.253 16h2.492L17.5 12.885z"/></svg>
|
After Width: | Height: | Size: 501 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M611.2 368 316.8 662.4c-6.4 6.4-9.6 16-9.6 22.4s3.2 16 9.6 22.4 16 9.6 22.4 9.6 16-3.2 22.4-9.6L656 412.8c6.4-6.4 9.6-16 9.6-22.4s-3.2-16-9.6-22.4c-12.8-12.8-32-12.8-44.8 0z"/><path d="m608 755.2-99.2 99.2c-96 96-249.6 96-342.4 3.2-92.8-92.8-92.8-246.4 3.2-342.4l99.2-99.2c12.8-12.8 12.8-32 0-44.8s-32-12.8-44.8 0l-99.2 99.2C3.2 592 3.2 784 121.6 902.4 179.2 960 259.2 992 336 992c80 0 156.8-28.8 217.6-89.6l99.2-99.2c12.8-12.8 12.8-32 0-44.8s-32-16-44.8-3.2zm294.4-633.6C844.8 64 771.2 35.2 688 35.2h-3.2c-83.2 0-160 35.2-217.6 92.8l-96 96c-12.8 12.8-12.8 32 0 44.8s32 12.8 44.8 0l96-96c48-48 108.8-73.6 172.8-73.6h3.2c64 0 121.6 25.6 166.4 70.4 92.8 92.8 92.8 246.4-3.2 345.6l-96 96c-12.8 12.8-12.8 32 0 44.8 6.4 6.4 16 9.6 22.4 9.6s16-3.2 22.4-9.6l96-96c121.6-124.8 124.8-320 6.4-438.4z"/></svg>
|
After Width: | Height: | Size: 909 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 1024 1024"><path fill="currentColor" d="M224 448a32 32 0 0 0-32 32v384a32 32 0 0 0 32 32h576a32 32 0 0 0 32-32V480a32 32 0 0 0-32-32zm0-64h576a96 96 0 0 1 96 96v384a96 96 0 0 1-96 96H224a96 96 0 0 1-96-96V480a96 96 0 0 1 96-96"/><path fill="currentColor" d="M512 544a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V576a32 32 0 0 1 32-32m192-160v-64a192 192 0 1 0-384 0v64zM512 64a256 256 0 0 1 256 256v128H256V320A256 256 0 0 1 512 64"/></svg>
|
After Width: | Height: | Size: 512 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M374.272 440.832H127.488c-33.792 0-61.44-27.648-61.44-61.44V132.608c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296c-.512 33.792-27.648 60.928-61.952 60.928zM127.488 132.608v247.296h247.296V132.608H127.488zM762.88 492.032c-16.384 0-31.744-6.144-43.52-17.92L544.768 299.52c-11.776-11.776-17.92-27.136-17.92-43.52s6.144-31.744 17.92-43.52L719.36 37.888c11.776-11.776 27.136-17.92 43.52-17.92s31.744 6.144 43.52 17.92L980.992 212.48c11.776 11.776 17.92 27.136 17.92 43.52s-6.144 31.744-17.92 43.52L806.4 474.112c-11.776 11.776-27.136 17.92-43.52 17.92zm0-410.624L588.288 256 762.88 430.592 937.472 256 762.88 81.408zM374.272 952.832H127.488c-33.792 0-61.44-27.648-61.44-61.44V644.096c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296c-.512 34.304-27.648 61.44-61.952 61.44zM127.488 644.608v247.296h247.296V644.608H127.488zm758.784 308.224H638.976c-33.792 0-61.44-27.648-61.44-61.44V644.096c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296c0 34.304-27.136 61.44-61.44 61.44zM639.488 644.608v247.296h247.296V644.608H639.488z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#50DCB6"/><path d="M790.148 502.822c0-30.856-25.013-55.87-55.869-55.87H483.666c-30.855 0-55.869 25.014-55.869 55.87v158.432c0 30.856 25.014 55.87 55.87 55.87h229.557a47.894 47.894 0 0 1 26.28 7.854l35.81 23.508c6.37 4.18 14.834-.388 14.834-8.007V502.822z" fill="#FFF" fill-opacity=".4"/><path d="M233.852 320.848c0-30.856 25.013-55.869 55.869-55.869h366.341c30.856 0 55.87 25.013 55.87 55.869v245.026c0 30.855-25.014 55.869-55.87 55.869h-325.28a47.894 47.894 0 0 0-26.295 7.865l-55.799 36.66c-6.369 4.185-14.836-.384-14.836-8.004V320.848z" fill="#FFF"/><path d="M323.242 446.952a34.32 34.32 0 1 0 68.64 0 34.32 34.32 0 1 0-68.64 0zm115.729 0a34.32 34.32 0 1 0 68.64 0 34.32 34.32 0 1 0-68.64 0zm115.729 0a34.32 34.32 0 1 0 68.639 0 34.32 34.32 0 1 0-68.64 0z" fill="#46D7B0"/></svg>
|
After Width: | Height: | Size: 949 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#FF822B"/><path d="M324.409 655.019c180.881 0 327.51-146.631 327.51-327.51 0-152.138-103.734-280.047-244.33-316.854C205.813 52.464 47.496 213.018 8.986 415.982c38.6 137.898 165.196 239.037 315.422 239.037z" fill="#FFF" fill-opacity=".2"/><path d="M512 1024c282.767 0 512-229.233 512-512 0-31.766-2.891-62.854-8.434-93.019-87.509-82.881-205.691-133.718-335.742-133.718-269.71 0-488.357 218.645-488.357 488.357 0 54.96 9.084 107.803 25.823 157.104C300.627 989.489 402.283 1024 512 1024z" fill="#FFF" fill-opacity=".15"/><path d="M732.536 756.566c36.39 0 65.89-29.5 65.89-65.89 0 36.39 29.502 65.89 65.889 65.89-17.054 0-65.89 29.503-65.89 65.89 0-36.387-29.5-65.89-65.889-65.89zM159.686 247.28c25.686 0 46.51-20.823 46.51-46.51 0 25.687 20.823 46.51 46.51 46.51-12.037 0-46.51 20.824-46.51 46.51 0-25.686-20.824-46.51-46.51-46.51z" fill="#FFF" fill-opacity=".5"/><path d="M206.195 333.323c8.563 0 15.504-6.94 15.504-15.503 0 8.562 6.94 15.503 15.503 15.503-4.012 0-15.503 6.941-15.503 15.504 0-8.563-6.941-15.504-15.504-15.504z" fill="#FFF" fill-opacity=".3"/><path d="M802.301 726.987c0 8.11-1.387 15.686-4.155 22.728-2.775 7.043-6.713 13.232-11.829 18.566-5.116 5.336-11.085 9.494-17.905 12.486-6.821 2.984-14.281 4.48-22.38 4.48H281.805c-8.1 0-15.773-1.496-23.019-4.48-7.247-2.992-13.641-7.15-19.183-12.486-5.542-5.334-9.912-11.523-13.108-18.566-3.198-7.042-4.796-14.618-4.796-22.728v-319.47c0-16.218 5.648-29.983 16.945-41.294 11.296-11.31 25.044-16.965 41.243-16.965h464.226c16.199 0 29.947 5.655 41.243 16.965 11.294 11.311 16.945 25.076 16.945 41.295v87.07h-145.15c-16.2 0-29.947 5.548-41.243 16.645-11.297 11.098-16.946 24.755-16.946 40.974.427 11.098 2.772 20.914 7.034 29.45 3.41 7.256 9.059 13.872 16.945 19.847 7.886 5.976 19.29 8.964 34.21 8.964H802.3v116.52zm-86.962-407.18H425.038c23.019-11.95 44.76-23.474 65.222-34.571a6020.558 6020.558 0 0 0 53.072-28.17c17.478-9.39 31.119-16.646 40.924-21.768 14.92-8.109 28.241-11.844 39.964-11.203 11.723.64 21.634 2.667 29.734 6.082 9.378 4.694 17.478 10.883 24.298 18.566l37.087 71.064zm-86.963 232.4c0-8.109 2.77-14.938 8.313-20.487 5.542-5.548 12.362-8.323 20.462-8.323s14.92 2.775 20.461 8.323c5.543 5.549 8.313 12.378 8.313 20.487 0 8.11-2.77 15.046-8.313 20.807-5.542 5.762-12.362 8.644-20.461 8.644-8.1 0-14.92-2.882-20.462-8.644-5.542-5.761-8.313-12.697-8.313-20.807z" fill="#FFF"/></svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><defs><style>@font-face{font-family:rbicon;src:url(chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2) format("woff2");font-weight:400;font-style:normal}</style></defs><path d="M64 64v576h832V64H64zM0 0h960v704H0V0z"/><path d="M192 896h576v64H192zm256-256h64v256h-64zm31.232-78.396 309.99-348.33-47.803-42.548-259.567 291.67-177.895-222.387L163.21 438.605l52.224 37.009 91.622-129.28z"/></svg>
|
After Width: | Height: | Size: 525 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.98 6.98 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></svg>
|
After Width: | Height: | Size: 272 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#F15BB5"/><path d="m772 432.8-7.9-63.3v-.2c-.1-1-.4-2.1-.7-3.1v-.3l-.2-.4-25.7-72.1c-.3-.7-.6-1.5-1-2.2-7-14.4-21.5-23.7-37.5-24h-24.1c-10 0-18.6 7.8-18.9 17.8-.4 10.5 8 19.1 18.4 19.1H699c1.9.3 3.4 1.4 4.2 3.1l13.5 37.9c.7 1.9.4 3.9-.7 5.6-1.1 1.6-3 2.6-5 2.6h-67.3c-3.5 0-6.2-2.8-6.2-6.2v-24.5c0-67.9-55.1-123-123-123s-123 55.1-123 123v24.5c0 3.5-2.8 6.2-6.2 6.2h-69.8c-3.4 0-6.2-2.8-6.2-6.2 0-.8.2-1.6.5-2.3l15.5-36.8c.7-2.1 2.5-3.5 4.7-3.9h24.1c10 0 18.6-7.8 18.9-17.8.4-10.5-8-19.1-18.4-19.1H330c-16.5.5-31.3 10.2-38.1 25.3l-30.6 72.1v.2c-.2.4-.3.8-.5 1.2v.4c-.3 1-.6 2.1-.7 3.1l-39 310.8c-6.4 50.5 29.5 96.7 80.1 103 3.8.5 7.7.7 11.5.7h94.5C514.8 718.5 680.7 597.9 772 432.8zM440.7 322.5c0-41 33.3-74.1 74.3-73.8 40.7.3 73.3 34.1 73.3 74.8V347c0 3.5-2.8 6.2-6.2 6.2H446.9c-3.5 0-6.2-2.8-6.2-6.2v-24.5zm152.7 257L514 662.4c-2.3 2.4-6.3 2.5-8.7.2l-.2-.2-79.4-82.9c-15.1-15.1-18.8-38.2-9.3-57.3 13.4-26.8 47.7-36.1 73.3-18.2 2.3 1.6 4.4 3.5 6.4 5.5l9.2 9.2c2.4 2.4 6.3 2.4 8.7 0l9.4-9.4c19.4-19.4 50.8-19.4 70.2 0 19.3 19.4 19.3 50.8-.2 70.2z" fill="#FFFDF3"/><path d="M803.7 691.6c0-3.8-.3-7.7-.7-11.4l-31-247.4c-91.3 165.1-257.2 285.7-364.8 351h304.1c51 0 92.4-41.2 92.4-92.2z" fill="#FFF" opacity=".9"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M520.235 504.577c51.305-33.512 85.254-91.477 85.254-157.301 0-103.485-84.273-187.757-187.758-187.757S229.974 243.79 229.974 347.275c0 65.715 33.95 123.68 85.255 157.301-47.266 15.72-90.603 42.245-126.845 78.486-61.24 61.24-94.97 142.675-94.97 229.239 0 13.318 10.698 24.016 24.015 24.016s24.015-10.699 24.015-24.016c0-152.28 123.899-276.177 276.178-276.177S693.801 660.02 693.801 812.302c0 13.318 10.697 24.016 24.014 24.016s24.016-10.699 24.016-24.016c0-86.564-33.73-168-94.97-229.237-36.024-36.134-79.36-62.768-126.627-78.488zm-242.23-157.192c0-77.067 62.658-139.726 139.726-139.726s139.727 62.658 139.727 139.726-62.66 139.726-139.727 139.726c-76.958 0-139.726-62.659-139.726-139.726z"/><path d="M871.406 599.002a323.731 323.731 0 0 0-150.643-119.86c27.072-40.28 41.481-87.765 41.045-136.778-.437-59.602-22.706-116.694-62.55-160.795-8.95-9.824-24.124-10.589-33.95-1.637-9.823 8.951-10.587 24.125-1.637 33.949 66.7 73.575 67.135 185.138.983 259.477-1.528 1.746-2.837 3.71-3.712 5.675-1.2 1.856-2.074 3.93-2.729 6.222-3.492 12.773 4.15 25.981 16.92 29.474C807.11 550.097 892.366 674.323 878.065 809.9c-1.419 13.208 8.188 24.998 21.396 26.417.873.109 1.746.109 2.51.109 12.117 0 22.597-9.17 23.907-21.504 7.968-75.868-11.353-152.608-54.471-215.921z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#69ADF4"/><path d="m562.058 278.412 42.198-12.096q35.292-10.117 45.409 25.176l117.221 408.934q10.117 35.292-25.176 45.409l-42.197 12.096q-35.293 10.117-45.41-25.176l-117.22-408.934q-10.118-35.292 25.175-45.41z" fill="#FFF" opacity=".4"/><path d="M284.134 263.383c-20.276 0-36.714 16.437-36.714 36.714v43.897a7.981 7.981 0 0 0 7.981 7.981h55.47c13.444 0 24.343 10.899 24.343 24.343s-10.899 24.343-24.343 24.343h-55.47a7.981 7.981 0 0 0-7.98 7.981V725.5c0 20.276 16.437 36.714 36.713 36.714h43.897c20.276 0 36.714-16.438 36.714-36.714V300.097c0-20.277-16.438-36.714-36.714-36.714h-43.897zm152.443 0c-20.276 0-36.714 16.437-36.714 36.714v122.912a7.981 7.981 0 0 0 7.981 7.98h49.085c13.444 0 24.343 10.9 24.343 24.344s-10.9 24.343-24.343 24.343h-49.085a7.981 7.981 0 0 0-7.981 7.981V725.5c0 20.276 16.438 36.714 36.714 36.714h43.897c20.276 0 36.714-16.438 36.714-36.714V300.097c0-20.277-16.438-36.714-36.714-36.714h-43.897z" fill="#FFF"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M924.146 233.09v-3.216l-.643-.642v-2.572l-.642-.642v-.643l-.643-.643h-.643l-.642-.642v-.643l-1.93-1.929v-.642l-.642-.643v-1.286h-1.929l-.643-.643h-1.286l-.642-.643v-.642h-.643v-.643h-.643l-.642-.643h-.643v-.642h-2.572v-.643h-5.144l-.642.643h-.643v.642h-1.286l-.643.643h-1.286L112.708 516c-10.287 3.858-15.431 14.789-11.574 25.719 2.572 5.787 7.073 9.644 12.217 11.573l235.972 94.518 24.433 135.668c1.93 10.931 12.217 17.36 22.505 16.074 4.5-.643 8.358-3.215 10.931-6.43l87.445-87.444 178.104 71.37c10.287 3.858 21.218-.642 25.719-10.287l223.756-523.383.642-.643v-7.073l1.288-2.571zM364.113 610.517 173.79 534.646l604.399-230.83-414.077 306.7zm41.15 127.952-12.86-74.586 62.369 25.076-49.509 49.51zm264.906-5.143L406.55 627.877 858.562 293.53 670.17 733.326z"/></svg>
|
After Width: | Height: | Size: 877 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="1em" height="1em" viewBox="0 0 512 512"><path d="m400 148-21.12-24.57A191.43 191.43 0 0 0 240 64C134 64 48 150 48 256s86 192 192 192a192.09 192.09 0 0 0 181.07-128" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="32"/><path d="M464 68.45V220a4 4 0 0 1-4 4H308.45a4 4 0 0 1-2.83-6.83L457.17 65.62a4 4 0 0 1 6.83 2.83z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 458 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="256" height="256"><path d="M79.238 961.896v-25.442c0-109.28 28.835-214.892 81.139-297.416 48.427-76.396 115.304-131.573 195.508-161.896A240.785 240.785 0 0 1 279.488 300.5c0-131.538 104.331-238.535 232.547-238.535S744.546 168.962 744.546 300.5a240.854 240.854 0 0 1-76.742 176.988c190.87 73.004 276.992 277.131 276.992 458.966v25.442H79.238zM694.908 300.5c0-103.43-82.039-187.615-182.873-187.615-100.835 0-182.873 84.184-182.873 187.615 0 103.465 82.038 187.65 182.873 187.65 100.834 0 182.873-84.185 182.873-187.65zm-79.166 213.508a226.454 226.454 0 0 1-103.707 25.096A225.935 225.935 0 0 1 407.912 513.8C212.888 564.927 136.804 752.854 129.5 910.977h765.035c-7.997-167.4-95.227-347.746-278.793-396.97zm-143.411 37.246h79.407l39.739-8.48-45.242 65.664 30.6 227.527-64.8 56.908-69.197-56.908 40.535-227.527-50.78-65.665 39.738 8.48z"/></svg>
|
After Width: | Height: | Size: 925 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="m586.667 494.933-8.534 40.534L533.333 416l-19.2 2.133-29.866 57.6H448l-8.533 19.2-25.6 51.2L384 473.6l-17.067 6.4 38.4 93.867H422.4l38.4-78.934h36.267l27.733-51.2L571.733 569.6h19.2l14.934-74.667h51.2v-19.2h-66.134l-4.266 19.2zM512 283.733c-117.333 0-213.333 96-213.333 213.334S394.667 710.4 512 710.4s213.333-96 213.333-213.333c0-119.467-96-213.334-213.333-213.334zm0 401.067c-104.533 0-189.867-85.333-189.867-189.867S407.467 305.067 512 305.067 701.867 390.4 701.867 494.933 616.533 684.8 512 684.8zM893.867 224c-44.8-14.933-134.4-44.8-179.2-66.133C625.067 113.067 590.933 83.2 556.8 57.6 544 46.933 529.067 42.667 512 42.667s-32 4.266-44.8 12.8c-34.133 25.6-68.267 55.466-157.867 100.266-44.8 21.334-134.4 51.2-179.2 66.134-29.866 10.666-46.933 36.266-44.8 66.133 8.534 98.133 32 288 89.6 405.333 68.267 134.4 236.8 238.934 302.934 279.467 10.666 6.4 21.333 8.533 34.133 8.533s23.467-2.133 34.133-8.533c66.134-40.533 234.667-145.067 302.934-279.467 57.6-117.333 81.066-307.2 89.6-401.066 2.133-32-17.067-59.734-44.8-68.267zm-83.2 450.133C768 759.467 672 846.933 524.8 936.533c-4.267 0-8.533 2.134-12.8 2.134s-8.533-2.134-10.667-4.267C352 846.933 256 757.333 213.333 674.133 153.6 554.667 132.267 347.733 128 288c-2.133-14.933 12.8-21.333 17.067-23.467l17.066-6.4c49.067-17.066 125.867-42.666 168.534-64 81.066-40.533 119.466-70.4 151.466-93.866 4.267-4.267 8.534-6.4 12.8-10.667 0-2.133 6.4-4.267 14.934-4.267h4.266c8.534 0 14.934 2.134 17.067 4.267 4.267 4.267 8.533 6.4 12.8 10.667 29.867 23.466 68.267 53.333 151.467 93.866 42.666 21.334 117.333 46.934 168.533 64l17.067 6.4c4.266 2.134 17.066 6.4 17.066 23.467-6.4 59.733-27.733 266.667-87.466 386.133z"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="m12 1 9.5 5.5v11L12 23l-9.5-5.5v-11L12 1zm0 2.311L4.5 7.653v8.694l7.5 4.342 7.5-4.342V7.653L12 3.311zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></svg>
|
After Width: | Height: | Size: 267 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10 6v15H8V6H2V4h14v2h-6zm8 8v7h-2v-7h-3v-2h8v2h-3z"/></svg>
|
After Width: | Height: | Size: 149 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85 1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></svg>
|
After Width: | Height: | Size: 473 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M139 669.6V164.3c0-12.7 10.3-23.1 23.1-23.1h694.4c12.7 0 23.1 10.4 23.1 23.1v248.5h70V164.3c0-51.3-41.8-93.1-93.1-93.1H162c-51.3.1-93 41.8-93 93.1v505.3c0 51.3 41.8 93.1 93.1 93.1h224.7v-70H162c-12.7 0-23-10.4-23-23.1zm-34.3 131h282v70h-282z"/><path d="m954.9 599.4-5.1-15c-11.5-33.9-29.4-64.9-53.2-91.9l-10.5-11.9h-83.2l-41.7-72.2-15.6-3.1c-34.8-6.9-71.3-6.9-106.1 0l-15.6 3.1-41.7 72.2H499l-10.5 11.9c-23.8 27.1-41.7 58-53.2 91.9l-5.1 15 41.7 72.2-41.7 72.2 5.1 15c11.5 33.9 29.4 64.9 53.2 91.9l10.5 11.9h83.2l41.7 72.2 15.6 3.1c17.4 3.5 35.3 5.2 53.1 5.2s35.6-1.8 53.1-5.2l15.6-3.1 41.7-72.2h83.2l10.5-11.9c23.8-27.1 41.7-58 53.2-91.9l5.1-15-41.7-72.2 41.6-72.2zm-76.8 151.2c-6.4 14.9-14.5 29-24.3 42h-91.2l-45.6 79c-16.1 1.9-32.4 1.9-48.5 0l-45.6-79h-91.2c-9.8-13-17.9-27-24.3-42l45.6-79.1-45.6-79.1c6.4-14.9 14.5-29 24.3-42h91.2l45.6-79c16.1-1.9 32.4-1.9 48.5 0l45.6 79h91.2c9.8 13 17.9 27 24.3 42l-45.6 79.1 45.6 79.1z"/><path d="M692.7 560.2c-61.4 0-111.3 49.9-111.3 111.3s49.9 111.3 111.3 111.3S804 732.9 804 671.5c0-61.3-49.9-111.3-111.3-111.3zm0 152.7c-22.8 0-41.3-18.5-41.3-41.3s18.5-41.3 41.3-41.3 41.3 18.5 41.3 41.3-18.5 41.3-41.3 41.3z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 64v896h1024V64H0zm384 576V448h256v192H384zm256 64v192H384V704h256zm0-512v192H384V192h256zm-320 0v192H64V192h256zM64 448h256v192H64V448zm640 0h256v192H704V448zm0-64V192h256v192H704zM64 704h256v192H64V704zm640 192V704h256v192H704z"/></svg>
|
After Width: | Height: | Size: 351 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M0 512a512 512 0 1 0 1024 0A512 512 0 1 0 0 512z" fill="#FF8F41"/><path d="M466.107 217.091h93.382q29.53 0 29.53 29.531v51.08q0 29.531-29.53 29.531h-93.381q-29.531 0-29.531-29.53v-51.081q0-29.53 29.53-29.53zm253.476 125.01 40.039-17.701c3.508-1.55 4.021-6.316.924-8.579l-55.735-40.715c-3.096-2.263-7.481-.325-7.89 3.488l-4.477 41.544-58.874 80.592 28.157 20.568 57.856-79.197z" fill="#FFF" opacity=".4"/><path d="M263.383 521.178a249.016 249.016 0 1 0 498.032 0 249.016 249.016 0 1 0-498.032 0z" fill="#FFF"/><path d="M512.4 348.383c9.917 0 17.957 8.04 17.957 17.958v143.565l97.432 55.102c8.634 4.882 11.674 15.839 6.792 24.471-4.882 8.634-15.84 11.674-24.472 6.791l-106.55-60.258a17.958 17.958 0 0 1-9.118-15.632V366.341c0-9.917 8.04-17.958 17.958-17.958z" fill="#FF8F41"/><path d="M472.493 524.371a40.705 40.705 0 1 0 81.409 0 40.705 40.705 0 1 0-81.41 0z" fill="#FF8338"/></svg>
|
After Width: | Height: | Size: 992 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M977.455 558.545h-34.91V453.818c0-44.218-37.236-81.454-81.454-81.454H546.909v-93.091h197.818c25.6 0 46.546-20.946 46.546-46.546V93.091c0-25.6-20.946-46.546-46.546-46.546H279.273c-25.6 0-46.546 20.946-46.546 46.546v139.636c0 25.6 20.946 46.546 46.546 46.546H477.09v93.09H162.909c-44.218 0-81.454 37.237-81.454 81.455v104.727h-34.91C20.945 558.545 0 579.491 0 605.091v325.818c0 25.6 20.945 46.546 46.545 46.546h139.637c25.6 0 46.545-20.946 46.545-46.546V605.091c0-25.6-20.945-46.546-46.545-46.546h-34.91V453.818c0-6.982 4.655-11.636 11.637-11.636h314.182v116.363h-34.91c-25.6 0-46.545 20.946-46.545 46.546v325.818c0 25.6 20.946 46.546 46.546 46.546h139.636c25.6 0 46.546-20.946 46.546-46.546V605.091c0-25.6-20.946-46.546-46.546-46.546H546.91V442.182h314.182c6.982 0 11.636 4.654 11.636 11.636v104.727h-34.909c-25.6 0-46.545 20.946-46.545 46.546v325.818c0 25.6 20.945 46.546 46.545 46.546h139.637c25.6 0 46.545-20.946 46.545-46.546V605.091c0-25.6-20.945-46.546-46.545-46.546zm-814.546 69.819v279.272H69.82V628.364h93.09zm395.636 0v279.272h-93.09V628.364h93.09zm-256-418.91v-93.09h418.91v93.09h-418.91zm651.637 698.182H861.09V628.364h93.09v279.272z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M599.378 958.454H424.62c-165.805 0-296.769 0-296.769-86.024v-17.178c0-161.188 133.115-292.258 296.77-292.258h174.756c163.619 0 296.769 131.102 296.769 292.258v17.178c0 86.024-137.557 86.024-296.768 86.024zm-176.39-346.981c-137.625 0-249.608 109.935-249.608 245.098v17.491c0 35.046 144.255 35.046 249.608 35.046h177.985c87.207 0 249.645 0 249.645-35.046v-17.491c0-135.163-112.018-245.098-249.645-245.098H422.988zm80.266-83.526c-129.923 0-235.555-104.14-235.555-232.12 0-128.015 105.632-232.119 235.555-232.119s235.554 104.104 235.554 232.12c0 127.978-105.7 232.12-235.554 232.12zM316.246 295.098c0 101.224 83.91 183.572 187.008 183.572 103.133 0 187.042-82.348 187.042-183.572 0-101.19-83.909-183.502-187.042-183.502-103.134 0-187.008 82.311-187.008 183.502zm0 17.767"/></svg>
|
After Width: | Height: | Size: 886 B |
|
@ -0,0 +1 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M6.536 518.536a501.106 501.106 0 1 0 1002.213 0 501.106 501.106 0 1 0-1002.213 0z" fill="#FA6B6D"/><path d="M513.09 262.536c68.63 0 125.276 55.558 125.276 123.098 0 67.54-55.557 123.098-125.277 123.098-68.63 0-125.276-55.558-125.276-123.098 1.09-68.63 56.647-123.098 125.276-123.098zm0 0c68.63 0 125.276 55.558 125.276 123.098 0 67.54-55.557 123.098-125.277 123.098-68.63 0-125.276-55.558-125.276-123.098 1.09-68.63 56.647-123.098 125.276-123.098zm-46.843 286.502h104.579c89.327 0 161.225 70.809 161.225 159.047v9.804c0 34.86-71.898 35.95-161.225 35.95h-104.58c-89.327 0-161.225 0-161.225-35.95v-9.804c0-88.238 72.988-159.047 161.226-159.047z" fill="#FFF"/></svg>
|
After Width: | Height: | Size: 774 B |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 47 KiB |
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<component :is="type" v-bind="linkProps(to)">
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: "AppLink",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
import { isExternal } from "@/utils/index";
|
||||
|
||||
const props = defineProps({
|
||||
to: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const isExternalLink = computed(() => isExternal(props.to));
|
||||
|
||||
const type = computed(() => {
|
||||
return isExternalLink.value ? "a" : "router-link";
|
||||
});
|
||||
|
||||
const linkProps = (to: string) => {
|
||||
return isExternalLink.value
|
||||
? { href: to, target: "_blank", rel: "noopener noreferrer" }
|
||||
: { to };
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<el-breadcrumb class="flex-y-center">
|
||||
<transition-group
|
||||
enter-active-class="animate__animated animate__fadeInRight"
|
||||
>
|
||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
||||
<span
|
||||
v-if="
|
||||
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
|
||||
"
|
||||
class="color-gray-400"
|
||||
>{{ translateRouteTitle(item.meta.title) }}</span
|
||||
>
|
||||
<a v-else @click.prevent="handleLink(item)">
|
||||
{{ translateRouteTitle(item.meta.title) }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouteLocationMatched } from "vue-router";
|
||||
import { compile } from "path-to-regexp";
|
||||
import router from "@/router";
|
||||
import { translateRouteTitle } from "@/utils/i18n";
|
||||
|
||||
const currentRoute = useRoute();
|
||||
const pathCompile = (path: string) => {
|
||||
const { params } = currentRoute;
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
};
|
||||
|
||||
const breadcrumbs = ref<Array<RouteLocationMatched>>([]);
|
||||
|
||||
function getBreadcrumb() {
|
||||
let matched = currentRoute.matched.filter(
|
||||
(item) => item.meta && item.meta.title
|
||||
);
|
||||
const first = matched[0];
|
||||
if (!isDashboard(first)) {
|
||||
matched = [
|
||||
{ path: "/dashboard", meta: { title: "dashboard" } } as any,
|
||||
].concat(matched);
|
||||
}
|
||||
breadcrumbs.value = matched.filter((item) => {
|
||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
|
||||
});
|
||||
}
|
||||
|
||||
function isDashboard(route: RouteLocationMatched) {
|
||||
const name = route && route.name;
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
name.toString().trim().toLocaleLowerCase() ===
|
||||
"Dashboard".toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
function handleLink(item: any) {
|
||||
const { redirect, path } = item;
|
||||
if (redirect) {
|
||||
router.push(redirect).catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
router.push(pathCompile(path)).catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => currentRoute.path,
|
||||
(path) => {
|
||||
if (path.startsWith("/redirect/")) {
|
||||
return;
|
||||
}
|
||||
getBreadcrumb();
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getBreadcrumb();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 覆盖 element-plus 的样式
|
||||
.el-breadcrumb__inner,
|
||||
.el-breadcrumb__inner a {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<el-select
|
||||
v-model="selectedValue"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
clearable
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DictAPI from "@/api/dict";
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 字典类型编码(eg: 性别-gender)
|
||||
*/
|
||||
typeCode: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]); // 父组件监听事件,同步子组件值的变化给父组件
|
||||
|
||||
const options: Ref<OptionType[]> = ref([]); // 字典下拉数据源
|
||||
|
||||
const selectedValue = ref<string | number | undefined>();
|
||||
|
||||
watch([options, () => props.modelValue], ([newOptions, newModelValue]) => {
|
||||
if (newOptions.length === 0) return; // 下拉数据源加载未完成不回显
|
||||
if (newModelValue == undefined) {
|
||||
selectedValue.value = undefined;
|
||||
return;
|
||||
}
|
||||
if (typeof newOptions[0].value === "number") {
|
||||
selectedValue.value = Number(newModelValue);
|
||||
} else if (typeof newOptions[0].value === "string") {
|
||||
selectedValue.value = String(newModelValue);
|
||||
} else {
|
||||
selectedValue.value = newModelValue;
|
||||
}
|
||||
});
|
||||
|
||||
function handleChange(val?: string | number | undefined) {
|
||||
emits("update:modelValue", val);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
// 根据字典类型编码(typeCode)获取字典选项
|
||||
DictAPI.getDictOptions(props.typeCode).then((data) => {
|
||||
options.value = data;
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<a
|
||||
href="https://github.com/haoxianrui"
|
||||
target="_blank"
|
||||
class="github-corner"
|
||||
aria-label="View source on Github"
|
||||
>
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 250 250"
|
||||
style="color: #fff; fill: #40c9c6"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
|
||||
<path
|
||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
||||
fill="currentColor"
|
||||
style="transform-origin: 130px 106px"
|
||||
class="octo-arm"
|
||||
/>
|
||||
<path
|
||||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
||||
fill="currentColor"
|
||||
class="octo-body"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
20%,
|
||||
60% {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
|
||||
40%,
|
||||
80% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 500px) {
|
||||
.github-corner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div
|
||||
class="px-[15px] flex items-center justify-center color-[var(--el-text-color-regular)]"
|
||||
@click="toggleClick"
|
||||
>
|
||||
<svg-icon
|
||||
class="hamburger"
|
||||
:class="{ 'is-active': isActive }"
|
||||
icon-class="indent-decrease"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
isActive: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["toggleClick"]);
|
||||
|
||||
function toggleClick() {
|
||||
emit("toggleClick");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.hamburger {
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.hamburger.is-active {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,208 @@
|
|||
<template>
|
||||
<div ref="iconSelectRef" :style="'width:' + width">
|
||||
<el-popover :visible="popoverVisible" :width="width" placement="bottom-end">
|
||||
<template #reference>
|
||||
<el-input
|
||||
class="reference"
|
||||
v-model="selectedIcon"
|
||||
readonly
|
||||
placeholder="点击选择图标"
|
||||
@click="popoverVisible = !popoverVisible"
|
||||
>
|
||||
<template #prepend>
|
||||
<template
|
||||
v-if="selectedIcon && selectedIcon.startsWith('el-icon-')"
|
||||
>
|
||||
<el-icon>
|
||||
<component :is="selectedIcon.replace('el-icon-', '')" />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg-icon :icon-class="selectedIcon" />
|
||||
</template>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<el-icon
|
||||
:style="{
|
||||
transform: popoverVisible ? 'rotate(180deg)' : 'rotate(0)',
|
||||
transition: 'transform .5s',
|
||||
}"
|
||||
@click="popoverVisible = !popoverVisible"
|
||||
>
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
|
||||
<!-- 下拉选择弹窗 -->
|
||||
<div ref="popoverContentRef">
|
||||
<el-input
|
||||
v-model="searchText"
|
||||
placeholder="搜索图标"
|
||||
clearable
|
||||
@input="filterIcons"
|
||||
/>
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="SVG 图标" name="svg">
|
||||
<el-scrollbar height="300px">
|
||||
<ul class="icon-container">
|
||||
<li
|
||||
v-for="icon in filteredSvgIcons"
|
||||
:key="'svg-' + icon"
|
||||
class="icon-item"
|
||||
@click="selectIcon(icon)"
|
||||
>
|
||||
<el-tooltip :content="icon" placement="bottom" effect="light">
|
||||
<svg-icon :icon-class="icon" />
|
||||
</el-tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Element 图标" name="element">
|
||||
<el-scrollbar height="300px">
|
||||
<ul class="icon-container">
|
||||
<li
|
||||
v-for="icon in filteredEpIcons"
|
||||
:key="icon"
|
||||
class="icon-item"
|
||||
@click="selectIcon(icon)"
|
||||
>
|
||||
<el-icon>
|
||||
<component :is="icon" />
|
||||
</el-icon>
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
require: false,
|
||||
default: "",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
require: false,
|
||||
default: "500px",
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const selectedIcon = toRef(props, "modelValue");
|
||||
|
||||
const iconSelectRef = ref();
|
||||
const popoverContentRef = ref();
|
||||
|
||||
const activeTab = ref("svg"); // 默认激活的Tab
|
||||
const searchText = ref(""); // 筛选的值
|
||||
const popoverVisible = ref(false); // 弹窗显示状态
|
||||
|
||||
const svgIcons: string[] = []; // SVG图标集合
|
||||
const filteredSvgIcons = ref<string[]>([]); // 过滤后的SVG图标名称集合
|
||||
|
||||
const epIcons: string[] = Object.keys(ElementPlusIconsVue); // Element Plus图标集合
|
||||
const filteredEpIcons = ref<string[]>([]); // 过滤后的Element Plus图标名称集合
|
||||
|
||||
onMounted(() => {
|
||||
loadIcons();
|
||||
});
|
||||
|
||||
/**
|
||||
* icon 加载
|
||||
*/
|
||||
function loadIcons() {
|
||||
const icons = import.meta.glob("../../assets/icons/*.svg");
|
||||
for (const path in icons) {
|
||||
const iconName = path.replace(/.*\/(.*)\.svg$/, "$1");
|
||||
svgIcons.push(iconName);
|
||||
}
|
||||
filteredSvgIcons.value = svgIcons;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选项卡切换
|
||||
*/
|
||||
function handleTabClick(tabPane: any) {
|
||||
activeTab.value = tabPane.name;
|
||||
filterIcons();
|
||||
}
|
||||
|
||||
/**
|
||||
* icon 筛选
|
||||
*/
|
||||
function filterIcons() {
|
||||
if (activeTab.value === "svg") {
|
||||
// 过滤SVG图标逻辑
|
||||
filteredSvgIcons.value = searchText.value
|
||||
? svgIcons.filter((iconName) =>
|
||||
iconName.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
: svgIcons;
|
||||
} else {
|
||||
// 过滤Element Plus图标逻辑 TODO
|
||||
filteredEpIcons.value = searchText.value
|
||||
? epIcons.filter((iconName) =>
|
||||
iconName.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
)
|
||||
: epIcons;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择图标
|
||||
*/
|
||||
function selectIcon(iconName: string) {
|
||||
if (activeTab.value === "element") {
|
||||
iconName = "el-icon-" + iconName;
|
||||
}
|
||||
emit("update:modelValue", iconName);
|
||||
popoverVisible.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击容器外的区域关闭弹窗 VueUse onClickOutside
|
||||
*/
|
||||
onClickOutside(iconSelectRef, () => (popoverVisible.value = false), {
|
||||
ignore: [popoverContentRef],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reference :deep(.el-input__wrapper),
|
||||
.reference :deep(.el-input__inner) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.icon-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
margin: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.icon-item:hover {
|
||||
border-color: #409eff;
|
||||
scale: 1.2;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<el-dropdown trigger="click" @command="handleLanguageChange">
|
||||
<div>
|
||||
<svg-icon icon-class="language" :size="size" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in langOptions"
|
||||
:key="item.value"
|
||||
:disabled="appStore.language === item.value"
|
||||
:command="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useAppStore } from "@/store/modules/app";
|
||||
import { LanguageEnum } from "@/enums/LanguageEnum";
|
||||
|
||||
defineProps({
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const langOptions = [
|
||||
{ label: "中文", value: LanguageEnum.ZH_CN },
|
||||
{ label: "English", value: LanguageEnum.EN },
|
||||
];
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { locale, t } = useI18n();
|
||||
|
||||
function handleLanguageChange(lang: string) {
|
||||
locale.value = lang;
|
||||
appStore.changeLanguage(lang);
|
||||
|
||||
ElMessage.success(t("langSelect.message.success"));
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,548 @@
|
|||
<template>
|
||||
<el-card shadow="never" class="table-container">
|
||||
<!-- 表格工具栏 -->
|
||||
<div class="flex-x-between mb-[10px]">
|
||||
<!-- 左侧工具栏 -->
|
||||
<div>
|
||||
<template v-for="item in toolbar" :key="item">
|
||||
<template v-if="typeof item === 'string'">
|
||||
<!-- 新增 -->
|
||||
<template v-if="item === 'add'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
type="success"
|
||||
icon="plus"
|
||||
@click="handleToolbar(item)"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 删除 -->
|
||||
<template v-else-if="item === 'delete'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
:disabled="removeIds.length === 0"
|
||||
@click="handleToolbar(item)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 导出 -->
|
||||
<template v-else-if="item === 'export'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
type="primary"
|
||||
icon="download"
|
||||
@click="handleToolbar(item)"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 其他 -->
|
||||
<template v-else-if="typeof item === 'object'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
|
||||
:icon="item.icon"
|
||||
type="default"
|
||||
@click="handleToolbar(item.name)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 右侧工具栏 -->
|
||||
<div>
|
||||
<template v-for="item in defaultToolbar" :key="item">
|
||||
<!-- 刷新 -->
|
||||
<template v-if="item === 'refresh'">
|
||||
<el-button icon="refresh" circle @click="handleToolbar(item)" />
|
||||
</template>
|
||||
<!-- 列设置 -->
|
||||
<template v-else-if="item === 'filter'">
|
||||
<el-popover placement="bottom" trigger="click">
|
||||
<template #reference>
|
||||
<el-button icon="Operation" circle />
|
||||
</template>
|
||||
<template v-for="col in cols" :key="col">
|
||||
<el-checkbox
|
||||
v-if="col.prop"
|
||||
v-model="col.show"
|
||||
:label="col.label"
|
||||
/>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
<!-- 搜索 -->
|
||||
<template v-else-if="item === 'search'">
|
||||
<el-button icon="search" circle @click="handleToolbar(item)" />
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 列表 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
v-bind="contentConfig.table"
|
||||
:data="pageData"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template v-for="col in cols" :key="col">
|
||||
<el-table-column v-if="col.show" v-bind="col">
|
||||
<template #default="scope">
|
||||
<!-- 显示图片 -->
|
||||
<template v-if="col.templet === 'image'">
|
||||
<template v-if="col.prop">
|
||||
<template v-if="Array.isArray(scope.row[col.prop])">
|
||||
<template
|
||||
v-for="(item, index) in scope.row[col.prop]"
|
||||
:key="item"
|
||||
>
|
||||
<el-image
|
||||
:src="item"
|
||||
:preview-src-list="scope.row[col.prop]"
|
||||
:initial-index="index"
|
||||
:preview-teleported="true"
|
||||
:style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-image
|
||||
:src="scope.row[col.prop]"
|
||||
:preview-src-list="[scope.row[col.prop]]"
|
||||
:preview-teleported="true"
|
||||
:style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 根据行的selectList属性返回对应列表值 -->
|
||||
<template v-else-if="col.templet === 'list'">
|
||||
<template v-if="col.prop">
|
||||
{{ (col.selectList ?? {})[scope.row[col.prop]] }}
|
||||
</template>
|
||||
</template>
|
||||
<!-- 格式化显示链接 -->
|
||||
<template v-else-if="col.templet === 'url'">
|
||||
<template v-if="col.prop">
|
||||
<el-link
|
||||
type="primary"
|
||||
:href="scope.row[col.prop]"
|
||||
target="_blank"
|
||||
>
|
||||
{{ scope.row[col.prop] }}
|
||||
</el-link>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 生成开关组件 -->
|
||||
<template v-else-if="col.templet === 'switch'">
|
||||
<template v-if="col.prop">
|
||||
<!-- pageData.length>0: 解决el-switch组件会在表格初始化的时候触发一次change事件 -->
|
||||
<el-switch
|
||||
v-model="scope.row[col.prop]"
|
||||
:active-value="col.activeValue ?? 1"
|
||||
:inactive-value="col.inactiveValue ?? 0"
|
||||
:inline-prompt="true"
|
||||
:active-text="col.activeText ?? ''"
|
||||
:inactive-text="col.inactiveText ?? ''"
|
||||
:validate-event="false"
|
||||
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)"
|
||||
@change="
|
||||
pageData.length > 0 &&
|
||||
handleModify(col.prop, scope.row[col.prop], scope.row)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 格式化为价格 -->
|
||||
<template v-else-if="col.templet === 'price'">
|
||||
<template v-if="col.prop">
|
||||
{{ `${col.priceFormat ?? "¥"}${scope.row[col.prop]}` }}
|
||||
</template>
|
||||
</template>
|
||||
<!-- 格式化为百分比 -->
|
||||
<template v-else-if="col.templet === 'percent'">
|
||||
<template v-if="col.prop"> {{ scope.row[col.prop] }}% </template>
|
||||
</template>
|
||||
<!-- 显示图标 -->
|
||||
<template v-else-if="col.templet === 'icon'">
|
||||
<template v-if="col.prop">
|
||||
<template v-if="scope.row[col.prop].startsWith('el-icon-')">
|
||||
<el-icon>
|
||||
<component
|
||||
:is="scope.row[col.prop].replace('el-icon-', '')"
|
||||
/>
|
||||
</el-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg-icon :icon-class="scope.row[col.prop]" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 格式化时间 -->
|
||||
<template v-else-if="col.templet === 'date'">
|
||||
<template v-if="col.prop">
|
||||
{{
|
||||
useDateFormat(
|
||||
scope.row[col.prop],
|
||||
col.dateFormat ?? "YYYY-MM-DD HH:mm:ss"
|
||||
).value
|
||||
}}
|
||||
</template>
|
||||
</template>
|
||||
<!-- 列操作栏 -->
|
||||
<template v-else-if="col.templet === 'tool'">
|
||||
<template
|
||||
v-for="item in col.operat ?? ['edit', 'delete']"
|
||||
:key="item"
|
||||
>
|
||||
<template v-if="typeof item === 'string'">
|
||||
<!-- 编辑/删除 -->
|
||||
<template v-if="item === 'edit' || item === 'delete'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
|
||||
:type="item === 'edit' ? 'primary' : 'danger'"
|
||||
:icon="item"
|
||||
size="small"
|
||||
link
|
||||
@click="
|
||||
handleOperat({
|
||||
name: item,
|
||||
row: scope.row,
|
||||
column: scope.column,
|
||||
$index: scope.$index,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ item === "edit" ? "编辑" : "删除" }}
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 其他 -->
|
||||
<template v-else-if="typeof item === 'object'">
|
||||
<el-button
|
||||
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
|
||||
:icon="item.icon"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
@click="
|
||||
handleOperat({
|
||||
name: item.name,
|
||||
row: scope.row,
|
||||
column: scope.column,
|
||||
$index: scope.$index,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ item.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 自定义 -->
|
||||
<template v-else-if="col.templet === 'custom'">
|
||||
<slot
|
||||
:name="col.slotName ?? col.prop"
|
||||
:prop="col.prop"
|
||||
v-bind="scope"
|
||||
></slot>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
v-model:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="handlePagination"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from "vue";
|
||||
import { useDateFormat } from "@vueuse/core";
|
||||
import { hasAuth } from "@/plugins/permission";
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||
import type { TableProps } from "element-plus";
|
||||
|
||||
// 对象类型
|
||||
export type IObject = Record<string, any>;
|
||||
// 定义接收的属性
|
||||
export interface IOperatData {
|
||||
name: string;
|
||||
row: any;
|
||||
column: any;
|
||||
$index: number;
|
||||
}
|
||||
export interface IContentConfig<T = any> {
|
||||
// 页面名称(参与组成权限标识,如sys:user:xxx)
|
||||
pageName: string;
|
||||
// table组件属性
|
||||
table?: Omit<TableProps<any>, "data">;
|
||||
// 列表的网络请求函数(需返回promise)
|
||||
indexAction: (queryParams: T) => Promise<any>;
|
||||
// 删除的网络请求函数(需返回promise)
|
||||
deleteAction?: (ids: string) => Promise<any>;
|
||||
// 导出的网络请求函数(需返回promise)
|
||||
exportAction?: (queryParams: T) => Promise<any>;
|
||||
// 修改属性的网络请求函数(需返回promise)
|
||||
modifyAction?: (data: {
|
||||
[key: string]: any;
|
||||
field: string;
|
||||
value: boolean | string | number;
|
||||
}) => Promise<any>;
|
||||
// 主键名(默认为id)
|
||||
pk?: string;
|
||||
// 表格工具栏(默认支持add,delete,export,也可自定义)
|
||||
toolbar?: Array<
|
||||
| "add"
|
||||
| "delete"
|
||||
| "export"
|
||||
| {
|
||||
auth: string;
|
||||
icon?: string;
|
||||
name: string;
|
||||
text: string;
|
||||
}
|
||||
>;
|
||||
// 表格工具栏右侧图标
|
||||
defaultToolbar?: ("refresh" | "filter" | "search")[];
|
||||
// table组件列属性(额外的属性templet,operat,slotName)
|
||||
cols: Array<{
|
||||
type?: "default" | "selection" | "index" | "expand";
|
||||
label?: string;
|
||||
prop?: string;
|
||||
width?: string | number;
|
||||
align?: "left" | "center" | "right";
|
||||
show?: boolean;
|
||||
templet?:
|
||||
| "image"
|
||||
| "list"
|
||||
| "url"
|
||||
| "switch"
|
||||
| "price"
|
||||
| "percent"
|
||||
| "icon"
|
||||
| "date"
|
||||
| "tool"
|
||||
| "custom";
|
||||
imageWidth?: number;
|
||||
imageHeight?: number;
|
||||
selectList?: Record<string, any>;
|
||||
activeValue?: boolean | string | number;
|
||||
inactiveValue?: boolean | string | number;
|
||||
activeText?: string;
|
||||
inactiveText?: string;
|
||||
priceFormat?: string;
|
||||
dateFormat?: string;
|
||||
operat?: Array<
|
||||
| "edit"
|
||||
| "delete"
|
||||
| {
|
||||
auth: string;
|
||||
icon?: string;
|
||||
name: string;
|
||||
text: string;
|
||||
}
|
||||
>;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
}
|
||||
const props = defineProps<{
|
||||
contentConfig: IContentConfig;
|
||||
}>();
|
||||
// 定义自定义事件
|
||||
const emit = defineEmits<{
|
||||
addClick: [];
|
||||
exportClick: [];
|
||||
searchClick: [];
|
||||
toolbarClick: [name: string];
|
||||
editClick: [row: IObject];
|
||||
operatClick: [data: IOperatData];
|
||||
}>();
|
||||
|
||||
// 主键
|
||||
const pk = props.contentConfig.pk ?? "id";
|
||||
// 表格左侧工具栏
|
||||
const toolbar = props.contentConfig.toolbar ?? ["add", "delete"];
|
||||
// 表格右侧工具栏
|
||||
const defaultToolbar = props.contentConfig.defaultToolbar ?? [
|
||||
"refresh",
|
||||
"filter",
|
||||
"search",
|
||||
];
|
||||
// 表格列
|
||||
const cols = ref(
|
||||
props.contentConfig.cols.map((col) => {
|
||||
if (col.show === undefined) {
|
||||
col.show = true;
|
||||
}
|
||||
return col;
|
||||
})
|
||||
);
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
// 删除ID集合 用于批量删除
|
||||
const removeIds = ref<(number | string)[]>([]);
|
||||
// 数据总数
|
||||
const total = ref(0);
|
||||
// 列表数据
|
||||
const pageData = ref<IObject[]>([]);
|
||||
// 每页条数
|
||||
const pageSize = 10;
|
||||
// 搜索参数
|
||||
const queryParams = reactive<IObject>({
|
||||
pageNum: 1,
|
||||
pageSize: pageSize,
|
||||
});
|
||||
// 上一次搜索条件
|
||||
let lastFormData = {};
|
||||
// 获取分页数据
|
||||
function fetchPageData(formData: IObject = {}, isRestart = false) {
|
||||
loading.value = true;
|
||||
lastFormData = formData;
|
||||
if (isRestart) {
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.pageSize = pageSize;
|
||||
}
|
||||
props.contentConfig
|
||||
.indexAction({ ...queryParams, ...formData })
|
||||
.then((data) => {
|
||||
total.value = data.total;
|
||||
pageData.value = data.list;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
fetchPageData();
|
||||
|
||||
// 行选中
|
||||
function handleSelectionChange(selection: any[]) {
|
||||
removeIds.value = selection.map((item) => item[pk]);
|
||||
}
|
||||
// 刷新
|
||||
function handleRefresh() {
|
||||
fetchPageData({}, true);
|
||||
}
|
||||
// 删除
|
||||
function handleDelete(id?: number | string) {
|
||||
const ids = [id || removeIds.value].join(",");
|
||||
if (!ids) {
|
||||
ElMessage.warning("请勾选删除项");
|
||||
return;
|
||||
}
|
||||
|
||||
ElMessageBox.confirm("确认删除?", "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
}).then(function () {
|
||||
if (props.contentConfig.deleteAction) {
|
||||
props.contentConfig.deleteAction(ids).then(() => {
|
||||
ElMessage.success("删除成功");
|
||||
handleRefresh();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error("未配置deleteAction");
|
||||
}
|
||||
});
|
||||
}
|
||||
// 分页
|
||||
function handlePagination() {
|
||||
fetchPageData(lastFormData);
|
||||
}
|
||||
// 操作栏
|
||||
function handleToolbar(name: string) {
|
||||
switch (name) {
|
||||
case "refresh":
|
||||
handleRefresh();
|
||||
break;
|
||||
case "search":
|
||||
emit("searchClick");
|
||||
break;
|
||||
case "add":
|
||||
emit("addClick");
|
||||
break;
|
||||
case "delete":
|
||||
handleDelete();
|
||||
break;
|
||||
case "export":
|
||||
emit("exportClick");
|
||||
break;
|
||||
default:
|
||||
emit("toolbarClick", name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 操作列
|
||||
function handleOperat(data: IOperatData) {
|
||||
switch (data.name) {
|
||||
case "edit":
|
||||
emit("editClick", data.row);
|
||||
break;
|
||||
case "delete":
|
||||
handleDelete(data.row[pk]);
|
||||
break;
|
||||
default:
|
||||
emit("operatClick", data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 导出Excel
|
||||
function exportPageData(queryParams: IObject = {}) {
|
||||
if (props.contentConfig.exportAction) {
|
||||
props.contentConfig.exportAction(queryParams).then((response) => {
|
||||
const fileData = response.data;
|
||||
const fileName = decodeURI(
|
||||
response.headers["content-disposition"].split(";")[1].split("=")[1]
|
||||
);
|
||||
const fileType =
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
|
||||
|
||||
const blob = new Blob([fileData], { type: fileType });
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const downloadLink = document.createElement("a");
|
||||
downloadLink.href = downloadUrl;
|
||||
downloadLink.download = fileName;
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
});
|
||||
} else {
|
||||
ElMessage.error("未配置exportAction");
|
||||
}
|
||||
}
|
||||
// 属性修改
|
||||
function handleModify(
|
||||
field: string,
|
||||
value: boolean | string | number,
|
||||
row: Record<string, any>
|
||||
) {
|
||||
if (props.contentConfig.modifyAction) {
|
||||
props.contentConfig.modifyAction({
|
||||
[pk]: row[pk],
|
||||
field: field,
|
||||
value: value,
|
||||
});
|
||||
} else {
|
||||
ElMessage.error("未配置modifyAction");
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露的属性和方法
|
||||
defineExpose({ fetchPageData, exportPageData });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
label-width="auto"
|
||||
v-bind="form"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
>
|
||||
<template v-for="item in formItems" :key="item.prop">
|
||||
<el-form-item v-show="!item.hidden" :label="item.label" :prop="item.prop">
|
||||
<!-- Label -->
|
||||
<template #label v-if="item.tips">
|
||||
<span>
|
||||
{{ item.label }}
|
||||
<el-tooltip
|
||||
placement="bottom"
|
||||
effect="light"
|
||||
:content="item.tips"
|
||||
:raw-content="true"
|
||||
>
|
||||
<el-icon style="vertical-align: -0.15em" size="16">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<!-- Input 输入框 -->
|
||||
<template v-if="item.type === 'input' || item.type === undefined">
|
||||
<el-input v-model="formData[item.prop]" v-bind="item.attrs" />
|
||||
</template>
|
||||
<!-- Select 选择器 -->
|
||||
<template v-else-if="item.type === 'select'">
|
||||
<el-select v-model="formData[item.prop]" v-bind="item.attrs">
|
||||
<template v-for="option in item.options" :key="option.value">
|
||||
<el-option v-bind="option" />
|
||||
</template>
|
||||
</el-select>
|
||||
</template>
|
||||
<!-- Radio 单选框 -->
|
||||
<template v-else-if="item.type === 'radio'">
|
||||
<el-radio-group v-model="formData[item.prop]" v-bind="item.attrs">
|
||||
<template v-for="option in item.options" :key="option.value">
|
||||
<el-radio v-bind="option" />
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<!-- Checkbox 多选框 -->
|
||||
<template v-else-if="item.type === 'checkbox'">
|
||||
<el-checkbox-group v-model="formData[item.prop]" v-bind="item.attrs">
|
||||
<template v-for="option in item.options" :key="option.value">
|
||||
<el-checkbox v-bind="option" />
|
||||
</template>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
<!-- Input Number 数字输入框 -->
|
||||
<template v-else-if="item.type === 'input-number'">
|
||||
<el-input-number v-model="formData[item.prop]" v-bind="item.attrs" />
|
||||
</template>
|
||||
<!-- TreeSelect 树形选择 -->
|
||||
<template v-else-if="item.type === 'tree-select'">
|
||||
<el-tree-select v-model="formData[item.prop]" v-bind="item.attrs" />
|
||||
</template>
|
||||
<!-- DatePicker 日期选择器 -->
|
||||
<template v-else-if="item.type === 'date-picker'">
|
||||
<el-date-picker v-model="formData[item.prop]" v-bind="item.attrs" />
|
||||
</template>
|
||||
<!-- Text 文本 -->
|
||||
<template v-else-if="item.type === 'text'">
|
||||
<el-text v-bind="item.attrs">{{ formData[item.prop] }}</el-text>
|
||||
</template>
|
||||
<!-- 自定义 -->
|
||||
<template v-else-if="item.type === 'custom'">
|
||||
<slot
|
||||
:name="item.slotName ?? item.prop"
|
||||
:prop="item.prop"
|
||||
:formData="formData"
|
||||
:attrs="item.attrs"
|
||||
></slot>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
import { reactive, ref, watch, computed, watchEffect } from "vue";
|
||||
import { IForm, IFormItems, IObject } from "./types";
|
||||
|
||||
// 定义接收的属性
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 主键名(主要用于编辑数据,默认为id)
|
||||
pk?: string;
|
||||
// form组件属性
|
||||
form?: IForm;
|
||||
// 表单项
|
||||
formItems: IFormItems;
|
||||
}>(),
|
||||
{
|
||||
pk: "id",
|
||||
}
|
||||
);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const formData = reactive<IObject>({});
|
||||
const formRules: FormRules = {};
|
||||
const watchArr = [];
|
||||
const computedArr = [];
|
||||
const watchEffectArr = [];
|
||||
// 初始化
|
||||
for (const item of props.formItems) {
|
||||
formData[item.prop] = item.initialValue ?? "";
|
||||
formRules[item.prop] = item.rules ?? [];
|
||||
if (item.watch !== undefined) {
|
||||
watchArr.push({ field: item.prop, func: item.watch });
|
||||
}
|
||||
if (item.computed !== undefined) {
|
||||
computedArr.push({ field: item.prop, func: item.computed });
|
||||
}
|
||||
if (item.watchEffect !== undefined) {
|
||||
watchEffectArr.push(item.watchEffect);
|
||||
}
|
||||
}
|
||||
watchArr.forEach(({ field, func }) => {
|
||||
watch(
|
||||
() => formData[field],
|
||||
(newValue, oldValue) => {
|
||||
func(newValue, oldValue, formData);
|
||||
}
|
||||
);
|
||||
});
|
||||
computedArr.forEach(({ field, func }) => {
|
||||
formData[field] = computed({
|
||||
get() {
|
||||
return func(formData);
|
||||
},
|
||||
// TODO
|
||||
set() {},
|
||||
});
|
||||
});
|
||||
watchEffectArr.forEach((func) => {
|
||||
watchEffect(() => {
|
||||
func(formData);
|
||||
});
|
||||
});
|
||||
// 获取表单数据
|
||||
function getFormData(key?: string) {
|
||||
return key === undefined ? formData : formData[key] ?? undefined;
|
||||
}
|
||||
// 设置表单值
|
||||
function setFormData(data: IObject) {
|
||||
for (const key in formData) {
|
||||
if (Object.hasOwn(formData, key) && key in data) {
|
||||
formData[key] = data[key];
|
||||
}
|
||||
}
|
||||
if (Object.hasOwn(data, props.pk)) {
|
||||
formData[props.pk] = data[props.pk];
|
||||
}
|
||||
}
|
||||
// 设置表单项值
|
||||
function setFormItemData(key: string, value: any) {
|
||||
formData[key] = value;
|
||||
}
|
||||
|
||||
// 暴露的属性和方法
|
||||
defineExpose({ formRef, getFormData, setFormData, setFormItemData });
|
||||
</script>
|
|
@ -0,0 +1,144 @@
|
|||
<template>
|
||||
<!-- drawer -->
|
||||
<template v-if="modalConfig.component === 'drawer'">
|
||||
<el-drawer
|
||||
v-model="modalVisible"
|
||||
:append-to-body="true"
|
||||
v-bind="modalConfig.drawer"
|
||||
@open="handleOpenModal"
|
||||
@close="handleCloseModal"
|
||||
>
|
||||
<!-- 表单 -->
|
||||
<page-form
|
||||
ref="pageFormRef"
|
||||
:pk="modalConfig.pk"
|
||||
:form="modalConfig.form"
|
||||
:form-items="modalConfig.formItems"
|
||||
/>
|
||||
<!-- 弹窗底部操作按钮 -->
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
<el-button @click="handleCloseModal">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<!-- dialog -->
|
||||
<template v-else>
|
||||
<el-dialog
|
||||
v-model="modalVisible"
|
||||
:align-center="true"
|
||||
:append-to-body="true"
|
||||
width="70vw"
|
||||
v-bind="modalConfig.dialog"
|
||||
style="padding-right: 0"
|
||||
@open="handleOpenModal"
|
||||
@close="handleCloseModal"
|
||||
>
|
||||
<!-- 滚动 -->
|
||||
<el-scrollbar max-height="65vh">
|
||||
<!-- 表单 -->
|
||||
<page-form
|
||||
ref="pageFormRef"
|
||||
:pk="modalConfig.pk"
|
||||
:form="modalConfig.form"
|
||||
:form-items="modalConfig.formItems"
|
||||
style="padding-right: var(--el-dialog-padding-primary)"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
<!-- 弹窗底部操作按钮 -->
|
||||
<template #footer>
|
||||
<div style="padding-right: var(--el-dialog-padding-primary)">
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
<el-button @click="handleCloseModal">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useThrottleFn } from "@vueuse/core";
|
||||
import { ref } from "vue";
|
||||
import PageForm from "./Form.vue";
|
||||
import { IDialog, IDrawer, IForm, IFormItems, IObject } from "./types";
|
||||
|
||||
// 定义接收的属性
|
||||
export interface IModalConfig<T = any> {
|
||||
// 页面名称
|
||||
pageName?: string;
|
||||
// 主键名(主要用于编辑数据,默认为id)
|
||||
pk?: string;
|
||||
// 组件类型
|
||||
component?: "dialog" | "drawer";
|
||||
// dialog组件属性
|
||||
dialog?: IDialog;
|
||||
// drawer组件属性
|
||||
drawer?: IDrawer;
|
||||
// form组件属性
|
||||
form?: IForm;
|
||||
// 表单项
|
||||
formItems: IFormItems<T>;
|
||||
// 提交之前处理
|
||||
beforeSubmit?: (data: T) => void;
|
||||
// 提交的网络请求函数(需返回promise)
|
||||
formAction: (data: T) => Promise<any>;
|
||||
}
|
||||
const props = defineProps<{
|
||||
modalConfig: IModalConfig;
|
||||
}>();
|
||||
// 自定义事件
|
||||
const emit = defineEmits<{
|
||||
submitClick: [];
|
||||
}>();
|
||||
|
||||
const modalVisible = ref(false);
|
||||
const pageFormRef = ref<InstanceType<typeof PageForm>>();
|
||||
let initialFormData = {};
|
||||
// 显示modal
|
||||
function setModalVisible(data: IObject = {}) {
|
||||
modalVisible.value = true;
|
||||
initialFormData = data;
|
||||
}
|
||||
// 表单提交
|
||||
const handleSubmit = useThrottleFn(() => {
|
||||
pageFormRef.value?.formRef?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
const formData = pageFormRef.value?.getFormData();
|
||||
if (typeof props.modalConfig.beforeSubmit === "function") {
|
||||
props.modalConfig.beforeSubmit(formData);
|
||||
}
|
||||
props.modalConfig.formAction(formData).then(() => {
|
||||
let msg = "操作成功";
|
||||
if (props.modalConfig.component === "drawer") {
|
||||
if (props.modalConfig.drawer?.title) {
|
||||
msg = props.modalConfig.drawer?.title;
|
||||
}
|
||||
} else {
|
||||
if (props.modalConfig.dialog?.title) {
|
||||
msg = props.modalConfig.dialog?.title;
|
||||
}
|
||||
}
|
||||
ElMessage.success(msg);
|
||||
emit("submitClick");
|
||||
handleCloseModal();
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
// 打开弹窗
|
||||
function handleOpenModal() {
|
||||
pageFormRef.value?.setFormData(initialFormData);
|
||||
}
|
||||
// 关闭弹窗
|
||||
function handleCloseModal() {
|
||||
modalVisible.value = false;
|
||||
pageFormRef.value?.formRef?.resetFields();
|
||||
pageFormRef.value?.formRef?.clearValidate();
|
||||
}
|
||||
// 暴露的属性和方法
|
||||
defineExpose({ setModalVisible });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,62 @@
|
|||
import type {
|
||||
DialogProps,
|
||||
DrawerProps,
|
||||
FormProps,
|
||||
FormItemRule,
|
||||
} from "element-plus";
|
||||
|
||||
// dialog组件属性
|
||||
export type IDialog = Partial<Omit<DialogProps, "modelValue">>;
|
||||
|
||||
// drawer组件属性
|
||||
export type IDrawer = Partial<Omit<DrawerProps, "modelValue">>;
|
||||
|
||||
// form组件属性
|
||||
export type IForm = Partial<Omit<FormProps, "model" | "rules">>;
|
||||
|
||||
// 对象类型
|
||||
export type IObject = Record<string, any>;
|
||||
|
||||
// 表单项
|
||||
export type IFormItems<T = any> = Array<{
|
||||
// 组件类型(如input,select,radio,custom等,默认input)
|
||||
type?:
|
||||
| "input"
|
||||
| "select"
|
||||
| "radio"
|
||||
| "checkbox"
|
||||
| "tree-select"
|
||||
| "date-picker"
|
||||
| "input-number"
|
||||
| "text"
|
||||
| "custom";
|
||||
// 组件属性
|
||||
attrs?: IObject;
|
||||
// 组件可选项(适用于select,radio,checkbox组件)
|
||||
options?: Array<{
|
||||
label: string;
|
||||
value: any;
|
||||
disabled?: boolean;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
// 插槽名(适用于组件类型为custom)
|
||||
slotName?: string;
|
||||
// 标签文本
|
||||
label: string;
|
||||
// 标签提示
|
||||
tips?: string;
|
||||
// 键名
|
||||
prop: string;
|
||||
// 验证规则
|
||||
rules?: FormItemRule[];
|
||||
// 初始值
|
||||
initialValue?: any;
|
||||
// 是否隐藏
|
||||
hidden?: boolean;
|
||||
// 监听函数
|
||||
watch?: (newValue: any, oldValue: any, data: T) => void;
|
||||
// 计算属性函数
|
||||
computed?: (data: T) => any;
|
||||
// 监听收集函数
|
||||
watchEffect?: (data: T) => void;
|
||||
}>;
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<div
|
||||
class="search-container"
|
||||
v-show="visible"
|
||||
v-hasPerm="[`${searchConfig.pageName}:query`]"
|
||||
>
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<template
|
||||
v-for="(item, index) in searchConfig.formItems"
|
||||
:key="item.prop"
|
||||
>
|
||||
<el-form-item
|
||||
v-show="isExpand ? true : index < showNumber"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
>
|
||||
<!-- Input 输入框 -->
|
||||
<template v-if="item.type === 'input' || item.type === undefined">
|
||||
<el-input
|
||||
v-model="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</template>
|
||||
<!-- Select 选择器 -->
|
||||
<template v-else-if="item.type === 'select'">
|
||||
<el-select v-model="queryParams[item.prop]" v-bind="item.attrs">
|
||||
<template v-for="option in item.options" :key="option.value">
|
||||
<el-option :label="option.label" :value="option.value" />
|
||||
</template>
|
||||
</el-select>
|
||||
</template>
|
||||
<!-- TreeSelect 树形选择 -->
|
||||
<template v-else-if="item.type === 'tree-select'">
|
||||
<el-tree-select
|
||||
v-model="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
/>
|
||||
</template>
|
||||
<!-- DatePicker 日期选择器 -->
|
||||
<template v-else-if="item.type === 'date-picker'">
|
||||
<el-date-picker
|
||||
v-model="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
/>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="handleQuery">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button icon="refresh" @click="handleReset">重置</el-button>
|
||||
<!-- 展开/收起 -->
|
||||
<el-link
|
||||
v-if="isExpandable && searchConfig.formItems.length > showNumber"
|
||||
class="ml-2"
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="isExpand = !isExpand"
|
||||
>
|
||||
<template v-if="isExpand"> 收起<i-ep-arrow-up /> </template>
|
||||
<template v-else> 展开<i-ep-arrow-down /> </template>
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from "element-plus";
|
||||
import { reactive, ref } from "vue";
|
||||
|
||||
// 对象类型
|
||||
type IObject = Record<string, any>;
|
||||
// 定义接收的属性
|
||||
export interface ISearchConfig {
|
||||
// 页面名称(参与组成权限标识,如sys:user:xxx)
|
||||
pageName: string;
|
||||
// 表单项
|
||||
formItems: Array<{
|
||||
// 组件类型(如input,select等)
|
||||
type?: "input" | "select" | "tree-select" | "date-picker";
|
||||
// 标签文本
|
||||
label: string;
|
||||
// 键名
|
||||
prop: string;
|
||||
// 组件属性
|
||||
attrs?: IObject;
|
||||
// 初始值
|
||||
initialValue?: any;
|
||||
// 可选项(适用于select组件)
|
||||
options?: { label: string; value: any }[];
|
||||
}>;
|
||||
// 是否开启展开和收缩
|
||||
isExpandable?: boolean;
|
||||
// 默认展示的表单项数量
|
||||
showNumber?: number;
|
||||
}
|
||||
interface IProps {
|
||||
searchConfig: ISearchConfig;
|
||||
}
|
||||
const props = defineProps<IProps>();
|
||||
// 自定义事件
|
||||
const emit = defineEmits<{
|
||||
queryClick: [queryParams: IObject];
|
||||
resetClick: [queryParams: IObject];
|
||||
}>();
|
||||
|
||||
// 是否显示
|
||||
const visible = ref(true);
|
||||
// 是否可展开/收缩
|
||||
const isExpandable = ref(props.searchConfig.isExpandable ?? true);
|
||||
// 是否已展开
|
||||
const isExpand = ref(false);
|
||||
// 表单项展示数量,若可展开,超出展示数量的表单项隐藏
|
||||
const showNumber = computed(() => {
|
||||
if (isExpandable.value === true) {
|
||||
return props.searchConfig.showNumber ?? 3;
|
||||
} else {
|
||||
return props.searchConfig.formItems.length;
|
||||
}
|
||||
});
|
||||
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
// 搜索表单数据
|
||||
const queryParams = reactive<IObject>({});
|
||||
for (const item of props.searchConfig.formItems) {
|
||||
queryParams[item.prop] = item.initialValue ?? "";
|
||||
}
|
||||
// 重置操作
|
||||
function handleReset() {
|
||||
queryFormRef.value?.resetFields();
|
||||
emit("resetClick", queryParams);
|
||||
}
|
||||
// 查询操作
|
||||
function handleQuery() {
|
||||
emit("queryClick", queryParams);
|
||||
}
|
||||
// 获取分页数据
|
||||
function getQueryParams() {
|
||||
return queryParams;
|
||||
}
|
||||
// 显示/隐藏 SearchForm
|
||||
function toggleVisible() {
|
||||
visible.value = !visible.value;
|
||||
}
|
||||
|
||||
// 暴露的属性和方法
|
||||
defineExpose({ getQueryParams, toggleVisible });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-container {
|
||||
padding: 18px 0 0 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<el-scrollbar>
|
||||
<div :class="{ hidden: hidden }" class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:background="background"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
total: {
|
||||
required: true,
|
||||
type: Number as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array as PropType<number[]>,
|
||||
default() {
|
||||
return [10, 20, 30, 50];
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: "total, sizes, prev, pager, next, jumper",
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["pagination", "update:page", "update:limit"]);
|
||||
|
||||
const currentPage = useVModel(props, "page", emit);
|
||||
|
||||
const pageSize = useVModel(props, "limit", emit);
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
emit("pagination", { page: currentPage, limit: val });
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val;
|
||||
emit("pagination", { page: val, limit: props.limit });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pagination {
|
||||
padding: 12px;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<el-dropdown trigger="click" @command="handleSizeChange">
|
||||
<div>
|
||||
<svg-icon icon-class="size" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item of sizeOptions"
|
||||
:key="item.value"
|
||||
:disabled="appStore.size == item.value"
|
||||
:command="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SizeEnum } from "@/enums/SizeEnum";
|
||||
import { useAppStore } from "@/store/modules/app";
|
||||
|
||||
const { t } = useI18n();
|
||||
const sizeOptions = computed(() => {
|
||||
return [
|
||||
{ label: t("sizeSelect.default"), value: SizeEnum.DEFAULT },
|
||||
{ label: t("sizeSelect.large"), value: SizeEnum.LARGE },
|
||||
{ label: t("sizeSelect.small"), value: SizeEnum.SMALL },
|
||||
];
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
function handleSizeChange(size: string) {
|
||||
appStore.changeSize(size);
|
||||
ElMessage.success(t("sizeSelect.message.success"));
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="svg-icon"
|
||||
:style="'width:' + size + ';height:' + size"
|
||||
>
|
||||
<use :xlink:href="symbolId" :fill="color" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: "icon",
|
||||
},
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "1em",
|
||||
},
|
||||
});
|
||||
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
overflow: hidden;
|
||||
vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 */
|
||||
outline: none;
|
||||
fill: currentcolor; /* 定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承 */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,371 @@
|
|||
<template>
|
||||
<div ref="tableSelectRef" :style="'width:' + width">
|
||||
<el-popover
|
||||
:visible="popoverVisible"
|
||||
:width="popoverWidth"
|
||||
placement="bottom-end"
|
||||
v-bind="selectConfig.popover"
|
||||
@show="handleShow"
|
||||
>
|
||||
<template #reference>
|
||||
<div @click="popoverVisible = !popoverVisible">
|
||||
<slot>
|
||||
<el-input
|
||||
class="reference"
|
||||
:model-value="text"
|
||||
:readonly="true"
|
||||
:placeholder="placeholder"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon
|
||||
:style="{
|
||||
transform: popoverVisible ? 'rotate(180deg)' : 'rotate(0)',
|
||||
transition: 'transform .5s',
|
||||
}"
|
||||
>
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 弹出框内容 -->
|
||||
<div ref="popoverContentRef">
|
||||
<!-- 表单 -->
|
||||
<el-form ref="formRef" :model="queryParams" :inline="true">
|
||||
<template v-for="item in selectConfig.formItems" :key="item.prop">
|
||||
<el-form-item :label="item.label" :prop="item.prop">
|
||||
<!-- Input 输入框 -->
|
||||
<template v-if="item.type === 'input'">
|
||||
<template v-if="item.attrs?.type === 'number'">
|
||||
<el-input
|
||||
v-model.number="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input
|
||||
v-model="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<!-- Select 选择器 -->
|
||||
<template v-else-if="item.type === 'select'">
|
||||
<el-select v-model="queryParams[item.prop]" v-bind="item.attrs">
|
||||
<template v-for="option in item.options" :key="option.value">
|
||||
<el-option :label="option.label" :value="option.value" />
|
||||
</template>
|
||||
</el-select>
|
||||
</template>
|
||||
<!-- TreeSelect 树形选择 -->
|
||||
<template v-else-if="item.type === 'tree-select'">
|
||||
<el-tree-select
|
||||
v-model="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
/>
|
||||
</template>
|
||||
<!-- DatePicker 日期选择器 -->
|
||||
<template v-else-if="item.type === 'date-picker'">
|
||||
<el-date-picker
|
||||
v-model="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
/>
|
||||
</template>
|
||||
<!-- Input 输入框 -->
|
||||
<template v-else>
|
||||
<template v-if="item.attrs?.type === 'number'">
|
||||
<el-input
|
||||
v-model.number="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input
|
||||
v-model="queryParams[item.prop]"
|
||||
v-bind="item.attrs"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="handleQuery">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button icon="refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 列表 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="pageData"
|
||||
:border="true"
|
||||
:max-height="250"
|
||||
:row-key="pk"
|
||||
:highlight-current-row="true"
|
||||
:class="{ radio: !isMultiple }"
|
||||
@select="handleSelect"
|
||||
@select-all="handleSelectAll"
|
||||
>
|
||||
<template v-for="col in selectConfig.tableColumns" :key="col.prop">
|
||||
<!-- 自定义 -->
|
||||
<template v-if="col.templet === 'custom'">
|
||||
<el-table-column v-bind="col">
|
||||
<template #default="scope">
|
||||
<slot
|
||||
:name="col.slotName ?? col.prop"
|
||||
:prop="col.prop"
|
||||
v-bind="scope"
|
||||
></slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<!-- 其他 -->
|
||||
<template v-else>
|
||||
<el-table-column v-bind="col" />
|
||||
</template>
|
||||
</template>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
v-model:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="handlePagination"
|
||||
/>
|
||||
<div class="feedback">
|
||||
<el-button type="primary" size="small" @click="handleConfirm">
|
||||
{{ confirmText }}
|
||||
</el-button>
|
||||
<el-button size="small" @click="handleClear"> 清 空 </el-button>
|
||||
<el-button size="small" @click="handleClose"> 关 闭 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { onClickOutside, useResizeObserver } from "@vueuse/core";
|
||||
import type { FormInstance, PopoverProps, TableInstance } from "element-plus";
|
||||
|
||||
// 对象类型
|
||||
export type IObject = Record<string, any>;
|
||||
// 定义接收的属性
|
||||
export interface ISelectConfig<T = any> {
|
||||
// 宽度
|
||||
width?: string;
|
||||
// 占位符
|
||||
placeholder?: string;
|
||||
// popover组件属性
|
||||
popover?: Partial<Omit<PopoverProps, "visible" | "v-model:visible">>;
|
||||
// 列表的网络请求函数(需返回promise)
|
||||
indexAction: (queryParams: T) => Promise<any>;
|
||||
// 主键名(跨页选择必填,默认为id)
|
||||
pk?: string;
|
||||
// 多选
|
||||
multiple?: boolean;
|
||||
// 表单项
|
||||
formItems: Array<{
|
||||
// 组件类型(如input,select等)
|
||||
type?: "input" | "select" | "tree-select" | "date-picker";
|
||||
// 标签文本
|
||||
label: string;
|
||||
// 键名
|
||||
prop: string;
|
||||
// 组件属性
|
||||
attrs?: IObject;
|
||||
// 初始值
|
||||
initialValue?: any;
|
||||
// 可选项(适用于select组件)
|
||||
options?: { label: string; value: any }[];
|
||||
}>;
|
||||
// 列选项
|
||||
tableColumns: Array<{
|
||||
type?: "default" | "selection" | "index" | "expand";
|
||||
label?: string;
|
||||
prop?: string;
|
||||
width?: string | number;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
}
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selectConfig: ISelectConfig;
|
||||
text?: string;
|
||||
}>(),
|
||||
{
|
||||
text: "",
|
||||
}
|
||||
);
|
||||
|
||||
// 自定义事件
|
||||
const emit = defineEmits<{
|
||||
confirmClick: [selection: any[]];
|
||||
}>();
|
||||
|
||||
// 主键
|
||||
const pk = props.selectConfig.pk ?? "id";
|
||||
// 是否多选
|
||||
const isMultiple = props.selectConfig.multiple === true;
|
||||
// 宽度
|
||||
const width = props.selectConfig.width ?? "100%";
|
||||
// 占位符
|
||||
const placeholder = props.selectConfig.placeholder ?? "请选择";
|
||||
// 是否显示弹出框
|
||||
const popoverVisible = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
// 数据总数
|
||||
const total = ref(0);
|
||||
// 列表数据
|
||||
const pageData = ref<IObject[]>([]);
|
||||
// 每页条数
|
||||
const pageSize = 10;
|
||||
// 搜索参数
|
||||
const queryParams = reactive<{
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
[key: string]: any;
|
||||
}>({
|
||||
pageNum: 1,
|
||||
pageSize: pageSize,
|
||||
});
|
||||
|
||||
// 计算popover的宽度
|
||||
const tableSelectRef = ref();
|
||||
const popoverWidth = ref(width);
|
||||
useResizeObserver(tableSelectRef, (entries) => {
|
||||
popoverWidth.value = `${entries[0].contentRect.width}px`;
|
||||
});
|
||||
|
||||
// 表单操作
|
||||
const formRef = ref<FormInstance>();
|
||||
// 初始化搜索条件
|
||||
for (const item of props.selectConfig.formItems) {
|
||||
queryParams[item.prop] = item.initialValue ?? "";
|
||||
}
|
||||
// 重置操作
|
||||
function handleReset() {
|
||||
formRef.value?.resetFields();
|
||||
fetchPageData(true);
|
||||
}
|
||||
// 查询操作
|
||||
function handleQuery() {
|
||||
fetchPageData(true);
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
function fetchPageData(isRestart = false) {
|
||||
loading.value = true;
|
||||
if (isRestart) {
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.pageSize = pageSize;
|
||||
}
|
||||
props.selectConfig
|
||||
.indexAction(queryParams)
|
||||
.then((data) => {
|
||||
total.value = data.total;
|
||||
pageData.value = data.list;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 列表操作
|
||||
const tableRef = ref<TableInstance>();
|
||||
// 数据刷新后是否保留选项
|
||||
for (const item of props.selectConfig.tableColumns) {
|
||||
if (item.type === "selection") {
|
||||
item.reserveSelection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 选择
|
||||
const selectedItems = ref<IObject[]>([]);
|
||||
const confirmText = computed(() => {
|
||||
return selectedItems.value.length > 0
|
||||
? `已选(${selectedItems.value.length})`
|
||||
: "确 定";
|
||||
});
|
||||
function handleSelect(selection: any[], row: any) {
|
||||
if (isMultiple || selection.length === 0) {
|
||||
// 多选
|
||||
selectedItems.value = selection;
|
||||
} else {
|
||||
// 单选
|
||||
selectedItems.value = [selection[selection.length - 1]];
|
||||
tableRef.value?.clearSelection();
|
||||
tableRef.value?.toggleRowSelection(selectedItems.value[0], true);
|
||||
tableRef.value?.setCurrentRow(selectedItems.value[0]);
|
||||
}
|
||||
}
|
||||
function handleSelectAll(selection: any[]) {
|
||||
if (isMultiple) {
|
||||
selectedItems.value = selection;
|
||||
}
|
||||
}
|
||||
// 分页
|
||||
function handlePagination() {
|
||||
fetchPageData();
|
||||
}
|
||||
|
||||
// 弹出框
|
||||
const isInit = ref(false);
|
||||
// 显示
|
||||
function handleShow() {
|
||||
if (isInit.value === false) {
|
||||
isInit.value = true;
|
||||
fetchPageData();
|
||||
}
|
||||
}
|
||||
// 确定
|
||||
function handleConfirm() {
|
||||
if (selectedItems.value.length === 0) {
|
||||
ElMessage.error("请选择数据");
|
||||
return;
|
||||
}
|
||||
popoverVisible.value = false;
|
||||
emit("confirmClick", selectedItems.value);
|
||||
}
|
||||
// 清空
|
||||
function handleClear() {
|
||||
tableRef.value?.clearSelection();
|
||||
selectedItems.value = [];
|
||||
}
|
||||
// 关闭
|
||||
function handleClose() {
|
||||
popoverVisible.value = false;
|
||||
}
|
||||
const popoverContentRef = ref();
|
||||
/* onClickOutside(tableSelectRef, () => (popoverVisible.value = false), {
|
||||
ignore: [popoverContentRef],
|
||||
}); */
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reference :deep(.el-input__wrapper),
|
||||
.reference :deep(.el-input__inner) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6px;
|
||||
}
|
||||
// 隐藏全选按钮
|
||||
.radio :deep(.el-table__header th.el-table__cell:nth-child(1) .el-checkbox) {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,134 @@
|
|||
<!-- 多图上传组件 -->
|
||||
<template>
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:http-request="handleUpload"
|
||||
:on-remove="handleRemove"
|
||||
:on-preview="previewImg"
|
||||
:limit="props.limit"
|
||||
>
|
||||
<i-ep-plus />
|
||||
</el-upload>
|
||||
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<img w-full :src="previewImgUrl" alt="Preview Image" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
UploadRawFile,
|
||||
UploadRequestOptions,
|
||||
UploadUserFile,
|
||||
UploadFile,
|
||||
UploadProps,
|
||||
} from "element-plus";
|
||||
import FileAPI from "@/api/file";
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 文件路径集合
|
||||
*/
|
||||
modelValue: {
|
||||
type: Array<string>,
|
||||
default: () => [],
|
||||
},
|
||||
/**
|
||||
* 文件上传数量限制
|
||||
*/
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const previewImgUrl = ref("");
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
const fileList = ref([] as UploadUserFile[]);
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal: string[]) => {
|
||||
const filePaths = fileList.value.map((file) => file.url);
|
||||
// 监听modelValue文件集合值未变化时,跳过赋值
|
||||
if (
|
||||
filePaths.length > 0 &&
|
||||
filePaths.length === newVal.length &&
|
||||
filePaths.every((x) => newVal.some((y) => y === x)) &&
|
||||
newVal.every((y) => filePaths.some((x) => x === y))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileList.value = newVal.map((filePath) => {
|
||||
return { url: filePath } as UploadUserFile;
|
||||
});
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/**
|
||||
* 自定义图片上传
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
async function handleUpload(options: UploadRequestOptions): Promise<any> {
|
||||
// 上传API调用
|
||||
const data = await FileAPI.upload(options.file);
|
||||
|
||||
// 上传成功需手动替换文件路径为远程URL,否则图片地址为预览地址 blob:http://
|
||||
const fileIndex = fileList.value.findIndex(
|
||||
(file) => file.uid == (options.file as any).uid
|
||||
);
|
||||
|
||||
fileList.value.splice(fileIndex, 1, {
|
||||
name: data.name,
|
||||
url: data.url,
|
||||
} as UploadUserFile);
|
||||
|
||||
emit(
|
||||
"update:modelValue",
|
||||
fileList.value.map((file) => file.url)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除图片
|
||||
*/
|
||||
function handleRemove(removeFile: UploadFile) {
|
||||
const filePath = removeFile.url;
|
||||
|
||||
if (filePath) {
|
||||
FileAPI.deleteByPath(filePath).then(() => {
|
||||
// 删除成功回调
|
||||
emit(
|
||||
"update:modelValue",
|
||||
fileList.value.map((file) => file.url)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制用户上传文件的格式和大小
|
||||
*/
|
||||
function handleBeforeUpload(file: UploadRawFile) {
|
||||
if (file.size > 2 * 1048 * 1048) {
|
||||
ElMessage.warning("上传图片不能大于2M");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览图片
|
||||
*/
|
||||
const previewImg: UploadProps["onPreview"] = (uploadFile) => {
|
||||
previewImgUrl.value = uploadFile.url!;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
</script>
|