更新 vue-element-plus-admin 到最新版本 2.5.6

This commit is contained in:
ktianc 2024-01-21 10:16:32 +08:00
parent bade36dd1b
commit 9ceeacb97b
109 changed files with 1892 additions and 920 deletions

View File

@ -1,20 +1,20 @@
{
"name": "vue-element-plus-admin",
"version": "2.3.0",
"version": "2.5.6",
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
"author": "Archer <502431556@qq.com>",
"private": false,
"scripts": {
"i": "pnpm install",
"dev": "vite --mode dev",
"ts:check": "vue-tsc --noEmit --skipLibCheck",
"build:pro": "vite build --mode pro",
"build:dev": "vite build --mode dev",
"serve:pro": "vite preview --mode pro",
"serve:dev": "vite preview --mode dev",
"npm:check": "npx npm-check-updates",
"clean": "npx rimraf node_modules",
"clean:cache": "npx rimraf node_modules/.cache",
"dev": "pnpm vite --mode dev",
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
"build:pro": "pnpm vite build --mode pro",
"build:dev": "pnpm vite build --mode dev",
"serve:pro": "pnpm vite preview --mode pro",
"serve:dev": "pnpm vite preview --mode dev",
"npm:check": "pnpx npm-check-updates -u",
"clean": "pnpx rimraf node_modules",
"clean:cache": "pnpx rimraf node_modules/.cache",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
@ -22,100 +22,105 @@
"icon": "esno ./scripts/icon.ts"
},
"dependencies": {
"@amap/amap-jsapi-loader": "1.0.1",
"@iconify/iconify": "3.1.1",
"@iconify/vue": "4.1.1",
"@vueuse/core": "10.3.0",
"@vueuse/core": "10.7.1",
"@wangeditor/editor": "5.1.23",
"@wangeditor/editor-for-vue": "5.1.10",
"@zxcvbn-ts/core": "3.0.3",
"@zxcvbn-ts/core": "3.0.4",
"animate.css": "4.1.1",
"axios": "1.4.0",
"dayjs": "1.11.9",
"driver.js": "1.2.1",
"axios": "1.6.5",
"cropperjs": "1.6.1",
"dayjs": "1.11.10",
"driver.js": "1.3.1",
"echarts": "5.4.3",
"echarts-wordcloud": "2.1.0",
"element-plus": "2.3.9",
"element-plus": "2.4.4",
"lodash-es": "4.17.21",
"mitt": "3.0.1",
"mockjs": "1.1.0",
"nprogress": "0.2.0",
"pinia": "2.1.6",
"pinia-plugin-persist": "1.0.0",
"pinia": "2.1.7",
"pinia-plugin-persistedstate": "3.2.1",
"qrcode": "1.5.3",
"qs": "6.11.2",
"url": "0.11.1",
"vue": "3.3.4",
"vue-i18n": "9.2.2",
"vue-json-pretty": "2.2.4",
"vue-router": "4.2.4",
"vue-types": "5.1.1"
"url": "0.11.3",
"vue": "3.4.7",
"vue-draggable-plus": "0.3.4",
"vue-i18n": "9.9.0",
"vue-json-pretty": "2.3.0",
"vue-router": "4.2.5",
"vue-types": "5.1.1",
"xgplayer": "3.0.11"
},
"devDependencies": {
"@iconify/json": "2.2.101",
"@intlify/unplugin-vue-i18n": "0.12.2",
"@amap/amap-jsapi-loader": "1.0.1",
"@commitlint/cli": "18.4.4",
"@commitlint/config-conventional": "18.4.4",
"@iconify/json": "2.2.166",
"@intlify/unplugin-vue-i18n": "2.0.0",
"@kjgl77/datav-vue3": "1.6.1",
"@purge-icons/generated": "0.9.0",
"@types/fs-extra": "11.0.2",
"@types/inquirer": "9.0.3",
"@types/lodash-es": "4.17.8",
"@types/node": "20.4.10",
"@types/nprogress": "0.2.0",
"@types/qrcode": "1.5.1",
"@types/qs": "6.9.7",
"@types/sortablejs": "1.15.1",
"@typescript-eslint/eslint-plugin": "6.3.0",
"@typescript-eslint/parser": "6.3.0",
"@unocss/transformer-variant-group": "0.55.0",
"@vitejs/plugin-legacy": "4.1.1",
"@vitejs/plugin-vue": "4.2.3",
"@vitejs/plugin-vue-jsx": "3.0.1",
"autoprefixer": "10.4.14",
"@types/fs-extra": "11.0.4",
"@types/inquirer": "9.0.7",
"@types/lodash-es": "4.17.12",
"@types/node": "20.10.8",
"@types/nprogress": "0.2.3",
"@types/qrcode": "1.5.5",
"@types/qs": "6.9.11",
"@types/sortablejs": "1.15.7",
"@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1",
"@unocss/transformer-variant-group": "0.58.3",
"@vitejs/plugin-legacy": "5.2.0",
"@vitejs/plugin-vue": "5.0.2",
"@vitejs/plugin-vue-jsx": "3.1.0",
"autoprefixer": "10.4.16",
"chalk": "5.3.0",
"consola": "3.2.3",
"cron-validate": "1.4.5",
"eslint": "8.47.0",
"eslint-config-prettier": "9.0.0",
"eslint-define-config": "1.23.0",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-vue": "9.17.0",
"esno": "0.17.0",
"fs-extra": "11.1.1",
"intro.js": "7.2.0",
"inquirer": "9.2.11",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-define-config": "2.1.0",
"eslint-plugin-prettier": "5.1.2",
"eslint-plugin-vue": "9.19.2",
"esno": "4.0.0",
"fs-extra": "11.2.0",
"inquirer": "9.2.12",
"less": "4.2.0",
"lint-staged": "13.2.3",
"lint-staged": "15.2.0",
"lodash": "4.17.21",
"moment": "2.29.4",
"plop": "3.1.2",
"postcss": "8.4.27",
"moment": "2.30.1",
"plop": "4.0.1",
"postcss": "8.4.33",
"postcss-html": "1.5.0",
"postcss-less": "6.0.0",
"prettier": "3.0.1",
"rimraf": "5.0.1",
"rollup": "3.28.0",
"stylelint": "15.10.2",
"prettier": "3.1.1",
"rimraf": "5.0.5",
"rollup": "4.9.4",
"rollup-plugin-visualizer": "5.12.0",
"stylelint": "16.1.0",
"stylelint-config-html": "1.1.0",
"stylelint-config-recommended": "13.0.0",
"stylelint-config-standard": "34.0.0",
"stylelint-order": "6.0.3",
"terser": "5.19.2",
"typescript": "5.1.6",
"unocss": "0.55.0",
"vite": "4.4.9",
"vite-plugin-ejs": "1.6.4",
"stylelint-config-recommended": "14.0.0",
"stylelint-config-standard": "36.0.0",
"stylelint-order": "6.0.4",
"terser": "5.26.0",
"typescript": "5.3.3",
"unocss": "0.58.3",
"vite": "5.0.11",
"vite-plugin-ejs": "1.7.0",
"vite-plugin-eslint": "1.8.1",
"vite-plugin-mock": "2.9.6",
"vite-plugin-progress": "0.0.7",
"vite-plugin-purge-icons": "0.9.2",
"vite-plugin-purge-icons": "0.10.0",
"vite-plugin-style-import": "2.0.0",
"vite-plugin-svg-icons": "2.0.1",
"vue-draggable-plus": "0.2.6",
"vue-tsc": "1.8.8",
"vue3-json-viewer": "2.2.2"
"vue-tsc": "1.8.27",
"vue3-json-viewer": "2.2.2",
"zipson": "^0.2.12"
},
"packageManager": "pnpm@8.1.0",
"engines": {
"node": ">= 14.18.0"
"node": ">=18.0.0",
"pnpm": ">=8.1.0"
},
"license": "MIT",
"repository": {

View File

@ -2,9 +2,7 @@
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { ConfigGlobal } from '@/components/ConfigGlobal'
import { isDark } from '@/utils/is'
import { useDesign } from '@/hooks/web/useDesign'
import { useStorage } from '@/hooks/web/useStorage'
import { getSystemBaseConfigApi } from '@/api/vadmin/system/settings'
const { getPrefixCls } = useDesign()
@ -17,18 +15,6 @@ const currentSize = computed(() => appStore.getCurrentSize)
const greyMode = computed(() => appStore.getGreyMode)
const { getStorage } = useStorage()
//
const setDefaultTheme = () => {
if (getStorage('isDark') !== null) {
appStore.setIsDark(getStorage('isDark'))
return
}
const isDarkTheme = isDark()
appStore.setIsDark(isDarkTheme)
}
// mate
const addMeta = (name: string, content: string) => {
const meta = document.createElement('meta')
@ -39,6 +25,9 @@ const addMeta = (name: string, content: string) => {
//
const setSystemConfig = async () => {
if (appStore.getLogoImage) {
return
}
const res = await getSystemBaseConfigApi()
if (res) {
appStore.setTitle(res.data.web_title || import.meta.env.VITE_APP_TITLE)
@ -52,7 +41,7 @@ const setSystemConfig = async () => {
}
}
setDefaultTheme()
appStore.initTheme()
setSystemConfig()
</script>

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<script lang="tsx">
import { ElCollapseTransition, ElDescriptions, ElDescriptionsItem, ElTooltip } from 'element-plus'
import { ElCollapseTransition, ElTooltip, ElRow, ElCol } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes'
import { ref, unref, PropType, computed, defineComponent } from 'vue'
@ -16,6 +16,8 @@ const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('descriptions')
const defaultData = '-'
export default defineComponent({
name: 'Descriptions',
props: {
@ -36,7 +38,7 @@ export default defineComponent({
default: () => ({})
}
},
setup(props, { slots, attrs }) {
setup(props, { attrs }) {
const getBindValue = computed((): any => {
const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
const obj = { ...attrs, ...props }
@ -59,7 +61,10 @@ export default defineComponent({
delete obj[key]
}
}
return obj
return {
labelClassName: `${prefixCls}-label`,
...obj
}
}
//
@ -103,26 +108,51 @@ export default defineComponent({
<ElCollapseTransition>
<div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
<ElDescriptions {...unref(getBindValue)}>
{{
extra: () => (slots['extra'] ? slots['extra']() : props.extra),
default: () => {
return props.schema.map((item) => {
return (
<ElDescriptionsItem key={item.field} {...getBindItemValue(item)}>
{{
label: () => (item.slots?.label ? item.slots?.label(item) : item.label),
default: () =>
item.slots?.default
? item.slots?.default(props.data)
: get(props.data, item.field)
}}
</ElDescriptionsItem>
)
})
}
}}
</ElDescriptions>
<ElRow
gutter={0}
{...unref(getBindValue)}
class="outline-1px outline-[var(--el-border-color-lighter)] outline-solid"
>
{props.schema.map((item) => {
return (
<ElCol
key={item.field}
span={item.span || 24 / props.column}
class="flex items-stretch"
>
{props.direction === 'horizontal' ? (
<div class="flex items-stretch bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
<div
{...getBindItemValue(item)}
class="w-120px text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-r-1px border-r-[var(--el-border-color-lighter)] border-r-solid "
>
{item.label}
</div>
<div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
{item.slots?.default
? item.slots?.default(props.data)
: get(props.data, item.field) ?? defaultData}
</div>
</div>
) : (
<div class="bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
<div
{...getBindItemValue(item)}
class="text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-b-1px border-b-[var(--el-border-color-lighter)] border-b-solid"
>
{item.label}
</div>
<div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
{item.slots?.default
? item.slots?.default(props.data)
: get(props.data, item.field) ?? defaultData}
</div>
</div>
)}
</ElCol>
)
})}
</ElRow>
</div>
</ElCollapseTransition>
</div>
@ -153,9 +183,13 @@ export default defineComponent({
}
}
.@{prefix-cls}-content {
:deep(.@{elNamespace}-descriptions__cell) {
width: 0;
}
:deep(.@{prefix-cls}-label) {
width: 150px !important;
}
// .@{prefix-cls}-content {
// :deep(.@{elNamespace}-descriptions__cell) {
// width: 0;
// }
// }
</style>

View File

@ -10,7 +10,6 @@ const props = defineProps({
modelValue: propTypes.bool.def(false),
title: propTypes.string.def('Dialog'),
fullscreen: propTypes.bool.def(true),
top: propTypes.string.def('8vh'),
height: propTypes.oneOfType([String, Number]).def('500px'),
width: propTypes.oneOfType([String, Number]).def('700px')
@ -109,26 +108,30 @@ const dialogStyle = computed(() => {
</template>
<style lang="less">
// .@{elNamespace}-overlay-dialog {
// display: flex;
// justify-content: center;
// align-items: center;
// }
.@{elNamespace}-overlay-dialog {
display: flex;
justify-content: center;
align-items: center;
}
.@{elNamespace}-dialog {
// margin: 0 !important;
margin: 0 !important;
&__header {
height: 54px;
padding: 0;
margin-right: 0 !important;
border-bottom: 1px solid var(--el-border-color);
padding: 0;
height: 54px;
}
&__body {
padding: 15px !important;
}
&__footer {
border-top: 1px solid var(--el-border-color);
}
&__headerbtn {
top: 0;
}

View File

@ -4,7 +4,6 @@ import networkError from '@/assets/svgs/500.svg'
import noPermission from '@/assets/svgs/403.svg'
import { propTypes } from '@/utils/propTypes'
import { useI18n } from '@/hooks/web/useI18n'
import { ElButton } from 'element-plus'
interface ErrorMap {
url: string
@ -51,7 +50,7 @@ const btnClick = () => {
<img width="350" :src="errorMap[type].url" alt="" />
<div class="text-14px text-[var(--el-color-info)]">{{ errorMap[type].message }}</div>
<div class="mt-20px">
<ElButton type="primary" @click="btnClick">{{ errorMap[type].buttonText }}</ElButton>
<BaseButton type="primary" @click="btnClick">{{ errorMap[type].buttonText }}</BaseButton>
</div>
</div>
</div>

View File

@ -15,7 +15,7 @@ const title = computed(() => appStore.getTitle)
<template>
<div
:class="prefixCls"
class="text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
class="shrink-0 text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
>
Copyright ©2021-present {{ title }}
</div>

View File

@ -95,9 +95,6 @@ export default defineComponent({
// element form
const elFormRef = ref<ComponentRef<typeof ElForm>>()
// useFormprops
const outsideProps = ref<FormProps>({})
const mergeProps = ref<FormProps>({})
const getProps = computed(() => {
@ -155,8 +152,6 @@ export default defineComponent({
const setProps = (props: FormProps = {}) => {
mergeProps.value = Object.assign(unref(mergeProps), props)
// @ts-ignore
outsideProps.value = props
}
const delSchema = (field: string) => {
@ -364,13 +359,31 @@ export default defineComponent({
}
})
return (
return item.component === ComponentNameEnum.UPLOAD ? (
<Com
vModel:file-list={itemVal.value}
ref={(el: any) => setComponentRefMap(el, item.field)}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
style={
item.componentProps?.style || {
width: '100%'
}
}
>
{{ ...slotsMap }}
</Com>
) : (
<Com
vModel={itemVal.value}
ref={(el: any) => setComponentRefMap(el, item.field)}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
style={item.componentProps?.style || {}}
style={
item.componentProps?.style || {
width: '100%'
}
}
>
{{ ...slotsMap }}
</Com>
@ -447,6 +460,10 @@ export default defineComponent({
{...getFormBindValue()}
model={unref(getProps).isCustom ? unref(getProps).model : formModel}
class={prefixCls}
// @ts-ignore
onSubmit={(e: Event) => {
e.preventDefault()
}}
>
{{
//
@ -466,4 +483,8 @@ export default defineComponent({
margin-right: 0 !important;
margin-left: 0 !important;
}
.@{elNamespace}-form--inline .@{elNamespace}-input {
width: 245px;
}
</style>

View File

@ -25,6 +25,7 @@ import { Editor } from '@/components/Editor'
import { Text } from '@/components/Text'
import { JsonEditor } from '@/components/JsonEditor'
import { ComponentName } from '../types'
import { IconPicker } from '@/components/IconPicker'
const componentMap: Recordable<Component, ComponentName> = {
RadioGroup: ElRadioGroup,
@ -51,6 +52,7 @@ const componentMap: Recordable<Component, ComponentName> = {
TreeSelect: ElTreeSelect,
Upload: ElUpload,
JsonEditor: JsonEditor,
IconPicker: IconPicker,
Text: Text
}

View File

@ -56,6 +56,7 @@ export enum ComponentNameEnum {
TREE_SELECT = 'TreeSelect',
UPLOAD = 'Upload',
JSON_EDITOR = 'JsonEditor',
ICON_PICKER = 'IconPicker',
Text = 'Text'
}

View File

@ -25,6 +25,11 @@ const symbolId = computed(() => {
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
})
// 使线
const isUseOnline = computed(() => {
return import.meta.env.VITE_USE_ONLINE_ICON === 'true'
})
const getIconifyStyle = computed(() => {
const { color, size } = props
return {
@ -40,7 +45,10 @@ const getIconifyStyle = computed(() => {
<use :xlink:href="symbolId" />
</svg>
<Icon v-else :icon="icon" :style="getIconifyStyle" />
<template v-else>
<Icon v-if="isUseOnline" :icon="icon" :style="getIconifyStyle" />
<div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
</template>
</ElIcon>
</template>
@ -49,11 +57,18 @@ const getIconifyStyle = computed(() => {
.@{prefix-cls},
.iconify {
&:hover {
:deep(svg) {
:deep(svg) {
&:hover {
// stylelint-disable-next-line
color: v-bind(hoverColor) !important;
}
}
}
.iconify {
&:hover {
// stylelint-disable-next-line
color: v-bind(hoverColor) !important;
}
}
</style>

View File

@ -7,12 +7,6 @@ import { ElInput, ElPopover, ElScrollbar, ElTabs, ElTabPane, ElPagination } from
import { useAppStore } from '@/store/modules/app'
import { computed, CSSProperties, ref, unref, watch } from 'vue'
import { nextTick } from 'vue'
import { propTypes } from '@/utils/propTypes'
defineProps({
//
inputDisabled: propTypes.bool.def(false)
})
const init = async (icon?: string) => {
if (!icon) return
@ -36,8 +30,8 @@ const iconSize = computed(() => {
return unref(size) === 'small'
? 'var(--el-component-size-small)'
: unref(size) === 'large'
? 'var(--el-component-size-large)'
: 'var(--el-component-size)'
? 'var(--el-component-size-large)'
: 'var(--el-component-size)'
})
const iconWrapStyle = computed((): CSSProperties => {
@ -96,6 +90,11 @@ const popoverShow = () => {
}
const iconSelect = (icon: string) => {
// icon
if (icon === unref(modelValue)) {
modelValue.value = ''
return
}
modelValue.value = icon
}
@ -112,7 +111,7 @@ const inputClear = () => {
<template>
<div :class="prefixCls" class="flex justify-center items-center box">
<ElInput :disabled="inputDisabled" v-model="modelValue" />
<ElInput disabled v-model="modelValue" clearable />
<ElPopover
placement="bottom"
trigger="click"
@ -123,7 +122,6 @@ const inputClear = () => {
<template #reference>
<div :style="iconWrapStyle">
<Icon v-if="modelValue" :icon="modelValue" />
<Icon v-else icon="ep:setting" />
</div>
</template>
<ElScrollbar class="h-[calc(100%-50px)]!">
@ -151,8 +149,10 @@ const inputClear = () => {
icon === modelValue ? 'var(--el-color-primary)' : 'var(--el-border-color)'
}`,
boxSizing: 'border-box',
margin: '2px'
margin: '2px',
transition: 'all 0.3s'
}"
class="hover:border-color-[var(--el-color-primary)]!"
@click="iconSelect(icon)"
>
<Icon

View File

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

View File

@ -0,0 +1,245 @@
<script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign'
import { nextTick, unref, ref, watch, onBeforeUnmount, onMounted, computed } from 'vue'
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.min.css'
import { ElDivider, ElUpload, UploadFile, ElMessage, ElTooltip } from 'element-plus'
import { useDebounceFn } from '@vueuse/core'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('image-cropping')
const props = defineProps({
imageUrl: {
type: String,
default: '',
required: true
},
cropBoxWidth: {
type: Number,
default: 200
},
cropBoxHeight: {
type: Number,
default: 200
},
boxWidth: {
type: [Number, String],
default: 425
},
boxHeight: {
type: [Number, String],
default: 320
},
showResult: {
type: Boolean,
default: true
},
showActions: {
type: Boolean,
default: true
}
})
const getBase64 = useDebounceFn(() => {
imgBase64.value = unref(cropperRef)?.getCroppedCanvas()?.toDataURL() ?? ''
}, 80)
const resetCropBox = () => {
const containerData = unref(cropperRef)?.getContainerData()
unref(cropperRef)?.setCropBoxData({
width: props.cropBoxWidth,
height: props.cropBoxHeight,
left: (containerData?.width || 0) / 2 - 100,
top: (containerData?.height || 0) / 2 - 100
})
imgBase64.value = unref(cropperRef)?.getCroppedCanvas()?.toDataURL() ?? ''
}
const getBoxStyle = computed(() => {
return {
width: `${props.boxWidth}px`,
height: `${props.boxHeight}px`
}
})
const getCropBoxStyle = computed(() => {
return {
width: `${props.cropBoxWidth}px`,
height: `${props.cropBoxHeight}px`
}
})
//
const getScaleSize = (scale: number) => {
return {
width: props.cropBoxWidth * scale + 'px',
height: props.cropBoxHeight * scale + 'px'
}
}
const imgBase64 = ref('')
const imgRef = ref<HTMLImageElement>()
const cropperRef = ref<Cropper>()
const intiCropper = () => {
if (!unref(imgRef)) return
const imgEl = unref(imgRef)!
cropperRef.value = new Cropper(imgEl, {
aspectRatio: 1,
viewMode: 1,
dragMode: 'move',
// cropBoxResizable: false,
// cropBoxMovable: false,
toggleDragModeOnDblclick: false,
checkCrossOrigin: false,
ready() {
resetCropBox()
},
cropmove() {
getBase64()
},
zoom() {
getBase64()
},
crop() {
getBase64()
}
})
}
const uploadChange = (uploadFile: UploadFile) => {
//
if (uploadFile?.raw?.type.indexOf('image') === -1) {
ElMessage.error('请上传图片格式的文件')
return
}
if (!uploadFile.raw) return
// 访
const url = URL.createObjectURL(uploadFile.raw)
unref(cropperRef)?.replace(url)
}
const reset = () => {
unref(cropperRef)?.reset()
}
const rotate = (deg: number) => {
unref(cropperRef)?.rotate(deg)
}
const scaleX = ref(1)
const scaleY = ref(1)
const scale = (type: 'scaleX' | 'scaleY') => {
if (type === 'scaleX') {
scaleX.value = scaleX.value === 1 ? -1 : 1
unref(cropperRef)?.[type](unref(scaleX))
} else {
scaleY.value = scaleY.value === 1 ? -1 : 1
unref(cropperRef)?.[type](unref(scaleY))
}
}
const zoom = (num: number) => {
unref(cropperRef)?.zoom(num)
}
onMounted(() => {
intiCropper()
})
watch(
() => props.imageUrl,
async (url) => {
if (url) {
unref(cropperRef)?.replace(url)
await nextTick()
resetCropBox()
}
}
)
onBeforeUnmount(() => {
unref(cropperRef)?.destroy()
})
defineExpose({
cropperExpose: cropperRef
})
</script>
<template>
<div
:class="{
[prefixCls]: true,
'flex items-center': showResult
}"
>
<div>
<div :style="getBoxStyle" class="flex justify-center items-center">
<img
v-show="imageUrl"
ref="imgRef"
:src="imageUrl"
class="block max-w-full"
crossorigin="anonymous"
alt=""
srcset=""
/>
</div>
<div v-if="showActions" class="mt-10px flex items-center">
<div class="flex items-center">
<ElTooltip content="选择文件" placement="bottom">
<ElUpload
action="''"
accept="image/*"
:auto-upload="false"
:show-file-list="false"
:on-change="uploadChange"
>
<BaseButton size="small" type="primary" class="mt-2px"
><Icon icon="ep:upload-filled"
/></BaseButton>
</ElUpload>
</ElTooltip>
</div>
<div class="flex items-center justify-end flex-1">
<ElTooltip content="重置" placement="bottom">
<BaseButton size="small" type="primary" @click="reset"
><Icon icon="ep:refresh"
/></BaseButton>
</ElTooltip>
<ElTooltip content="逆时针旋转" placement="bottom">
<BaseButton size="small" type="primary" @click="rotate(-45)"
><Icon icon="ant-design:rotate-left-outlined"
/></BaseButton>
</ElTooltip>
<ElTooltip content="顺时针旋转" placement="bottom">
<BaseButton size="small" type="primary" @click="rotate(45)"
><Icon icon="ant-design:rotate-right-outlined"
/></BaseButton>
</ElTooltip>
<ElTooltip content="水平翻转" placement="bottom">
<BaseButton size="small" type="primary" @click="scale('scaleX')"
><Icon icon="vaadin:arrows-long-h"
/></BaseButton>
</ElTooltip>
<ElTooltip content="垂直翻转" placement="bottom">
<BaseButton size="small" type="primary" @click="scale('scaleY')"
><Icon icon="vaadin:arrows-long-v"
/></BaseButton>
</ElTooltip>
<ElTooltip content="放大" placement="bottom">
<BaseButton size="small" type="primary" @click="zoom(0.1)"
><Icon icon="ant-design:zoom-in-outlined"
/></BaseButton>
</ElTooltip>
<ElTooltip content="缩小" placement="bottom">
<BaseButton size="small" type="primary" @click="zoom(-0.1)"
><Icon icon="ant-design:zoom-out-outlined"
/></BaseButton>
</ElTooltip>
</div>
</div>
</div>
<div v-if="imgBase64 && showResult" class="ml-20px">
<div class="flex justify-center items-center">
<img :src="imgBase64" class="rounded-[50%]" :style="getCropBoxStyle" />
</div>
<ElDivider />
<div class="flex justify-center items-center">
<img :src="imgBase64" class="rounded-[50%]" :style="getScaleSize(0.2)" />
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.25)" />
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.3)" />
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.35)" />
</div>
</div>
</div>
</template>

View File

@ -86,7 +86,7 @@ const getPasswordStrength = computed(() => {
background-color: transparent;
border-color: var(--el-color-white);
border-style: solid;
border-width: 0 5px 0 5px;
border-width: 0 5px;
content: '';
}

View File

@ -30,13 +30,7 @@ watch(
show.value = true
return
}
if (!collapse) {
setTimeout(() => {
show.value = !collapse
}, 400)
} else {
show.value = !collapse
}
show.value = !collapse
}
)

View File

@ -123,30 +123,10 @@ export default defineComponent({
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-menu';
// .is-active--after {
// position: absolute;
// top: 0;
// right: 0;
// width: 4px;
// height: 100%;
// background-color: var(--el-color-primary);
// content: '';
// }
.@{prefix-cls} {
position: relative;
transition: width var(--transition-time-02);
// &:after {
// position: absolute;
// top: 0;
// right: 0;
// height: 100%;
// width: 1px;
// background-color: var(--el-border-color);
// content: '';
// }
:deep(.@{elNamespace}-menu) {
width: 100% !important;
border-right: none;
@ -168,7 +148,6 @@ export default defineComponent({
}
//
.@{elNamespace}-sub-menu.is-active,
.@{elNamespace}-menu-item.is-active {
color: var(--left-menu-text-active-color) !important;
background-color: var(--left-menu-bg-active-color) !important;
@ -180,10 +159,6 @@ export default defineComponent({
.@{elNamespace}-menu-item.is-active {
position: relative;
// &:after {
// .is-active--after;
// }
}
//
@ -203,16 +178,11 @@ export default defineComponent({
& > .is-active > .@{elNamespace}-sub-menu__title {
position: relative;
background-color: var(--left-menu-collapse-bg-active-color) !important;
// &:after {
// .is-active--after;
// }
}
}
//
:deep(.horizontal-collapse-transition) {
// transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;
.@{prefix-cls}__title {
display: none;
}
@ -235,7 +205,7 @@ export default defineComponent({
.@{elNamespace}-menu-item.is-active {
position: relative;
&:after {
&::after {
display: none !important;
}
}
@ -254,16 +224,6 @@ export default defineComponent({
<style lang="less">
@prefix-cls: ~'@{namespace}-menu-popper';
// .is-active--after {
// position: absolute;
// top: 0;
// right: 0;
// width: 4px;
// height: 100%;
// background-color: var(--el-color-primary);
// content: '';
// }
.@{prefix-cls}--vertical,
.@{prefix-cls}--horizontal {
//
@ -290,10 +250,6 @@ export default defineComponent({
&:hover {
background-color: var(--left-menu-bg-active-color) !important;
}
// &:after {
// .is-active--after;
// }
}
}
</style>

View File

@ -12,9 +12,10 @@ export const useRenderMenuItem = (
menuMode: 'vertical' | 'horizontal'
) => {
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
return routers.map((v) => {
const meta = v.meta ?? {}
if (!meta.hidden) {
return routers
.filter((v) => !v.meta?.hidden)
.map((v) => {
const meta = v.meta ?? {}
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
@ -48,8 +49,7 @@ export const useRenderMenuItem = (
</ElSubMenu>
)
}
}
})
})
}
return {

View File

@ -10,10 +10,14 @@ export const useRenderMenuTitle = () => {
return icon ? (
<>
<Icon icon={meta.icon}></Icon>
<span class="v-menu__title">{t(title as string)}</span>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
{t(title as string)}
</span>
</>
) : (
<span class="v-menu__title">{t(title as string)}</span>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
{t(title as string)}
</span>
)
}

View File

@ -242,7 +242,7 @@ const disabledClick = () => {
.@{prefix-cls} {
&--disabled {
background: rgba(255, 255, 255, 0.95);
background: rgb(255 255 255 / 95%);
& > div {
transform: translate(-50%, -50%);

View File

@ -88,6 +88,9 @@ const newSchema = computed(() => {
/>
</div>
)
},
label: () => {
return <span>&nbsp;</span>
}
}
}
@ -117,11 +120,14 @@ const setProps = (props: SearchProps = {}) => {
outsideProps.value = props
}
const schemaRef = ref<FormSchema[]>([])
// formModel
watch(
() => unref(newSchema),
async (schema = []) => {
formModel.value = initModel(schema, unref(formModel))
schemaRef.value = schema
},
{
immediate: true,
@ -241,7 +247,7 @@ const onFormValidate = (prop: FormItemProp, isValid: boolean, message: string) =
hide-required-asterisk
:inline="getProps.inline"
:is-col="getProps.isCol"
:schema="newSchema"
:schema="schemaRef"
@register="formRegister"
@validate="onFormValidate"
/>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import { ElButton } from 'element-plus'
import { useIcon } from '@/hooks/web/useIcon'
import { propTypes } from '@/utils/propTypes'
import { useI18n } from '@/hooks/web/useI18n'
@ -31,7 +30,7 @@ const onExpand = () => {
</script>
<template>
<ElButton
<BaseButton
v-if="showSearch"
type="primary"
:loading="searchLoading"
@ -39,21 +38,21 @@ const onExpand = () => {
@click="onSearch"
>
{{ t('common.query') }}
</ElButton>
<ElButton
</BaseButton>
<BaseButton
v-if="showReset"
:loading="resetLoading"
:icon="useIcon({ icon: 'ep:refresh-right' })"
@click="onReset"
>
{{ t('common.reset') }}
</ElButton>
<ElButton
</BaseButton>
<BaseButton
v-if="showExpand"
:icon="useIcon({ icon: visible ? 'ep:arrow-down' : 'ep:arrow-up' })"
:icon="useIcon({ icon: visible ? 'ep:arrow-up' : 'ep:arrow-down' })"
text
@click="onExpand"
>
{{ t(visible ? 'common.shrink' : 'common.expand') }}
</ElButton>
</BaseButton>
</template>

View File

@ -1,12 +1,11 @@
<script setup lang="ts">
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
import { ref, unref, computed, watch } from 'vue'
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
import { ref, unref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
import { useCssVar } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app'
import { trim, setCssVar } from '@/utils'
import { trim, setCssVar, getCssVar } from '@/utils'
import ColorRadioPicker from './components/ColorRadioPicker.vue'
import InterfaceDisplay from './components/InterfaceDisplay.vue'
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
@ -14,7 +13,7 @@ import { useStorage } from '@/hooks/web/useStorage'
import { useClipboard } from '@vueuse/core'
import { useDesign } from '@/hooks/web/useDesign'
const { removeStorage } = useStorage()
const { clear: storageClear } = useStorage('localStorage')
const { getPrefixCls } = useDesign()
@ -24,8 +23,6 @@ const appStore = useAppStore()
const { t } = useI18n()
const layout = computed(() => appStore.getLayout)
const drawer = ref(false)
//
@ -42,70 +39,27 @@ const setSystemTheme = (color: string) => {
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
const setHeaderTheme = (color: string) => {
const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
const topToolBorderColor = isDarkColor ? color : '#eee'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
appStore.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor,
topToolBorderColor
})
if (unref(layout) === 'top') {
setMenuTheme(color)
}
appStore.setHeaderTheme(color)
}
//
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
const setMenuTheme = (color: string) => {
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
const isDarkColor = colorIsDark(color)
const theme: Recordable = {
//
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
//
leftMenuBgColor: color,
//
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
//
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
//
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
//
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
//
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
// logo
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo
logoBorderColor: isDarkColor ? color : '#eee'
}
appStore.setTheme(theme)
appStore.setCssVarTheme()
appStore.setMenuTheme(color)
}
// layout
watch(
() => layout.value,
(n) => {
if (n === 'top' && !appStore.getIsDark) {
headerTheme.value = '#fff'
setHeaderTheme('#fff')
} else {
setMenuTheme(unref(menuTheme))
}
}
)
// watch(
// () => layout.value,
// (n) => {
// if (n === 'top' && !appStore.getIsDark) {
// headerTheme.value = '#fff'
// setHeaderTheme('#fff')
// } else {
// setMenuTheme(unref(menuTheme))
// }
// }
// )
//
const copyConfig = async () => {
@ -174,7 +128,8 @@ const copyConfig = async () => {
//
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
}
`
`,
legacy: true
})
if (!isSupported) {
ElMessage.error(t('setting.copyFailed'))
@ -188,11 +143,15 @@ const copyConfig = async () => {
//
const clear = () => {
removeStorage('layout')
removeStorage('theme')
removeStorage('isDark')
storageClear()
window.location.reload()
}
const themeChange = () => {
const color = getCssVar('--el-bg-color')
setMenuTheme(color)
setHeaderTheme(color)
}
</script>
<template>
@ -212,7 +171,7 @@ const clear = () => {
<div class="text-center">
<!-- 主题 -->
<ElDivider>{{ t('setting.theme') }}</ElDivider>
<ThemeSwitch />
<ThemeSwitch @change="themeChange" />
<!-- 布局 -->
<ElDivider>{{ t('setting.layout') }}</ElDivider>
@ -253,23 +212,21 @@ const clear = () => {
/>
<!-- 菜单主题 -->
<template v-if="layout !== 'top'">
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
<ColorRadioPicker
v-model="menuTheme"
:schema="[
'#fff',
'#001529',
'#212121',
'#273352',
'#191b24',
'#383f45',
'#001628',
'#344058'
]"
@change="setMenuTheme"
/>
</template>
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
<ColorRadioPicker
v-model="menuTheme"
:schema="[
'#fff',
'#001529',
'#212121',
'#273352',
'#191b24',
'#383f45',
'#001628',
'#344058'
]"
@change="setMenuTheme"
/>
</div>
<!-- 界面显示 -->
@ -278,12 +235,14 @@ const clear = () => {
<ElDivider />
<div>
<ElButton type="primary" class="w-full" @click="copyConfig">{{ t('setting.copy') }}</ElButton>
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
t('setting.copy')
}}</BaseButton>
</div>
<div class="mt-5px">
<ElButton type="danger" class="w-full" @click="clear">
<BaseButton type="danger" class="w-full" @click="clear">
{{ t('setting.clearAndReset') }}
</ElButton>
</BaseButton>
</div>
</ElDrawer>
</template>

View File

@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -79,7 +79,7 @@ const layout = computed(() => appStore.getLayout)
content: '';
}
&:after {
&::after {
position: absolute;
top: 0;
left: 0;
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
content: '';
}
&:after {
&::after {
position: absolute;
top: 0;
left: 0;
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
content: '';
}
&:after {
&::after {
position: absolute;
top: 0;
left: 0;

View File

@ -1,12 +1,11 @@
<script setup lang="ts">
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
import { ref, unref, computed, watch } from 'vue'
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
import { ref, unref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
import { useCssVar } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app'
import { trim, setCssVar } from '@/utils'
import { trim, setCssVar, getCssVar } from '@/utils'
import ColorRadioPicker from './components/ColorRadioPicker.vue'
import InterfaceDisplay from './components/InterfaceDisplay.vue'
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
@ -15,7 +14,7 @@ import { useClipboard } from '@vueuse/core'
import { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes'
const { removeStorage } = useStorage()
const { clear: storageClear } = useStorage('localStorage')
const { getPrefixCls } = useDesign()
@ -29,8 +28,6 @@ const appStore = useAppStore()
const { t } = useI18n()
const layout = computed(() => appStore.getLayout)
const drawer = ref(false)
//
@ -47,70 +44,27 @@ const setSystemTheme = (color: string) => {
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
const setHeaderTheme = (color: string) => {
const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
const topToolBorderColor = isDarkColor ? color : '#eee'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
appStore.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor,
topToolBorderColor
})
if (unref(layout) === 'top') {
setMenuTheme(color)
}
appStore.setHeaderTheme(color)
}
//
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
const setMenuTheme = (color: string) => {
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
const isDarkColor = colorIsDark(color)
const theme: Recordable = {
//
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
//
leftMenuBgColor: color,
//
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
//
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
//
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
//
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
//
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
// logo
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo
logoBorderColor: isDarkColor ? color : '#eee'
}
appStore.setTheme(theme)
appStore.setCssVarTheme()
appStore.setMenuTheme(color)
}
// layout
watch(
() => layout.value,
(n) => {
if (n === 'top' && !appStore.getIsDark) {
headerTheme.value = '#fff'
setHeaderTheme('#fff')
} else {
setMenuTheme(unref(menuTheme))
}
}
)
// watch(
// () => layout.value,
// (n) => {
// if (n === 'top' && !appStore.getIsDark) {
// headerTheme.value = '#fff'
// setHeaderTheme('#fff')
// } else {
// setMenuTheme(unref(menuTheme))
// }
// }
// )
//
const copyConfig = async () => {
@ -179,7 +133,8 @@ const copyConfig = async () => {
//
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
}
`
`,
legacy: true
})
if (!isSupported) {
ElMessage.error(t('setting.copyFailed'))
@ -193,11 +148,15 @@ const copyConfig = async () => {
//
const clear = () => {
removeStorage('layout')
removeStorage('theme')
removeStorage('isDark')
storageClear()
window.location.reload()
}
const themeChange = () => {
const color = getCssVar('--el-bg-color')
setMenuTheme(color)
setHeaderTheme(color)
}
</script>
<template>
@ -219,7 +178,7 @@ const clear = () => {
<div class="text-center">
<!-- 主题 -->
<ElDivider>{{ t('setting.theme') }}</ElDivider>
<ThemeSwitch />
<ThemeSwitch @change="themeChange" />
<!-- 布局 -->
<ElDivider>{{ t('setting.layout') }}</ElDivider>
@ -260,23 +219,21 @@ const clear = () => {
/>
<!-- 菜单主题 -->
<template v-if="layout !== 'top'">
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
<ColorRadioPicker
v-model="menuTheme"
:schema="[
'#fff',
'#001529',
'#212121',
'#273352',
'#191b24',
'#383f45',
'#001628',
'#344058'
]"
@change="setMenuTheme"
/>
</template>
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
<ColorRadioPicker
v-model="menuTheme"
:schema="[
'#fff',
'#001529',
'#212121',
'#273352',
'#191b24',
'#383f45',
'#001628',
'#344058'
]"
@change="setMenuTheme"
/>
</div>
<!-- 界面显示 -->
@ -285,14 +242,14 @@ const clear = () => {
<ElDivider />
<div>
<ElButton type="primary" class="w-full" @click="copyConfig">{{
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
t('setting.copy')
}}</ElButton>
}}</BaseButton>
</div>
<div class="mt-5px">
<ElButton type="danger" class="w-full" @click="clear">
<BaseButton type="danger" class="w-full" @click="clear">
{{ t('setting.clearAndReset') }}
</ElButton>
</BaseButton>
</div>
</ElDrawer>
</div>

View File

@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -79,7 +79,7 @@ const layout = computed(() => appStore.getLayout)
content: '';
}
&:after {
&::after {
position: absolute;
top: 0;
left: 0;
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
content: '';
}
&:after {
&::after {
position: absolute;
top: 0;
left: 0;
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
border: 2px solid #e5e7eb;
border-radius: 4px;
&:before {
&::before {
position: absolute;
top: 0;
left: 0;
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
content: '';
}
&:after {
&::after {
position: absolute;
top: 0;
left: 0;

View File

@ -5,7 +5,9 @@ import {
ElPagination,
ComponentSize,
ElTooltipProps,
ElImage
ElImage,
ElEmpty,
ElCard
} from 'element-plus'
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes'
@ -16,6 +18,9 @@ import { CSSProperties } from 'vue'
import { getSlot } from '@/utils/tsxHelper'
import TableActions from './components/TableActions.vue'
import { useAppStore } from '@/store/modules/app'
import { createVideoViewer } from '@/components/VideoPlayer'
import { Icon } from '@/components/Icon'
import { BaseButton } from '@/components/Button'
const appStore = useAppStore()
@ -58,8 +63,13 @@ export default defineComponent({
type: Array as PropType<Recordable[]>,
default: () => []
},
//
preview: {
//
imagePreview: {
type: Array as PropType<string[]>,
default: () => []
},
//
videoPreview: {
type: Array as PropType<string[]>,
default: () => []
},
@ -189,7 +199,25 @@ export default defineComponent({
default: 'fixed'
},
scrollbarAlwaysOn: propTypes.bool.def(false),
flexible: propTypes.bool.def(false)
flexible: propTypes.bool.def(false),
//
customContent: propTypes.bool.def(false),
cardBodyStyle: {
type: Object as PropType<CSSProperties>,
default: () => ({})
},
cardBodyClass: {
type: String as PropType<string>,
default: ''
},
cardWrapStyle: {
type: Object as PropType<CSSProperties>,
default: () => ({})
},
cardWrapClass: {
type: String as PropType<string>,
default: ''
}
},
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
setup(props, { attrs, emit, slots, expose }) {
@ -236,7 +264,7 @@ export default defineComponent({
const addColumn = (column: TableColumn, index?: number) => {
const { columns } = unref(getProps)
if (index) {
if (index !== void 0) {
columns.splice(index, 0, column)
} else {
columns.push(column)
@ -325,7 +353,8 @@ export default defineComponent({
})
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
unref(getProps)
return columnsChildren.map((v) => {
if (v.show === false) return null
const props = { ...v } as any
@ -336,20 +365,20 @@ export default defineComponent({
const slots = {
default: (...args: any[]) => {
const data = args[0]
let isImageUrl = false
if (preview.length) {
isImageUrl = preview.some((item) => (item as string) === v.field)
}
let isPreview = false
isPreview =
imagePreview.some((item) => (item as string) === v.field) ||
videoPreview.some((item) => (item as string) === v.field)
return children && children.length
? renderTreeTableColumn(children)
: props?.slots?.default
? props.slots.default(...args)
: v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isImageUrl
? renderPreview(get(data.row, v.field))
: get(data.row, v.field)
? props.slots.default(...args)
: v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isPreview
? renderPreview(get(data.row, v.field), v.field)
: get(data.row, v.field)
}
}
if (props?.slots?.header) {
@ -370,17 +399,32 @@ export default defineComponent({
})
}
const renderPreview = (url: string) => {
const renderPreview = (url: string, field: string) => {
const { imagePreview, videoPreview } = unref(getProps)
return (
<div class="flex items-center">
<ElImage
src={url}
fit="cover"
class="w-[100%] h-100px"
lazy
preview-src-list={[url]}
preview-teleported
/>
{imagePreview.includes(field) ? (
<ElImage
src={url}
fit="cover"
class="w-[100%]"
lazy
preview-src-list={[url]}
preview-teleported
/>
) : videoPreview.includes(field) ? (
<BaseButton
type="primary"
icon={<Icon icon="ep:video-play" />}
onClick={() => {
createVideoViewer({
url
})
}}
>
预览
</BaseButton>
) : null}
</div>
)
}
@ -395,7 +439,8 @@ export default defineComponent({
headerAlign,
showOverflowTooltip,
reserveSelection,
preview
imagePreview,
videoPreview
} = unref(getProps)
return (columnsChildren || columns).map((v) => {
@ -421,6 +466,7 @@ export default defineComponent({
reserveSelection={reserveSelection}
align="center"
headerAlign="center"
selectable={v.selectable}
width="50px"
fixed="left"
></ElTableColumn>
@ -435,20 +481,20 @@ export default defineComponent({
default: (...args: any[]) => {
const data = args[0]
let isImageUrl = false
if (preview.length) {
isImageUrl = preview.some((item) => (item as string) === v.field)
}
let isPreview = false
isPreview =
imagePreview.some((item) => (item as string) === v.field) ||
videoPreview.some((item) => (item as string) === v.field)
return children && children.length
? renderTreeTableColumn(children)
: props?.slots?.default
? props.slots.default(...args)
: v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isImageUrl
? renderPreview(get(data.row, v.field))
: get(data.row, v.field)
? props.slots.default(...args)
: v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isPreview
? renderPreview(get(data.row, v.field), v.field)
: get(data.row, v.field)
}
}
if (props?.slots?.header) {
@ -482,36 +528,77 @@ export default defineComponent({
return (
<div v-loading={unref(getProps).loading}>
<div class="flex justify-between mb-1">
<div>{toolbar}</div>
<div class="pt-2">
{unref(getProps).showAction ? (
<TableActions
activeUID={unref(getProps).activeUID}
columns={unref(getProps).columns}
el-table-ref={elTableRef}
onChangSize={changSize}
onRefresh={refresh}
/>
) : null}
{unref(getProps).customContent ? (
<div class="flex flex-wrap">
{unref(getProps)?.data?.length ? (
unref(getProps)?.data.map((item) => {
const cardSlots = {
default: () => {
return getSlot(slots, 'content', item)
}
}
if (getSlot(slots, 'content-header')) {
cardSlots['header'] = () => {
return getSlot(slots, 'content-header', item)
}
}
if (getSlot(slots, 'content-footer')) {
cardSlots['footer'] = () => {
return getSlot(slots, 'content-footer', item)
}
}
return (
<ElCard
shadow="hover"
class={unref(getProps).cardWrapClass}
style={unref(getProps).cardWrapStyle}
bodyClass={unref(getProps).cardBodyClass}
bodyStyle={unref(getProps).cardBodyStyle}
>
{cardSlots}
</ElCard>
)
})
) : (
<div class="flex flex-1 justify-center">
<ElEmpty description="暂无数据" />
</div>
)}
</div>
</div>
) : (
<>
<div class="flex justify-between mb-1">
<div>{toolbar}</div>
<div class="pt-2">
{unref(getProps).showAction ? (
<TableActions
activeUID={unref(getProps).activeUID}
columns={unref(getProps).columns}
el-table-ref={elTableRef}
onChangSize={changSize}
onRefresh={refresh}
/>
) : null}
</div>
</div>
<ElTable
ref={elTableRef}
data={unref(getProps).data}
{...unref(getBindValue)}
header-cell-style={
appStore.getIsDark
? { color: '#CFD3DC', 'background-color': '#000' }
: { color: '#000', 'background-color': '#f5f7fa' }
}
>
{{
default: () => renderTableColumn(),
...tableSlots
}}
</ElTable>
</>
)}
<ElTable
ref={elTableRef}
data={unref(getProps).data}
{...unref(getBindValue)}
header-cell-style={
appStore.getIsDark
? { color: '#CFD3DC', 'background-color': '#000' }
: { color: '#000', 'background-color': '#f5f7fa' }
}
>
{{
default: () => renderTableColumn(),
...tableSlots
}}
</ElTable>
{unref(getProps).pagination ? (
<ElPagination
v-model:pageSize={pageSizeRef.value}

View File

@ -9,7 +9,6 @@ import {
ElPopover,
ElCheckbox,
ElScrollbar,
ElButton,
ElTable,
ElDivider
} from 'element-plus'
@ -23,6 +22,7 @@ import { useStorage } from '@/hooks/web/useStorage'
import cloneDeep from 'lodash/cloneDeep'
import { propTypes } from '@/utils/propTypes'
import { moveElementToIndex } from '@/utils/index'
import { BaseButton } from '@/components/Button'
const appStore = useAppStore()
const sizeMap = computed(() => appStore.sizeMap)
@ -263,9 +263,9 @@ export default defineComponent({
{t('common.SerialNumberColumn')}
</ElCheckbox>
</div>
<ElButton type="primary" link onClick={resetTableColumns}>
<BaseButton type="primary" link onClick={resetTableColumns}>
{t('common.reset')}
</ElButton>
</BaseButton>
</div>
<ElScrollbar max-height="400px">
<VueDraggable

View File

@ -9,6 +9,8 @@ const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('theme-switch')
const emit = defineEmits(['change'])
const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
const CrescentMoon = useIcon({ icon: 'emojione-monotone:crescent-moon', color: '#fde047' })
@ -23,6 +25,7 @@ const blackColor = 'var(--el-color-black)'
const themeChange = (val: boolean) => {
appStore.setIsDark(val)
emit('change', val)
}
</script>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { useAuthStore } from '@/store/modules/auth'
import { useDesign } from '@/hooks/web/useDesign'
import LockDialog from './components/LockDialog.vue'
import { ref, computed } from 'vue'
@ -14,7 +14,7 @@ const lockStore = useLockStore()
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
const authStore = useAuthStoreWithOut()
const authStore = useAuthStore()
const { getPrefixCls } = useDesign()
@ -73,13 +73,13 @@ const user = computed(() => authStore.getUser)
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem>
<ElButton @click="toHome" link>个人主页</ElButton>
<BaseButton @click="toHome" link>个人主页</BaseButton>
</ElDropdownItem>
<ElDropdownItem>
<ElButton @click="toGitee" link>Gitee</ElButton>
<BaseButton @click="toGitee" link>Gitee</BaseButton>
</ElDropdownItem>
<ElDropdownItem>
<ElButton @click="toGithub" link>Github</ElButton>
<BaseButton @click="toGithub" link>Github</BaseButton>
</ElDropdownItem>
<ElDropdownItem divided>
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>

View File

@ -7,7 +7,6 @@ import { useForm } from '@/hooks/web/useForm'
import { reactive, computed } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { FormSchema } from '@/components/Form'
import { ElButton } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
import { useLockStore } from '@/store/modules/lock'
@ -87,14 +86,14 @@ const handleLock = async () => {
</div>
<Form :is-col="false" :schema="schema" :rules="rules" @register="formRegister" />
<template #footer>
<ElButton type="primary" @click="handleLock">{{ t('lock.lock') }}</ElButton>
<BaseButton type="primary" @click="handleLock">{{ t('lock.lock') }}</BaseButton>
</template>
</Dialog>
</template>
<style lang="less" scoped>
:global(.v-lock-dialog) {
@media (max-width: 767px) {
@media (width <= 767px) {
max-width: calc(100vw - 16px);
}
}

View File

@ -1,14 +1,14 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { ElInput, ElButton } from 'element-plus'
import { ElInput } from 'element-plus'
import { useLockStore } from '@/store/modules/lock'
import { useI18n } from '@/hooks/web/useI18n'
import { useNow } from '@/hooks/web/useNow'
import { useDesign } from '@/hooks/web/useDesign'
import { Icon } from '@/components/Icon'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { useAuthStore } from '@/store/modules/auth'
const authStore = useAuthStoreWithOut()
const authStore = useAuthStore()
const password = ref('')
const loading = ref(false)
@ -92,7 +92,7 @@ function handleShowForm(show = false) {
{{ t('lock.message') }}
</span>
<div :class="`${prefixCls}-entry__footer enter-x`">
<ElButton
<BaseButton
type="primary"
size="small"
class="mt-2 mr-2 enter-x"
@ -101,8 +101,8 @@ function handleShowForm(show = false) {
@click="handleShowForm(true)"
>
{{ t('common.back') }}
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="primary"
size="small"
class="mt-2 mr-2 enter-x"
@ -111,8 +111,8 @@ function handleShowForm(show = false) {
@click="goLogin"
>
{{ t('lock.backToLogin') }}
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="primary"
class="mt-2"
size="small"
@ -121,7 +121,7 @@ function handleShowForm(show = false) {
:disabled="loading"
>
{{ t('lock.entrySystem') }}
</ElButton>
</BaseButton>
</div>
</div>
</div>
@ -190,6 +190,7 @@ function handleShowForm(show = false) {
font-size: 90px;
}
}
@media screen and (min-width: @screen-lg) {
span:not(.meridiem) {
font-size: 220px;
@ -201,6 +202,7 @@ function handleShowForm(show = false) {
font-size: 260px;
}
}
@media screen and (min-width: @screen-2xl) {
span:not(.meridiem) {
font-size: 320px;

View File

@ -0,0 +1,27 @@
import { VNode, createVNode, render } from 'vue'
import VideoPlayer from './src/VideoPlayer.vue'
import { isClient } from '@/utils/is'
import { VideoPlayerViewer } from '@/components/VideoPlayerViewer'
import { toAnyString } from '@/utils'
export { VideoPlayer }
let instance: Nullable<VNode> = null
export function createVideoViewer(options: { url: string; poster?: string; show?: boolean }) {
if (!isClient) return
const { url, poster } = options
const propsData: Partial<{ url: string; poster?: string; show?: boolean; id?: string }> = {}
const container = document.createElement('div')
const id = toAnyString()
container.id = id
propsData.url = url
propsData.poster = poster
propsData.show = true
propsData.id = id
document.body.appendChild(container)
instance = createVNode(VideoPlayerViewer, propsData)
render(instance, container)
}

View File

@ -0,0 +1,51 @@
<script setup lang="ts">
import Player from 'xgplayer'
import { ref, unref, onMounted, watch, onBeforeUnmount, nextTick } from 'vue'
import 'xgplayer/dist/index.min.css'
const props = defineProps({
url: {
type: String,
default: '',
required: true
},
poster: {
type: String,
default: ''
}
})
const playerRef = ref<Player>()
const videoEl = ref<HTMLDivElement>()
const intiPlayer = () => {
if (!unref(videoEl)) return
new Player({
autoplay: false,
...props,
el: unref(videoEl)
})
}
onMounted(() => {
intiPlayer()
})
watch(
() => props,
async (newProps) => {
await nextTick()
if (newProps) {
unref(playerRef)?.setConfig(newProps)
}
},
{
deep: true
}
)
onBeforeUnmount(() => {
unref(playerRef)?.destroy()
})
defineExpose({
playerExpose: () => unref(playerRef)
})
</script>
<template>
<div ref="videoEl"></div>
</template>

View File

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

View File

@ -0,0 +1,46 @@
<script setup lang="ts">
import { VideoPlayer } from '@/components/VideoPlayer'
import { ElOverlay } from 'element-plus'
import { ref, nextTick } from 'vue'
import { Icon } from '@/components/Icon'
const props = defineProps({
show: {
type: Boolean,
default: false
},
url: {
type: String,
default: '',
required: true
},
poster: {
type: String,
default: ''
},
id: {
type: String,
default: ''
}
})
const visible = ref(props.show)
const close = async () => {
visible.value = false
await nextTick()
const wrap = document.getElementById(props.id)
if (!wrap) return
document.body.removeChild(wrap)
}
</script>
<template>
<ElOverlay v-show="visible" @click="close">
<div class="w-full h-full flex justify-center items-center relative" @click="close">
<div
class="w-44px h-44px color-[#fff] bg-[var(--el-text-color-regular)] rounded-full border-[#fff] flex justify-center items-center cursor-pointer absolute top-40px right-40px"
@click="close"
>
<Icon icon="ep:close" :size="24" />
</div>
<VideoPlayer :url="url" :poster="poster" />
</div>
</ElOverlay>
</template>

View File

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

View File

@ -0,0 +1,234 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { ref, nextTick, unref, onMounted, watch } from 'vue'
import { useEventListener, useIntersectionObserver } from '@vueuse/core'
import { debounce } from 'lodash-es'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('waterfall')
const emit = defineEmits(['loadMore'])
const prop = defineProps({
data: propTypes.arrayOf(propTypes.any),
reset: propTypes.bool.def(true),
width: propTypes.number.def(200),
gap: propTypes.number.def(20),
props: propTypes.objectOf(propTypes.string).def({
src: 'src',
height: 'height'
}),
cols: propTypes.number.def(undefined),
loadingText: propTypes.string.def('加载中...'),
loading: propTypes.bool.def(false),
end: propTypes.bool.def(false),
endText: propTypes.string.def('没有更多了'),
autoCenter: propTypes.bool.def(true),
layout: propTypes.oneOf(['javascript', 'flex']).def('flex')
})
const wrapEl = ref<HTMLDivElement>()
const heights = ref<number[]>([])
const wrapHeight = ref(0)
const wrapWidth = ref(0)
const loadMore = ref<HTMLDivElement>()
// = /
const innerCols = ref(0)
const filterData = ref<any[]>([])
const filterWaterfall = async () => {
filterData.value = []
const { props, width, gap } = prop
const data = prop.data as any[]
await nextTick()
const container = unref(wrapEl) as HTMLElement
if (!container) return
innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
const length = data.length
for (let i = 0; i < length; i++) {
if (i < unref(innerCols)) {
heights.value[i] = data[i][props.height as string]
filterData.value.push({
...data[i],
top: 0,
left: i * (width + gap)
})
} else {
//
//
let minHeight = heights.value[0]
let index = 0
//
for (let j = 1; j < unref(innerCols); j++) {
if (unref(heights)[j] < minHeight) {
minHeight = unref(heights)[j]
index = j
}
}
//
heights.value[index] += data[i][props.height as string] + gap
filterData.value.push({
...data[i],
top: minHeight + gap,
left: index * (width + gap)
})
}
}
wrapHeight.value = Math.max(...unref(heights))
wrapWidth.value = unref(innerCols) * (width + gap) - gap
}
const flexWaterfall = async () => {
const { width, gap } = prop
const data = prop.data as any[]
await nextTick()
const container = unref(wrapEl) as HTMLElement
if (!container) return
innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
const length = data.length
//
const arr = new Array(unref(innerCols)).fill([])
// dataarr
for (let i = 0; i < length; i++) {
const index = i % unref(innerCols)
arr[index] = [...arr[index], data[i]]
}
filterData.value = arr
}
const initLayout = () => {
const { layout } = prop
if (layout === 'javascript') {
filterWaterfall()
} else if (layout === 'flex') {
flexWaterfall()
}
}
watch(
() => [prop.data, prop.cols],
() => {
initLayout()
},
{
immediate: true
}
)
onMounted(() => {
if (unref(prop.reset)) {
useEventListener(window, 'resize', debounce(initLayout, 300))
}
useIntersectionObserver(
unref(loadMore),
([{ isIntersecting }]) => {
if (isIntersecting && !prop.loading && !prop.end) {
emit('loadMore')
}
},
{
threshold: 0.1
}
)
})
</script>
<template>
<div
:class="[
prefixCls,
'flex',
'items-center',
{
'justify-center': autoCenter
}
]"
ref="wrapEl"
:style="{
height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
}"
>
<template v-if="layout === 'javascript'">
<div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
<div
v-for="(item, $index) in filterData"
:class="[
`${prefixCls}-item__${$index}`,
{
absolute: layout === 'javascript'
}
]"
:key="`water-${$index}`"
:style="{
width: `${width}px`,
height: `${item[props.height as string]}px`,
top: `${item.top}px`,
left: `${item.left}px`
}"
>
<img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
</div>
<div
ref="loadMore"
class="h-40px flex justify-center absolute w-full"
:style="{
top: `${wrapHeight + gap}px`
}"
>
{{ end ? endText : loadingText }}
</div>
</div>
</template>
<template v-else-if="layout === 'flex'">
<div
class="relative flex pb-40px"
:style="{
width: cols ? '100%' : 'auto'
}"
>
<div
v-for="(item, $index) in filterData"
:key="`waterWrap-${$index}`"
class="flex-1"
:style="{
marginRight: $index === filterData.length - 1 ? '0' : `${gap}px`
}"
>
<div
v-for="(child, i) in item"
:key="`waterWrap-${$index}-${i}`"
:style="{
marginBottom: `${gap}px`,
width: cols ? '100%' : `${width}px`,
height: cols ? 'auto' : `${child[props.height as string]}px`
}"
>
<img :src="child[props.src as string]" class="w-full h-full block" alt="" srcset="" />
</div>
</div>
<div
ref="loadMore"
class="h-40px flex justify-center absolute w-full items-center"
:style="{
bottom: 0
}"
>
{{ end ? endText : loadingText }}
</div>
</div>
</template>
</div>
</template>

View File

@ -1,8 +1,10 @@
import type { App } from 'vue'
import { Icon } from './Icon'
import { Permission } from './Permission'
import { BaseButton } from './Button'
export const setupGlobCom = (app: App<Element>): void => {
app.component('Icon', Icon)
app.component('Permission', Permission)
app.component('BaseButton', BaseButton)
}

View File

@ -1,7 +1,5 @@
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { useStorage } from '@/hooks/web/useStorage'
import { useAppStore } from '@/store/modules/app'
import { useAuthStore } from '@/store/modules/auth'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import qs from 'qs'
import { config } from './config'
import { ElMessage } from 'element-plus'
@ -9,8 +7,6 @@ import request from '@/config/axios'
const { result_code, unauthorized_code, request_timeout } = config
const { getStorage, setStorage } = useStorage()
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: '/api', // api 的 base_url
@ -21,10 +17,10 @@ const service: AxiosInstance = axios.create({
// request拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const appStore = useAppStore()
const token = getStorage(appStore.getToken)
const authStore = useAuthStoreWithOut()
const token = authStore.getToken
if (token !== '') {
;(config.headers as any)['Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
;(config.headers as any)[authStore.getTokenKey ?? 'Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
}
if (
config.method === 'post' &&
@ -87,18 +83,18 @@ service.interceptors.response.use(
if (refresh === '1') {
// 因token快过期刷新token
refreshToken().then((res) => {
const appStore = useAppStore()
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
setStorage(appStore.getRefreshToken, res.data.refresh_token)
const authStore = useAuthStoreWithOut()
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
authStore.setRefreshToken(res.data.refresh_token)
})
}
return response.data
} else if (code === unauthorized_code) {
// 因token无效token过期导致
refreshToken().then((res) => {
const appStore = useAppStore()
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
setStorage(appStore.getRefreshToken, res.data.refresh_token)
const authStore = useAuthStoreWithOut()
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
authStore.setRefreshToken(res.data.refresh_token)
ElMessage.error('操作失败,请重试')
})
} else {
@ -108,7 +104,7 @@ service.interceptors.response.use(
(error: AxiosError) => {
console.log('err', error)
let { message } = error
const authStore = useAuthStore()
const authStore = useAuthStoreWithOut()
const status = error.response?.status
switch (status) {
case 400:
@ -158,8 +154,8 @@ service.interceptors.response.use(
// 刷新Token
const refreshToken = (): Promise<IResponse> => {
const appStore = useAppStore()
const data = getStorage(appStore.getRefreshToken)
const authStore = useAuthStoreWithOut()
const data = authStore.getRefreshToken
return request.post({ url: '/auth/token/refresh', data })
}

View File

@ -5,11 +5,11 @@ import { isArray } from '@/utils/is'
import { useAuthStoreWithOut } from '@/store/modules/auth'
const { t } = useI18n()
const authStore = useAuthStoreWithOut()
// 全部权限
const all_permission = ['*.*.*']
const hasPermission = (value: string | string[]): boolean => {
const authStore = useAuthStoreWithOut()
const permissions = authStore.getPermissions
if (!value) {
throw new Error(t('permission.hasPermission'))

View File

@ -0,0 +1,47 @@
import { ref } from 'vue'
const useClipboard = () => {
const copied = ref(false)
const text = ref('')
const isSupported = ref(false)
if (!navigator.clipboard && !document.execCommand) {
isSupported.value = false
} else {
isSupported.value = true
}
const copy = (str: string) => {
if (navigator.clipboard) {
navigator.clipboard.writeText(str).then(() => {
text.value = str
copied.value = true
resetCopied()
})
return
}
const input = document.createElement('input')
input.setAttribute('readonly', 'readonly')
input.setAttribute('value', str)
document.body.appendChild(input)
input.select()
input.setSelectionRange(0, 9999)
if (document.execCommand('copy')) {
text.value = str
document.execCommand('copy')
copied.value = true
resetCopied()
}
document.body.removeChild(input)
}
const resetCopied = () => {
setTimeout(() => {
copied.value = false
}, 1500)
}
return { copy, text, copied, isSupported }
}
export { useClipboard }

View File

@ -79,19 +79,14 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
for (let i = 0; i < length; i++) {
const schemaItem = crudSchema[i]
// 判断是否隐藏
if (!schemaItem?.search?.hidden) {
const searchSchemaItem = {
component: schemaItem?.search?.component || 'Input',
...schemaItem.search,
field: schemaItem.field,
label: schemaItem.search?.label || schemaItem.label
}
// 删除不必要的字段
delete searchSchemaItem.hidden
searchSchema.push(searchSchemaItem)
const searchSchemaItem = {
component: schemaItem?.search?.component || 'Input',
...schemaItem.search,
field: schemaItem.field,
label: schemaItem.search?.label || schemaItem.label
}
searchSchema.push(searchSchemaItem)
}
return searchSchema
@ -127,19 +122,14 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
for (let i = 0; i < length; i++) {
const formItem = crudSchema[i]
// 判断是否隐藏
if (!formItem?.form?.hidden) {
const formSchemaItem = {
component: formItem?.form?.component || 'Input',
...formItem.form,
field: formItem.field,
label: formItem.form?.label || formItem.label
}
// 删除不必要的字段
delete formSchemaItem.hidden
formSchema.push(formSchemaItem)
const formSchemaItem = {
component: formItem?.form?.component || 'Input',
...formItem.form,
field: formItem.field,
label: formItem.form?.label || formItem.label
}
formSchema.push(formSchemaItem)
}
return formSchema

View File

@ -2,6 +2,7 @@ import type { Form, FormExpose } from '@/components/Form'
import type { ElForm, ElFormItem } from 'element-plus'
import { ref, unref, nextTick } from 'vue'
import { FormSchema, FormSetProps, FormProps } from '@/components/Form'
import { isEmptyVal, isObject } from '@/utils/is'
export const useForm = () => {
// From实例
@ -93,9 +94,27 @@ export const useForm = () => {
* @description
* @returns form data
*/
getFormData: async <T = Recordable>(): Promise<T> => {
getFormData: async <T = Recordable>(filterEmptyVal = true): Promise<T> => {
const form = await getForm()
return form?.formModel as T
const model = form?.formModel as any
if (filterEmptyVal) {
// 使用reduce过滤空值并返回一个新对象
return Object.keys(model).reduce((prev, next) => {
const value = model[next]
if (!isEmptyVal(value)) {
if (isObject(value)) {
if (Object.keys(value).length > 0) {
prev[next] = value
}
} else {
prev[next] = value
}
}
return prev
}, {}) as T
} else {
return model as T
}
},
/**

View File

@ -0,0 +1,21 @@
import { ref, onBeforeUnmount } from 'vue'
const useNetwork = () => {
const online = ref(true)
const updateNetwork = () => {
online.value = navigator.onLine
}
window.addEventListener('online', updateNetwork)
window.addEventListener('offline', updateNetwork)
onBeforeUnmount(() => {
window.removeEventListener('online', updateNetwork)
window.removeEventListener('offline', updateNetwork)
})
return { online }
}
export { useNetwork }

View File

@ -1,13 +1,15 @@
import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut()
export const usePageLoading = () => {
const loadStart = () => {
const appStore = useAppStoreWithOut()
appStore.setPageLoading(true)
}
const loadDone = () => {
const appStore = useAppStoreWithOut()
appStore.setPageLoading(false)
}

View File

@ -4,7 +4,7 @@ const getValueType = (value: any) => {
return type.slice(8, -1)
}
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionStorage') => {
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'localStorage') => {
const setStorage = (key: string, value: any) => {
const valueType = getValueType(value)
window[type].setItem(key, JSON.stringify({ type: valueType, value }))

View File

@ -3,10 +3,10 @@ import { isString } from '@/utils/is'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useI18n } from '@/hooks/web/useI18n'
const appStore = useAppStoreWithOut()
export const useTitle = (newTitle?: string) => {
const { t } = useI18n()
const appStore = useAppStoreWithOut()
const title = ref(
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
)

View File

@ -71,8 +71,14 @@ export default defineComponent({
.@{prefix-cls} {
background-color: var(--app-content-bg-color);
:deep(.@{elNamespace}-scrollbar__view) {
height: 100% !important;
.@{prefix-cls}-content-scrollbar {
& > :deep(.el-scrollbar__wrap) {
& > .@{elNamespace}-scrollbar__view {
display: flex;
height: 100% !important;
flex-direction: column;
}
}
}
}
</style>

View File

@ -6,10 +6,6 @@ import { computed } from 'vue'
const appStore = useAppStore()
const layout = computed(() => appStore.getLayout)
const fixedHeader = computed(() => appStore.getFixedHeader)
const footer = computed(() => appStore.getFooter)
const tagsViewStore = useTagsViewStore()
@ -17,39 +13,12 @@ const tagsViewStore = useTagsViewStore()
const getCaches = computed((): string[] => {
return tagsViewStore.getCachedViews
})
const tagsView = computed(() => appStore.getTagsView)
</script>
<template>
<section
:class="[
'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color-new)] dark:bg-[var(--el-bg-color)]',
{
'!min-h-[calc(100%-var(--app-footer-height))]':
(fixedHeader &&
(layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
footer) ||
(!tagsView && layout === 'top' && footer),
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]':
tagsView && layout === 'top' && footer,
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]':
!fixedHeader && layout === 'classic' && footer,
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
!fixedHeader && layout === 'topLeft' && footer,
// '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height)-var(--top-tool-height))]':
// !fixedHeader && layout === 'top' && footer,
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]':
fixedHeader && layout === 'cutMenu' && footer,
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]':
!fixedHeader && layout === 'cutMenu' && footer
}
'flex-1 p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]'
]"
>
<router-view>

View File

@ -42,8 +42,7 @@ export default defineComponent({
id={`${variables.namespace}-tool-header`}
class={[
prefixCls,
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between',
'dark:bg-[var(--el-bg-color)]'
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
]}
>
{layout.value !== 'top' ? (

View File

@ -195,8 +195,8 @@ export const useRenderLayout = () => {
`${prefixCls}-content`,
'w-full',
{
'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,
'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
'h-[calc(100%-var(--top-tool-height))]': !fixedHeader.value,
'h-[calc(100%-var(--tags-view-height)-var(--top-tool-height))]': fixedHeader.value
}
]}
>

View File

@ -50,6 +50,8 @@ export default {
notSpace: 'Spaces are not allowed',
notSpecialCharacters: 'Special characters are not allowed',
isEqual: 'The two are not equal',
// 列设置
setting: 'Setting',
selectAll: 'Select all',
SerialNumberColumn: 'Index column'
},
@ -187,7 +189,14 @@ export default {
function: 'Function',
multipleTabs: 'Multiple tabs',
details: 'Details',
iconPicker: 'Icon picker'
iconPicker: 'Icon picker',
request: 'Request',
waterfall: 'Waterfall',
imageCropping: 'Image cropping',
videoPlayer: 'Video player',
// 表格视频预览
tableVideoPreview: 'Table video preview',
cardTable: 'Card table'
},
permission: {
hasPermission: 'Please set the operation permission value'
@ -333,7 +342,8 @@ export default {
lazyLoad: 'Lazy load',
upload: 'Upload',
// 用户头像
userAvatar: 'User avatar'
userAvatar: 'User avatar',
iconPicker: 'Icon picker'
},
guideDemo: {
guide: 'Guide',
@ -462,7 +472,9 @@ export default {
fixedHeaderOrAuto: 'Fixed header or auto',
getSelections: 'Get selections',
preview: 'Preview',
showOrHiddenSortable: 'Show or hidden sortable'
showOrHiddenSortable: 'Show or hidden sortable',
videoPreview: 'Video preview',
cardTable: 'Card table'
},
richText: {
richText: 'Rich text',

View File

@ -50,6 +50,7 @@ export default {
notSpace: '不能包含空格',
notSpecialCharacters: '不能包含特殊字符',
isEqual: '两次输入不一致',
setting: '设置',
selectAll: '全选',
SerialNumberColumn: '序号列'
},
@ -84,7 +85,7 @@ export default {
sizeIcon: '尺寸图标',
localeIcon: '多语言图标',
tagsView: '标签页',
logo: '标志',
logo: 'Logo',
greyMode: '灰色模式',
fixedHeader: '固定头部',
headerTheme: '头部主题',
@ -185,7 +186,13 @@ export default {
function: '功能',
multipleTabs: '多开标签页',
details: '详情页',
iconPicker: '图标选择器'
iconPicker: '图标选择器',
request: '请求',
waterfall: '瀑布流',
imageCropping: '图片裁剪',
videoPlayer: '视频播放器',
tableVideoPreview: '表格视频预览',
cardTable: '卡片表格'
},
permission: {
hasPermission: '请设置操作权限值'
@ -328,7 +335,8 @@ export default {
customContent: '自定义内容',
lazyLoad: '懒加载',
upload: '上传',
userAvatar: '用户头像'
userAvatar: '用户头像',
iconPicker: '图标选择器'
},
guideDemo: {
guide: '引导页',
@ -455,7 +463,9 @@ export default {
fixedHeaderOrAuto: '固定头部/自动',
getSelections: '获取多选数据',
preview: '封面',
showOrHiddenSortable: '显示/隐藏排序'
showOrHiddenSortable: '显示/隐藏排序',
videoPreview: '视频预览',
cardTable: '卡片表格'
},
richText: {
richText: '富文本',
@ -531,7 +541,7 @@ export default {
menu: {
menuName: '菜单名称',
icon: '图标',
permission: '权限标识',
permission: '按钮权限',
component: '组件',
path: '路径',
status: '状态',

View File

@ -1,3 +1,5 @@
import 'vue/jsx'
// 引入windi css
import '@/plugins/unocss'

View File

@ -1,6 +1,4 @@
import router from './router'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useStorage } from '@/hooks/web/useStorage'
import type { RouteRecordRaw } from 'vue-router'
import { useTitle } from '@/hooks/web/useTitle'
import { useNProgress } from '@/hooks/web/useNProgress'
@ -9,13 +7,6 @@ import { usePageLoading } from '@/hooks/web/usePageLoading'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { getRoleMenusApi } from '@/api/login'
const permissionStore = usePermissionStoreWithOut()
const appStore = useAppStoreWithOut()
const authStore = useAuthStoreWithOut()
const { getStorage, setStorage } = useStorage()
const { start, done } = useNProgress()
const { loadStart, loadDone } = usePageLoading()
@ -25,7 +16,9 @@ const whiteList = ['/login'] // 不重定向白名单
router.beforeEach(async (to, from, next) => {
start()
loadStart()
if (getStorage(appStore.getToken)) {
const permissionStore = usePermissionStoreWithOut()
const authStore = useAuthStoreWithOut()
if (authStore.getToken) {
if (to.path === '/login') {
next({ path: '/' })
} else if (to.path === '/reset/password') {
@ -42,7 +35,6 @@ router.beforeEach(async (to, from, next) => {
// 开发者可根据实际情况进行修改
const res = await getRoleMenusApi()
const routers = res.data || []
setStorage('roleRouters', routers)
await permissionStore.generateRoutes(routers).catch(() => {})
permissionStore.getAddRouters.forEach((route) => {
router.addRoute(route as RouteRecordRaw) // 动态添加可访问路由表

View File

@ -12,7 +12,13 @@ export const setupElementPlus = (app: App<Element>) => {
app.use(plugin)
})
// 为了开发环境启动更快,一次性引入所有样式
if (import.meta.env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'true') {
import('element-plus/dist/index.css')
return
}
components.forEach((component) => {
app.component(component.name, component)
app.component(component.name!, component)
})
}

View File

@ -1,3 +1 @@
import 'virtual:svg-icons-register'
import '@purge-icons/generated'

View File

@ -115,7 +115,7 @@ const router = createRouter({
})
export const resetRouter = (): void => {
const resetWhiteNameList = ['Login', 'NoFind', 'Root']
const resetWhiteNameList = ['Login', 'NoFind', 'Root', 'ResetPassword', 'Redirect']
router.getRoutes().forEach((route) => {
const { name } = route
if (name && !resetWhiteNameList.includes(name as string)) {

View File

@ -1,10 +1,12 @@
import type { App } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
// pinia-plugin-persistedstate 持久化存储官方文档https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/
const store = createPinia()
store.use(piniaPersist)
store.use(piniaPluginPersistedstate)
export const setupStore = (app: App<Element>) => {
app.use(store)

View File

@ -2,9 +2,10 @@ import { defineStore } from 'pinia'
import { store } from '../index'
import { setCssVar, humpToUnderline } from '@/utils'
import { ElMessage, ComponentSize } from 'element-plus'
import { useStorage } from '@/hooks/web/useStorage'
const { getStorage, setStorage } = useStorage()
import { colorIsDark, hexToRGB, lighten, mix } from '@/utils/color'
import { useCssVar } from '@vueuse/core'
import { unref } from 'vue'
import { useDark } from '@vueuse/core'
interface AppState {
breadcrumb: boolean
@ -25,7 +26,6 @@ interface AppState {
pageLoading: boolean
layout: LayoutType
title: string
userInfo: string
isDark: boolean
currentSize: ComponentSize
sizeMap: ComponentSize[]
@ -34,8 +34,6 @@ interface AppState {
theme: ThemeTypes
fixedMenu: boolean
token: string
refreshToken: string
logoImage: string
footerContent: string
icpNumber: string
@ -44,7 +42,6 @@ interface AppState {
export const useAppStore = defineStore('app', {
state: (): AppState => {
return {
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其它项目冲突
sizeMap: ['default', 'large', 'small'],
mobile: false, // 是否是移动端
title: import.meta.env.VITE_APP_TITLE, // 标题
@ -63,14 +60,14 @@ export const useAppStore = defineStore('app', {
fixedHeader: true, // 固定toolheader
footer: true, // 显示页脚
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
dynamicRouter: true, // 是否动态路由
serverDynamicRouter: true, // 是否服务端渲染动态路由
fixedMenu: false, // 是否固定菜单
layout: getStorage('layout') || 'classic', // layout布局
isDark: getStorage('isDark'), // 是否是暗黑模式
currentSize: getStorage('default') || 'default', // 组件尺寸
theme: getStorage('theme') || {
layout: 'classic', // layout布局
isDark: false, // 是否是暗黑模式
currentSize: 'default', // 组件尺寸
theme: {
// 主题色
elColorPrimary: '#409eff',
// 左侧菜单边框颜色
@ -101,8 +98,6 @@ export const useAppStore = defineStore('app', {
topToolBorderColor: '#eee'
},
token: 'Token', // 存储Token字段
refreshToken: 'RefreshToken', // 存储刷新Token字段
logoImage: '', // logo图片
footerContent: '', // 页脚内容
icpNumber: '' // 备案号
@ -166,9 +161,6 @@ export const useAppStore = defineStore('app', {
getTitle(): string {
return this.title
},
getUserInfo(): string {
return this.userInfo
},
getIsDark(): boolean {
return this.isDark
},
@ -191,12 +183,6 @@ export const useAppStore = defineStore('app', {
getLogoImage(): string {
return this.logoImage
},
getToken(): string {
return this.token
},
getRefreshToken(): string {
return this.refreshToken
},
getFooterContent(): string {
return this.footerContent
},
@ -245,15 +231,12 @@ export const useAppStore = defineStore('app', {
this.greyMode = greyMode
},
setDynamicRouter(dynamicRouter: boolean) {
setStorage('dynamicRouter', dynamicRouter)
this.dynamicRouter = dynamicRouter
},
setServerDynamicRouter(serverDynamicRouter: boolean) {
setStorage('serverDynamicRouter', serverDynamicRouter)
this.serverDynamicRouter = serverDynamicRouter
},
setFixedMenu(fixedMenu: boolean) {
setStorage('fixedMenu', fixedMenu)
this.fixedMenu = fixedMenu
},
setPageLoading(pageLoading: boolean) {
@ -265,7 +248,6 @@ export const useAppStore = defineStore('app', {
return
}
this.layout = layout
setStorage('layout', this.layout)
},
setTitle(title: string) {
this.title = title
@ -279,23 +261,22 @@ export const useAppStore = defineStore('app', {
document.documentElement.classList.add('light')
document.documentElement.classList.remove('dark')
}
setStorage('isDark', this.isDark)
this.setPrimaryLight()
},
setCurrentSize(currentSize: ComponentSize) {
this.currentSize = currentSize
setStorage('currentSize', this.currentSize)
},
setMobile(mobile: boolean) {
this.mobile = mobile
},
setTheme(theme: ThemeTypes) {
this.theme = Object.assign(this.theme, theme)
setStorage('theme', this.theme)
},
setCssVarTheme() {
for (const key in this.theme) {
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
}
this.setPrimaryLight()
},
setFooter(footer: boolean) {
this.footer = footer
@ -309,8 +290,75 @@ export const useAppStore = defineStore('app', {
},
setIcpNumber(icpNumber: string) {
this.icpNumber = icpNumber
},
setPrimaryLight() {
if (this.theme.elColorPrimary) {
const elColorPrimary = this.theme.elColorPrimary
const color = this.isDark ? '#000000' : '#ffffff'
const lightList = [3, 5, 7, 8, 9]
lightList.forEach((v) => {
setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
})
setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
}
},
setMenuTheme(color: string) {
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
const isDarkColor = colorIsDark(color)
const theme: Recordable = {
// 左侧菜单边框颜色
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
// 左侧菜单背景颜色
leftMenuBgColor: color,
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单字体颜色
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
// 左侧菜单选中字体颜色
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
// logo字体颜色
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo边框颜色
logoBorderColor: isDarkColor ? color : '#eee'
}
this.setTheme(theme)
this.setCssVarTheme()
},
setHeaderTheme(color: string) {
const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
const topToolBorderColor = isDarkColor ? color : '#eee'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
this.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor,
topToolBorderColor
})
if (this.getLayout === 'top') {
this.setMenuTheme(color)
}
},
initTheme() {
const isDark = useDark({
valueDark: 'dark',
valueLight: 'light'
})
isDark.value = this.getIsDark
}
}
},
persist: true
})
export const useAppStoreWithOut = () => {

View File

@ -2,7 +2,6 @@ import { defineStore } from 'pinia'
import { store } from '../index'
import { UserLoginType } from '@/api/login/types'
import { loginApi } from '@/api/login'
import { useAppStore } from '@/store/modules/app'
import { useStorage } from '@/hooks/web/useStorage'
import { getCurrentAdminUserInfo } from '@/api/vadmin/auth/user'
import { resetRouter } from '@/router'
@ -10,7 +9,7 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
import router from '@/router'
import { ElMessage } from 'element-plus'
const { setStorage, clear } = useStorage()
const { clear } = useStorage()
export interface UserState {
id?: number
@ -32,6 +31,9 @@ export interface AuthState {
isUser: boolean // 是否已经登录并获取到用户信息
roles: string[] // 当前用户角色 role_key 列表
permissions: string[] // 当前用户权限列表
tokenKey: string // 提交认证请求时,设置的 header key
token: string // 认证 token
refreshToken: string // 刷新 token
}
export const useAuthStore = defineStore('auth', {
@ -40,10 +42,22 @@ export const useAuthStore = defineStore('auth', {
user: {},
roles: [],
permissions: [],
isUser: false
isUser: false,
tokenKey: 'Authorization',
token: '',
refreshToken: ''
}
},
getters: {
getTokenKey(): string {
return this.tokenKey
},
getToken(): string {
return this.token
},
getRefreshToken(): string {
return this.refreshToken
},
getUser(): UserState {
return this.user
},
@ -58,24 +72,34 @@ export const useAuthStore = defineStore('auth', {
}
},
actions: {
setToken(token: string) {
this.token = token
},
setRefreshToken(refreshToken: string) {
this.refreshToken = refreshToken
},
async login(formData: UserLoginType) {
formData.platform = '0'
const res = await loginApi(formData)
if (res) {
const appStore = useAppStore()
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
setStorage(appStore.getRefreshToken, res.data.refresh_token)
this.token = `${res.data.token_type} ${res.data.access_token}`
this.refreshToken = res.data.refresh_token
// 获取当前登录用户的信息
await this.setUserInfo()
}
return res
},
logout(message?: string) {
clear()
reset() {
this.user = {}
this.roles = []
this.permissions = []
this.isUser = false
this.token = ''
this.refreshToken = ''
},
logout(message?: string) {
clear()
this.reset()
const tagsViewStore = useTagsViewStore()
tagsViewStore.delAllViews()
resetRouter()
@ -105,7 +129,8 @@ export const useAuthStore = defineStore('auth', {
})
this.permissions = res.data.permissions
}
}
},
persist: true
})
export const useAuthStoreWithOut = () => {

View File

@ -10,7 +10,11 @@ export const useDictStore = defineStore('dict', {
state: (): DictState => ({
dictObj: {}
}),
getters: {},
getters: {
getDictObjData(): Recordable {
return this.dictObj
}
},
actions: {
async getDictObj(dictTypes: string[]) {
const result: Recordable = {}
@ -34,7 +38,8 @@ export const useDictStore = defineStore('dict', {
}
return result
}
}
},
persist: true
})
export const useDictStoreWithOut = () => {

View File

@ -5,7 +5,7 @@ import en from 'element-plus/es/locale/lang/en'
import { useStorage } from '@/hooks/web/useStorage'
import { LocaleDropdownType } from '@/components/LocaleDropdown'
const { getStorage, setStorage } = useStorage()
const { getStorage, setStorage } = useStorage('localStorage')
const elLocaleMap = {
'zh-CN': zhCn,
@ -51,7 +51,8 @@ export const useLocaleStore = defineStore('locales', {
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
setStorage('lang', localeMap?.lang)
}
}
},
persist: true
})
export const useLocaleStoreWithOut = () => {

View File

@ -40,10 +40,7 @@ export const useLockStore = defineStore('lock', {
}
}
},
persist: {
enabled: true,
strategies: [{ key: 'lock', storage: localStorage }]
}
persist: true
})
export const useLockStoreWithOut = () => {

View File

@ -60,6 +60,9 @@ export const usePermissionStore = defineStore('permission', {
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
this.menuTabRouters = routers
}
},
persist: {
paths: ['routers', 'addRouters', 'menuTabRouters']
}
})

View File

@ -4,12 +4,7 @@ import { getRawRoute } from '@/utils/routerHelper'
import { defineStore } from 'pinia'
import { store } from '../index'
import { findIndex } from '@/utils'
import { useStorage } from '@/hooks/web/useStorage'
import { useAppStoreWithOut } from './app'
const appStore = useAppStoreWithOut()
const { getStorage } = useStorage()
import { useAuthStoreWithOut } from './auth'
export interface TagsViewState {
visitedViews: RouteLocationNormalizedLoaded[]
@ -95,8 +90,9 @@ export const useTagsViewStore = defineStore('tagsView', {
},
// 删除所有tag
delAllVisitedViews() {
const authStore = useAuthStoreWithOut()
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
this.visitedViews = getStorage(appStore.getUserInfo)
this.visitedViews = authStore.getUser
? this.visitedViews.filter((tag) => tag?.meta?.affix)
: []
},
@ -157,7 +153,8 @@ export const useTagsViewStore = defineStore('tagsView', {
}
}
}
}
},
persist: false
})
export const useTagsViewStoreWithOut = () => {

View File

@ -151,3 +151,22 @@ const subtractLight = (color: string, amount: number) => {
const c = cc < 0 ? 0 : cc
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
}
/**
* Mixes two colors.
*
* @param {string} color1 - The first color, should be a 6-digit hexadecimal color code starting with `#`.
* @param {string} color2 - The second color, should be a 6-digit hexadecimal color code starting with `#`.
* @param {number} [weight=0.5] - The weight of color1 in the mix, should be a number between 0 and 1, where 0 represents 100% of color2, and 1 represents 100% of color1.
* @returns {string} The mixed color, a 6-digit hexadecimal color code starting with `#`.
*/
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
let color = '#'
for (let i = 0; i <= 2; i++) {
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16)
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16)
const c = Math.round(c1 * weight + c2 * (1 - weight))
color += c.toString(16).padStart(2, '0')
}
return color
}

View File

@ -1,5 +1,3 @@
// import type { Plugin } from 'vue'
/**
*
* @param component
@ -47,6 +45,10 @@ export const setCssVar = (prop: string, val: any, dom = document.documentElement
dom.style.setProperty(prop, val)
}
export const getCssVar = (prop: string, dom = document.documentElement) => {
return getComputedStyle(dom).getPropertyValue(prop)
}
/**
*
* @param {Array} ary
@ -123,6 +125,17 @@ export function firstUpperCase(str: string) {
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
}
/**
* formData
*/
export function objToFormData(obj: Recordable) {
const formData = new FormData()
Object.keys(obj).forEach((key) => {
formData.append(key, obj[key])
})
return formData
}
// 根据当前时间获取祝福语
export const getGreeting = (): string => {
const now = new Date()

View File

@ -1,12 +1,11 @@
import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types'
import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'
import { CSSProperties } from 'vue'
// 自定义扩展vue-types
type PropTypes = VueTypesInterface & {
readonly style: VueTypeValidableDef<CSSProperties>
}
const propTypes = createTypes({
const newPropTypes = createTypes({
func: undefined,
bool: undefined,
string: undefined,
@ -15,15 +14,12 @@ const propTypes = createTypes({
integer: undefined
}) as PropTypes
// 需要自定义扩展的类型
// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
propTypes.extend([
{
name: 'style',
getter: true,
type: [String, Object],
default: undefined
class propTypes extends newPropTypes {
static get style() {
return toValidableType('style', {
type: [String, Object]
})
}
])
}
export { propTypes }

View File

@ -155,12 +155,12 @@ initMap()
background-color: #f13737;
box-shadow: 0px 0px 15px #f61212;
border-radius: 50%;
-webkit-animation-name: 'alarmDeviceBreath'; /*动画属性名也就是我们前面keyframes定义的动画名*/
-webkit-animation-duration: 1s; /*动画持续时间*/
-webkit-animation-timing-function: ease; /*动画频率和transition-timing-function是一样的*/
-webkit-animation-delay: 0s; /*动画延迟时间*/
-webkit-animation-iteration-count: infinite; /*定义循环资料infinite为无限次*/
-webkit-animation-direction: alternate; /*定义动画方式*/
--webkit-animation-name: 'alarmDeviceBreath'; /*动画属性名也就是我们前面keyframes定义的动画名*/
--webkit-animation-duration: 1s; /*动画持续时间*/
--webkit-animation-timing-function: ease; /*动画频率和transition-timing-function是一样的*/
--webkit-animation-delay: 0s; /*动画延迟时间*/
--webkit-animation-iteration-count: infinite; /*定义循环资料infinite为无限次*/
--webkit-animation-direction: alternate; /*定义动画方式*/
}
</style>

View File

@ -3,14 +3,14 @@ import { ElCard, ElRow, ElCol, ElTabs, ElTabPane, ElAvatar } from 'element-plus'
import { computed, ref } from 'vue'
import InfoWrite from './components/InfoWrite.vue'
import PasswordWrite from './components/PasswordWrite.vue'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { useAuthStore } from '@/store/modules/auth'
import avatar from '@/assets/imgs/avatar.jpg'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
const activeName = ref('info')
const authStore = useAuthStoreWithOut()
const authStore = useAuthStore()
let genderOptions = ref<DictDetail[]>([])

View File

@ -3,13 +3,14 @@ import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { reactive, ref } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { ElButton, ElMessage } from 'element-plus'
import { useAuthStore } from '@/store/modules/auth'
import { ElMessage } from 'element-plus'
import { postCurrentUserUpdateInfo } from '@/api/vadmin/auth/user'
import { BaseButton } from '@/components/Button'
const { required, isTelephone } = useValidator()
const authStore = useAuthStoreWithOut()
const authStore = useAuthStore()
const formSchema = reactive<FormSchema[]>([
{
@ -92,9 +93,9 @@ const formSchema = reactive<FormSchema[]>([
return (
<>
<div class="w-[50%]">
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
<BaseButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
保存
</ElButton>
</BaseButton>
</div>
</>
)

View File

@ -3,13 +3,14 @@ import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { reactive, ref } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { ElButton, ElMessage } from 'element-plus'
import { useAuthStore } from '@/store/modules/auth'
import { ElMessage } from 'element-plus'
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
import { BaseButton } from '@/components/Button'
const { required } = useValidator()
const authStore = useAuthStoreWithOut()
const authStore = useAuthStore()
const formSchema = reactive<FormSchema[]>([
{
@ -57,9 +58,9 @@ const formSchema = reactive<FormSchema[]>([
return (
<>
<div class="w-[50%]">
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
<BaseButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
保存
</ElButton>
</BaseButton>
</div>
</>
)

View File

@ -2,18 +2,18 @@
import { reactive, ref, watch } from 'vue'
import { Form } from '@/components/Form'
import { useI18n } from '@/hooks/web/useI18n'
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
import { ElCheckbox, ElLink } from 'element-plus'
import { useForm } from '@/hooks/web/useForm'
import { getRoleMenusApi } from '@/api/login'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { useAuthStore } from '@/store/modules/auth'
import { usePermissionStore } from '@/store/modules/permission'
import { useRouter } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
import { UserLoginType } from '@/api/login/types'
import { useValidator } from '@/hooks/web/useValidator'
import { useStorage } from '@/hooks/web/useStorage'
import { FormSchema } from '@/components/Form'
import { Icon } from '@/components/Icon'
import { BaseButton } from '@/components/Button'
const emit = defineEmits(['to-telephone'])
@ -21,10 +21,9 @@ const { required } = useValidator()
const permissionStore = usePermissionStore()
const authStore = useAuthStoreWithOut()
const authStore = useAuthStore()
const { currentRoute, addRoute, push } = useRouter()
const { setStorage } = useStorage()
const { t } = useI18n()
@ -122,14 +121,19 @@ const schema = reactive<FormSchema[]>([
return (
<>
<div class="w-[100%]">
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={signIn}>
<BaseButton
loading={loading.value}
type="primary"
class="w-[100%]"
onClick={signIn}
>
{t('login.login')}
</ElButton>
</BaseButton>
</div>
<div class="w-[100%] mt-15px">
<ElButton class="w-[100%]" onClick={toTelephoneLogin}>
<BaseButton class="w-[100%]" onClick={toTelephoneLogin}>
{t('login.smsLogin')}
</ElButton>
</BaseButton>
</div>
</>
)
@ -241,7 +245,6 @@ const getMenu = async () => {
const res = await getRoleMenusApi()
if (res) {
const routers = res.data || []
setStorage('roleRouters', routers)
await permissionStore.generateRoutes(routers).catch(() => {})
permissionStore.getAddRouters.forEach((route) => {
addRoute(route as RouteRecordRaw) // 访

View File

@ -3,16 +3,16 @@ import { Form } from '@/components/Form'
import { reactive, ref, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { useForm } from '@/hooks/web/useForm'
import { ElButton, ElInput, FormRules, ElDivider, ElMessage } from 'element-plus'
import { ElInput, FormRules, ElDivider, ElMessage } from 'element-plus'
import { useValidator } from '@/hooks/web/useValidator'
import { FormSchema } from '@/components/Form'
import { postSMSCodeApi } from '@/api/login'
import { UserLoginType } from '@/api/login/types'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { useAuthStore } from '@/store/modules/auth'
import { RouteLocationNormalizedLoaded, useRouter, RouteRecordRaw } from 'vue-router'
import { getRoleMenusApi } from '@/api/login'
import { useStorage } from '@/hooks/web/useStorage'
import { usePermissionStore } from '@/store/modules/permission'
import { BaseButton } from '@/components/Button'
const emit = defineEmits(['to-password'])
@ -22,8 +22,7 @@ const { t } = useI18n()
const { required } = useValidator()
const { currentRoute, addRoute, push } = useRouter()
const permissionStore = usePermissionStore()
const authStore = useAuthStoreWithOut()
const { setStorage } = useStorage()
const authStore = useAuthStore()
const schema = reactive<FormSchema[]>([
{
@ -74,13 +73,13 @@ const schema = reactive<FormSchema[]>([
<>
<ElDivider direction="vertical" />
{SMSCodeStatus.value ? (
<ElButton type="primary" link onClick={getSMSCode}>
<BaseButton type="primary" link onClick={getSMSCode}>
{t('login.getSMSCode')}
</ElButton>
</BaseButton>
) : (
<ElButton type="primary" disabled={!SMSCodeStatus.value} link>
<BaseButton type="primary" disabled={!SMSCodeStatus.value} link>
{SMSCodeNumber.value + t('login.SMSCodeRetry')}
</ElButton>
</BaseButton>
)}
</>
)
@ -110,19 +109,19 @@ const schema = reactive<FormSchema[]>([
return (
<div class="w-[100%]">
<div class="w-[100%]">
<ElButton
<BaseButton
type="primary"
class="w-[100%]"
loading={loading.value}
onClick={telephoneCodeLogin}
>
{t('login.login')}
</ElButton>
</BaseButton>
</div>
<div class="w-[100%] mt-15px">
<ElButton class="w-[100%]" onClick={toPasswordLogin}>
<BaseButton class="w-[100%]" onClick={toPasswordLogin}>
{t('login.passwordLogin')}
</ElButton>
</BaseButton>
</div>
</div>
)
@ -216,7 +215,6 @@ const getMenu = async () => {
const res = await getRoleMenusApi()
if (res) {
const routers = res.data || []
setStorage('roleRouters', routers)
await permissionStore.generateRoutes(routers).catch(() => {})
permissionStore.getAddRouters.forEach((route) => {
addRoute(route as RouteRecordRaw) // 访

View File

@ -3,21 +3,19 @@ import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { computed, reactive, ref, watch } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { ElButton, ElMessage } from 'element-plus'
import { useAuthStore } from '@/store/modules/auth'
import { ElMessage } from 'element-plus'
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
import { getRoleMenusApi } from '@/api/login'
import { useStorage } from '@/hooks/web/useStorage'
import { usePermissionStore } from '@/store/modules/permission'
import { RouteLocationNormalizedLoaded, RouteRecordRaw, useRouter } from 'vue-router'
import { useAppStore } from '@/store/modules/app'
import { Footer } from '@/components/Footer'
const { required } = useValidator()
const { setStorage } = useStorage()
const { addRoute, push, currentRoute } = useRouter()
const authStore = useAuthStoreWithOut()
const authStore = useAuthStore()
const appStore = useAppStore()
const permissionStore = usePermissionStore()
@ -112,7 +110,6 @@ const getMenu = async () => {
const res = await getRoleMenusApi()
if (res) {
const routers = res.data || []
setStorage('roleRouters', routers)
await permissionStore.generateRoutes(routers).catch(() => {})
permissionStore.getAddRouters.forEach((route) => {
addRoute(route as RouteRecordRaw) // 访
@ -137,9 +134,9 @@ const getMenu = async () => {
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
/>
<div class="w-[100%]">
<ElButton :loading="loading" type="primary" class="w-[100%]" @click="save">
<BaseButton :loading="loading" type="primary" class="w-[100%]" @click="save">
重置密码
</ElButton>
</BaseButton>
</div>
</div>

View File

@ -9,10 +9,11 @@ import {
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch, ElRow, ElCol } from 'element-plus'
import { ElSwitch, ElRow, ElCol } from 'element-plus'
import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'AuthDept'
@ -89,7 +90,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.disabled} disabled />
<ElSwitch modelValue={!row.disabled} disabled />
</>
)
}
@ -105,13 +106,13 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => editAction(row)}>
<BaseButton type="primary" link size="small" onClick={() => editAction(row)}>
编辑
</ElButton>
<ElButton type="primary" link size="small" onClick={() => addSonAction(row)}>
</BaseButton>
<BaseButton type="primary" link size="small" onClick={() => addSonAction(row)}>
添加子部门
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
loading={delLoading.value}
link
@ -119,7 +120,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -212,7 +213,7 @@ const save = async () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="addAction">新增部门</ElButton>
<BaseButton type="primary" @click="addAction">新增部门</BaseButton>
</ElCol>
</ElRow>
</template>
@ -223,10 +224,15 @@ const save = async () => {
<Write ref="writeRef" :current-row="currentRow" :parent-id="parentId" />
<template #footer>
<ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
<BaseButton
v-if="actionType !== 'detail'"
type="primary"
:loading="saveLoading"
@click="save"
>
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -9,13 +9,14 @@ import {
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch, ElRow, ElCol } from 'element-plus'
import { ElSwitch, ElRow, ElCol } from 'element-plus'
import { Icon } from '@/components/Icon'
import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { useDictStore } from '@/store/modules/dict'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'AuthMenu'
@ -122,7 +123,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.noCache} disabled />
<ElSwitch modelValue={!row.noCache} disabled />
</>
)
}
@ -138,7 +139,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.hidden} disabled />
<ElSwitch modelValue={!row.hidden} disabled />
</>
)
}
@ -154,7 +155,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.disabled} disabled />
<ElSwitch modelValue={!row.disabled} disabled />
</>
)
}
@ -173,7 +174,7 @@ const tableColumns = reactive<TableColumn[]>([
const del = ['auth.menu.delete']
return (
<>
<ElButton
<BaseButton
type="primary"
v-hasPermi={update}
link
@ -181,8 +182,8 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => editAction(row)}
>
编辑
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="primary"
v-hasPermi={add}
link
@ -190,8 +191,8 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => addSonAction(row)}
>
添加子菜单
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
v-hasPermi={del}
loading={delLoading.value}
@ -200,7 +201,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -293,8 +294,8 @@ const save = async () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" v-hasPermi="['auth.menu.create']" @click="addAction"
>新增菜单</ElButton
<BaseButton type="primary" v-hasPermi="['auth.menu.create']" @click="addAction"
>新增菜单</BaseButton
>
</ElCol>
</ElRow>
@ -306,10 +307,15 @@ const save = async () => {
<Write ref="writeRef" :current-row="currentRow" :parent-id="parentId" />
<template #footer>
<ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
<BaseButton
v-if="actionType !== 'detail'"
type="primary"
:loading="saveLoading"
@click="save"
>
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -5,8 +5,8 @@ import { PropType, reactive, watch } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { propTypes } from '@/utils/propTypes'
import { getMenuTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { ElButton } from 'element-plus'
import { IconPicker } from '@/components/IconPicker'
import { BaseButton } from '@/components/Button'
const { required } = useValidator()
@ -88,9 +88,9 @@ const formSchema = reactive<FormSchema[]>([
<div style="display: flex; justify-content: space-between">
<IconPicker style="width: 470px" input-disabled={false} v-model={data['icon']} />
<div style="margin-left: 10px">
<ElButton type="primary" onClick={toIconify}>
<BaseButton type="primary" onClick={toIconify}>
Iconify
</ElButton>
</BaseButton>
</div>
</div>
</>

View File

@ -10,7 +10,7 @@ import {
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch } from 'element-plus'
import { ElSwitch } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
@ -19,6 +19,7 @@ import AuthManage from './components/AuthManage.vue'
import { Dialog } from '@/components/Dialog'
import { DictDetail, selectDictLabel } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'AuthRole'
@ -105,7 +106,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.disabled} disabled />
<ElSwitch modelValue={!row.disabled} disabled />
</>
)
}
@ -120,7 +121,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={row.is_admin} disabled />
<ElSwitch modelValue={row.is_admin} disabled />
</>
)
}
@ -143,7 +144,7 @@ const tableColumns = reactive<TableColumn[]>([
const del = ['auth.role.delete']
return (
<>
<ElButton
<BaseButton
v-show={row.id !== 1}
type="primary"
v-hasPermi={update}
@ -152,8 +153,8 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => editAction(row)}
>
编辑
</ElButton>
<ElButton
</BaseButton>
<BaseButton
v-show={row.id !== 1}
type="primary"
link
@ -161,8 +162,8 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => authManageActive(row)}
>
权限管理
</ElButton>
<ElButton
</BaseButton>
<BaseButton
v-show={row.id !== 1}
type="danger"
v-hasPermi={del}
@ -172,7 +173,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -329,8 +330,8 @@ const save = async () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" v-hasPermi="['auth.role.create']" @click="addAction"
>新增角色</ElButton
<BaseButton type="primary" v-hasPermi="['auth.role.create']" @click="addAction"
>新增角色</BaseButton
>
</ElCol>
</ElRow>
@ -342,10 +343,10 @@ const save = async () => {
<Write ref="writeRef" :current-row="currentRow" />
<template #footer>
<ElButton type="primary" :loading="saveLoading" @click="save">
<BaseButton type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import {
ElDrawer,
ElButton,
ElDivider,
ElSelect,
ElOption,
@ -171,7 +170,7 @@ defineExpose({
<div class="mt-1 text-[#909399]">
<span>角色名称{{ data.name }}</span>
</div>
<ElButton type="primary" :loading="loading" @click="submit">保存</ElButton>
<BaseButton type="primary" :loading="loading" @click="submit">保存</BaseButton>
</div>
<ElDivider />
<ElContainer>
@ -192,7 +191,7 @@ defineExpose({
/>
</ElSelect>
<div
v-if="data.data_range === '4'"
v-if="data.data_range === '3'"
class="mt-3 max-h-[65vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto"
>
<ElTree

View File

@ -11,7 +11,7 @@ import {
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch, ElRow, ElCol, ElMessage } from 'element-plus'
import { ElSwitch, ElRow, ElCol, ElMessage } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
@ -22,6 +22,7 @@ import { useDictStore } from '@/store/modules/dict'
import Import from './components/Import.vue'
import PasswordSendSMS from './components/PasswordSendSMS.vue'
import PasswordSendEmail from './components/PasswordSendEmail.vue'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'AuthUser'
@ -164,7 +165,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={row.is_active} disabled />
<ElSwitch modelValue={row.is_active} disabled />
</>
)
}
@ -179,7 +180,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={row.is_staff} disabled />
<ElSwitch modelValue={row.is_staff} disabled />
</>
)
}
@ -209,7 +210,7 @@ const tableColumns = reactive<TableColumn[]>([
const del = ['auth.user.delete']
return (
<>
<ElButton
<BaseButton
type="primary"
v-hasPermi={update}
link
@ -217,8 +218,8 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => editAction(row)}
>
编辑
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
v-hasPermi={del}
loading={delLoading.value}
@ -227,7 +228,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -438,22 +439,22 @@ const save = async () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5" v-hasPermi="['auth.user.create']">
<ElButton type="primary" @click="addAction">新增用户</ElButton>
<BaseButton type="primary" @click="addAction">新增用户</BaseButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.import']">
<ElButton @click="importList">批量导入用户</ElButton>
<BaseButton @click="importList">批量导入用户</BaseButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.export']">
<ElButton @click="exportQueryList()">导出筛选用户</ElButton>
<BaseButton @click="exportQueryList()">导出筛选用户</BaseButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.reset']">
<ElButton @click="sendPasswordToSMS">重置密码通知短信</ElButton>
<BaseButton @click="sendPasswordToSMS">重置密码通知短信</BaseButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.reset']">
<ElButton @click="sendPasswordToEmail">重置密码通知邮件</ElButton>
<BaseButton @click="sendPasswordToEmail">重置密码通知邮件</BaseButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.delete']">
<ElButton type="danger" @click="delData(null)">批量删除</ElButton>
<BaseButton type="danger" @click="delData(null)">批量删除</BaseButton>
</ElCol>
</ElRow>
</template>
@ -487,10 +488,10 @@ const save = async () => {
/>
<template #footer v-if="actionType === 'add' || actionType === 'edit'">
<ElButton type="primary" :loading="saveLoading" @click="save">
<BaseButton type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -3,7 +3,6 @@ import {
ElLink,
ElRow,
ElCol,
ElButton,
ElTable,
ElTableColumn,
ElUpload,
@ -130,21 +129,21 @@ const downloadErrorFile = async (row: Recordable) => {
:disabled="tableData.length > 0"
>
<ElTooltip effect="dark" content="只支持上传XLSX文件" placement="top">
<ElButton type="primary" size="small" :disabled="tableData.length > 0"
>上传文件</ElButton
<BaseButton type="primary" size="small" :disabled="tableData.length > 0"
>上传文件</BaseButton
>
</ElTooltip>
</ElUpload>
</div>
</ElCol>
<ElCol :span="1.5">
<ElButton
<BaseButton
type="primary"
size="small"
:disabled="tableData.length === 0"
:loading="importLoading"
@click="handleImport"
>确认导入</ElButton
>确认导入</BaseButton
>
</ElCol>
</ElRow>
@ -156,7 +155,7 @@ const downloadErrorFile = async (row: Recordable) => {
<template #default>
<ElPopconfirm title="确认删除吗?" @confirm="handleDelete">
<template #reference>
<ElButton link type="primary" size="small">删除</ElButton>
<BaseButton link type="primary" size="small">删除</BaseButton>
</template>
</ElPopconfirm>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ElButton, ElTable, ElTableColumn, ElPopconfirm, ElMessage, ElTag } from 'element-plus'
import { ElTable, ElTableColumn, ElPopconfirm, ElMessage, ElTag } from 'element-plus'
import { postUsersInitPasswordSendEmailApi } from '@/api/vadmin/auth/user'
import { ref, PropType } from 'vue'
@ -40,12 +40,12 @@ const initPassword = async () => {
<div>
<div class="flex justify-between">
<span>已选用户列表</span>
<ElButton
<BaseButton
type="primary"
:disabled="tableData?.length === 0"
:loading="loading"
@click="initPassword"
>确认重置并发送邮件通知</ElButton
>确认重置并发送邮件通知</BaseButton
>
</div>
<ElTable
@ -86,8 +86,8 @@ const initPassword = async () => {
<template #default="scope">
<ElPopconfirm title="确认移除吗?" @confirm="handleDelete(scope.$index)">
<template #reference>
<ElButton v-if="scope.row.send_sms_status !== true" link type="primary" size="small"
>移除</ElButton
<BaseButton v-if="scope.row.send_sms_status !== true" link type="primary" size="small"
>移除</BaseButton
>
</template>
</ElPopconfirm>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ElButton, ElTable, ElTableColumn, ElPopconfirm, ElMessage, ElTag } from 'element-plus'
import { ElTable, ElTableColumn, ElPopconfirm, ElMessage, ElTag } from 'element-plus'
import { postUsersInitPasswordSendSMSApi } from '@/api/vadmin/auth/user'
import { ref, PropType } from 'vue'
@ -40,12 +40,12 @@ const initPassword = async () => {
<div>
<div class="flex justify-between">
<span>已选用户列表</span>
<ElButton
<BaseButton
type="primary"
:disabled="tableData?.length === 0"
:loading="loading"
@click="initPassword"
>确认重置并发送短信通知</ElButton
>确认重置并发送短信通知</BaseButton
>
</div>
<ElTable
@ -86,8 +86,8 @@ const initPassword = async () => {
<template #default="scope">
<ElPopconfirm title="确认移除吗?" @confirm="handleDelete(scope.$index)">
<template #reference>
<ElButton v-if="scope.row.send_sms_status !== true" link type="primary" size="small"
>移除</ElButton
<BaseButton v-if="scope.row.send_sms_status !== true" link type="primary" size="small"
>移除</BaseButton
>
</template>
</ElPopconfirm>

View File

@ -3,13 +3,14 @@ import { reactive, ref, unref } from 'vue'
import { getIssueListApi, delIssueListApi } from '@/api/vadmin/help/issue'
import { useTable } from '@/hooks/web/useTable'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch, ElRow, ElCol } from 'element-plus'
import { ElSwitch, ElRow, ElCol } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
import { useDictStore } from '@/store/modules/dict'
import { DictDetail } from '@/utils/dict'
import { useRouter } from 'vue-router'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'HelpIssue'
@ -85,7 +86,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={row.is_active} disabled />
<ElSwitch modelValue={row.is_active} disabled />
</>
)
}
@ -114,10 +115,10 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => editAction(row)}>
<BaseButton type="primary" link size="small" onClick={() => editAction(row)}>
编辑
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
loading={delLoading.value}
link
@ -125,7 +126,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -224,7 +225,7 @@ const addAction = () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="addAction">新增常见问题</ElButton>
<BaseButton type="primary" @click="addAction">新增常见问题</BaseButton>
</ElCol>
</ElRow>
</template>

View File

@ -4,7 +4,7 @@ import { useForm } from '@/hooks/web/useForm'
import { reactive, ref } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { useRouter } from 'vue-router'
import { ElButton, ElMessage } from 'element-plus'
import { ElMessage } from 'element-plus'
import { ContentWrap } from '@/components/ContentWrap'
// import { useTagsViewStore } from '@/store/modules/tagsView'
import {
@ -13,6 +13,7 @@ import {
putIssueApi,
getIssueCategoryOptionsApi
} from '@/api/vadmin/help/issue'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'HelpIssueForm'
@ -114,9 +115,9 @@ const formSchema = reactive<FormSchema[]>([
default: () => {
return (
<>
<ElButton loading={saveLoading.value} type="primary" onClick={submit}>
<BaseButton loading={saveLoading.value} type="primary" onClick={submit}>
立即保存
</ElButton>
</BaseButton>
</>
)
}

View File

@ -10,7 +10,7 @@ import {
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch, ElRow, ElCol } from 'element-plus'
import { ElSwitch, ElRow, ElCol } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
@ -18,6 +18,7 @@ import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { useDictStore } from '@/store/modules/dict'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'HelpIssueCategory'
@ -94,7 +95,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={row.is_active} disabled />
<ElSwitch modelValue={row.is_active} disabled />
</>
)
}
@ -121,10 +122,10 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => editAction(row)}>
<BaseButton type="primary" link size="small" onClick={() => editAction(row)}>
编辑
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
loading={delLoading.value}
link
@ -132,7 +133,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -276,7 +277,7 @@ const save = async () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="addAction">新增常见问题类别</ElButton>
<BaseButton type="primary" @click="addAction">新增常见问题类别</BaseButton>
</ElCol>
</ElRow>
</template>
@ -287,10 +288,10 @@ const save = async () => {
<Write ref="writeRef" :current-row="currentRow" />
<template #footer>
<ElButton type="primary" :loading="saveLoading" @click="save">
<BaseButton type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -4,13 +4,14 @@ import { addImagesApi, getImagesListApi, delImagesListApi } from '@/api/vadmin/r
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElMessage, ElRow, ElCol, ElImage } from 'element-plus'
import { ElMessage, ElRow, ElCol, ElImage } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { useClipboard } from '@vueuse/core'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'ResourceImage'
@ -123,13 +124,13 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => toCopy(row.id)}>
<BaseButton type="primary" link size="small" onClick={() => toCopy(row.id)}>
复制编号
</ElButton>
<ElButton type="primary" link size="small" onClick={() => toCopy(row.image_url)}>
</BaseButton>
<BaseButton type="primary" link size="small" onClick={() => toCopy(row.image_url)}>
复制链接
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
loading={delLoading.value}
link
@ -137,7 +138,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -259,8 +260,8 @@ const save = async () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="addAction">新增图片素材</ElButton>
<ElButton type="danger" @click="delData(null)">批量删除</ElButton>
<BaseButton type="primary" @click="addAction">新增图片素材</BaseButton>
<BaseButton type="danger" @click="delData(null)">批量删除</BaseButton>
</ElCol>
</ElRow>
</template>
@ -271,10 +272,10 @@ const save = async () => {
<Write ref="writeRef" :current-row="currentRow" />
<template #footer>
<ElButton type="primary" :loading="saveLoading" @click="save">
<BaseButton type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -10,13 +10,14 @@ import {
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch, ElRow, ElCol } from 'element-plus'
import { ElSwitch, ElRow, ElCol } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { propTypes } from '@/utils/propTypes'
import { BaseButton } from '@/components/Button'
const props = defineProps({
dictTypeId: propTypes.number.def(undefined)
@ -79,7 +80,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.disabled} disabled />
<ElSwitch modelValue={!row.disabled} disabled />
</>
)
}
@ -105,10 +106,10 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => editAction(row)}>
<BaseButton type="primary" link size="small" onClick={() => editAction(row)}>
编辑
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
loading={delLoading.value}
link
@ -116,7 +117,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -237,7 +238,7 @@ watch(
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="addAction">新增字典元素</ElButton>
<BaseButton type="primary" @click="addAction">新增字典元素</BaseButton>
</ElCol>
</ElRow>
</template>
@ -248,10 +249,10 @@ watch(
<Write ref="writeRef" :current-row="currentRow" :dict-type-id="dictTypeId" />
<template #footer>
<ElButton type="primary" :loading="saveLoading" @click="save">
<BaseButton type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -10,7 +10,7 @@ import {
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElMessage, ElSwitch, ElRow, ElCol } from 'element-plus'
import { ElMessage, ElSwitch, ElRow, ElCol } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
@ -18,6 +18,7 @@ import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { Icon } from '@/components/Icon'
import { useClipboard } from '@vueuse/core'
import { BaseButton } from '@/components/Button'
const { t } = useI18n()
@ -97,7 +98,7 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElSwitch value={!row.disabled} disabled />
<ElSwitch modelValue={!row.disabled} disabled />
</>
)
}
@ -123,10 +124,10 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => editAction(row)}>
<BaseButton type="primary" link size="small" onClick={() => editAction(row)}>
编辑
</ElButton>
<ElButton
</BaseButton>
<BaseButton
type="danger"
loading={delLoading.value}
link
@ -134,7 +135,7 @@ const tableColumns = reactive<TableColumn[]>([
onClick={() => delData(row)}
>
删除
</ElButton>
</BaseButton>
</>
)
}
@ -273,10 +274,10 @@ const clearCurrentRow = async () => {
<template #toolbar>
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="addAction">新增字典类型</ElButton>
<BaseButton type="primary" @click="addAction">新增字典类型</BaseButton>
</ElCol>
<ElCol :span="1.5">
<ElButton type="danger" @click="clearCurrentRow">清除选择</ElButton>
<BaseButton type="danger" @click="clearCurrentRow">清除选择</BaseButton>
</ElCol>
</ElRow>
</template>
@ -287,10 +288,10 @@ const clearCurrentRow = async () => {
<Write ref="writeRef" :current-row="currentRow" />
<template #footer>
<ElButton type="primary" :loading="saveLoading" @click="save">
<BaseButton type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</BaseButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -4,7 +4,7 @@ import { getRecordLoginListApi } from '@/api/vadmin/system/record/login'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton, ElSwitch } from 'element-plus'
import { ElSwitch } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
@ -12,6 +12,7 @@ import Detail from './components/Detail.vue'
import { Dialog } from '@/components/Dialog'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'SystemRecordLogin'
@ -72,7 +73,7 @@ const tableColumns = reactive<TableColumn[]>([
default: (data: any) => {
return (
<>
<ElSwitch value={data.row.status} size="small" disabled />
<ElSwitch modelValue={data.row.status} size="small" disabled />
</>
)
}
@ -168,9 +169,9 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link onClick={() => action(row, 'detail')}>
<BaseButton type="primary" link onClick={() => action(row, 'detail')}>
详情
</ElButton>
</BaseButton>
</>
)
}
@ -289,7 +290,7 @@ const action = (row: any, type: string) => {
<Detail v-if="actionType === 'detail'" :current-row="currentRow" />
<template #footer>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -46,7 +46,7 @@ const detailSchema = reactive<DescriptionsSchema[]>([
default: (data: any) => {
return (
<>
<ElSwitch value={data.status} size="small" disabled />
<ElSwitch modelValue={data.status} size="small" disabled />
</>
)
}

View File

@ -4,12 +4,12 @@ import { getRecordOperationListApi } from '@/api/vadmin/system/record/operation'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
import Detail from './components/Detail.vue'
import { Dialog } from '@/components/Dialog'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'SystemRecordOperation'
@ -141,9 +141,9 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link onClick={() => action(row, 'detail')}>
<BaseButton type="primary" link onClick={() => action(row, 'detail')}>
详情
</ElButton>
</BaseButton>
</>
)
}
@ -222,7 +222,7 @@ const action = (row: any, type: string) => {
<Detail v-if="actionType === 'detail'" :current-row="currentRow" />
<template #footer>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -3,7 +3,6 @@ import { reactive, ref, unref } from 'vue'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { Table, TableColumn } from '@/components/Table'
import { ElButton } from 'element-plus'
import { Search } from '@/components/Search'
import { FormSchema } from '@/components/Form'
import { ContentWrap } from '@/components/ContentWrap'
@ -13,6 +12,7 @@ import { selectDictLabel, DictDetail } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
import { useRouter } from 'vue-router'
import { getTaskRecordListApi } from '@/api/vadmin/system/task'
import { BaseButton } from '@/components/Button'
defineOptions({
name: 'SystemRecordTask'
@ -144,9 +144,9 @@ const tableColumns = reactive<TableColumn[]>([
const row = data.row
return (
<>
<ElButton type="primary" link size="small" onClick={() => view(row)}>
<BaseButton type="primary" link size="small" onClick={() => view(row)}>
详情
</ElButton>
</BaseButton>
</>
)
}
@ -228,7 +228,7 @@ if (job_id) {
<Detail v-if="actionType === 'detail'" :current-row="currentRow" />
<template #footer>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
<BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
</template>
</Dialog>
</template>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import { ElButton } from 'element-plus'
import { getSystemSettingsApi, putSystemSettingsApi } from '@/api/vadmin/system/settings'
import { propTypes } from '@/utils/propTypes'
import { Editor, EditorExpose } from '@/components/Editor'
@ -74,7 +73,7 @@ getData()
:editorConfig="editorConfig"
/>
<div class="mt-10px" style="float: right">
<ElButton :loading="loading" type="primary" @click="save">立即保存</ElButton>
<BaseButton :loading="loading" type="primary" @click="save">立即保存</BaseButton>
</div>
</template>

View File

@ -1,26 +1,27 @@
<script setup lang="tsx">
import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { ElButton, ElUpload } from 'element-plus'
import { ElUpload } from 'element-plus'
import { getSystemSettingsApi, putSystemSettingsApi } from '@/api/vadmin/system/settings'
import { reactive, ref } from 'vue'
import { reactive, ref, computed } from 'vue'
import { ElMessage, ElIcon } from 'element-plus'
import type { UploadProps } from 'element-plus'
import { useStorage } from '@/hooks/web/useStorage'
import { useAuthStore } from '@/store/modules/auth'
import { useAppStore } from '@/store/modules/app'
import { propTypes } from '@/utils/propTypes'
import { Icon } from '@/components/Icon'
import { useValidator } from '@/hooks/web/useValidator'
import { BaseButton } from '@/components/Button'
const { required } = useValidator()
const { getStorage } = useStorage()
const authStore = useAuthStore()
const appStore = useAppStore()
const props = defineProps({
tabId: propTypes.number
})
const token = getStorage(appStore.getToken)
const token = computed(() => authStore.getToken)
const formSchema = reactive<FormSchema[]>([
{
@ -211,9 +212,9 @@ const formSchema = reactive<FormSchema[]>([
default: () => {
return (
<>
<ElButton loading={loading.value} type="primary" onClick={save}>
<BaseButton loading={loading.value} type="primary" onClick={save}>
立即提交
</ElButton>
</BaseButton>
</>
)
}

View File

@ -1,12 +1,12 @@
<script setup lang="tsx">
import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { ElButton } from 'element-plus'
import { getSystemSettingsApi, putSystemSettingsApi } from '@/api/vadmin/system/settings'
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { useValidator } from '@/hooks/web/useValidator'
import { BaseButton } from '@/components/Button'
const { required } = useValidator()
@ -78,9 +78,9 @@ const formSchema = reactive<FormSchema[]>([
default: () => {
return (
<>
<ElButton loading={loading.value} type="primary" onClick={save}>
<BaseButton loading={loading.value} type="primary" onClick={save}>
立即提交
</ElButton>
</BaseButton>
</>
)
}

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