style(web): 统一后台图标与全局视觉

This commit is contained in:
Zhou Weidong committed 2026-10-19 11:12:38 +08:00
1 parent 5c087f4db9
commit 97fbeda508
67 files changed
+737 -744

No files matched your search

-1
View File
@@ -2,7 +2,6 @@
export type UserLoginType = {
username: string
password: string
}
export type UserType = {
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

+5
View File
@@ -0,0 +1,5 @@
// Author: fruit.com.cn
import AppIcon from './src/AppIcon.vue'
export { AppIcon }
+40
View File
@@ -0,0 +1,40 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { computed } from 'vue'
import { Icon } from '@/components/Icon'
import { normalizeAppIcon } from '@/constants/icons'
import { propTypes } from '@/utils/propTypes'
/**
* 后台统一图标入口:默认使用 Material Symbols Rounded。
*/
const props = defineProps({
icon: propTypes.string,
color: propTypes.string,
size: propTypes.number.def(18),
hoverColor: propTypes.string
})
const resolvedIcon = computed(() => normalizeAppIcon(props.icon))
</script>
<template>
<Icon
:icon="resolvedIcon"
:color="color"
:size="size"
:hover-color="hoverColor"
class="app-icon"
/>
</template>
<style scoped>
.app-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
vertical-align: middle;
}
</style>
+4 -4
View File
@@ -9,7 +9,7 @@ import { filterBreadcrumb } from './helper'
import { filter, treeToList } from '@/utils/tree'
import type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
@@ -54,7 +54,7 @@ export default defineComponent({
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon && breadcrumbIcon.value ? (
<>
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title)}
<AppIcon icon={meta.icon} class="mr-[5px]"></AppIcon> {t(v?.meta?.title)}
</>
) : (
t(v?.meta?.title)
@@ -117,10 +117,10 @@ export default defineComponent({
:deep(&__item):last-child {
.@{prefix-cls}__inner {
color: var(--el-text-color-placeholder);
color: var(--el-text-color-regular);
&:hover {
color: var(--el-text-color-placeholder);
color: var(--el-color-primary);
}
}
}
+3 -2
View File
@@ -5,6 +5,7 @@ import { computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -26,9 +27,9 @@ const toggleCollapse = () => {
<template>
<div :class="prefixCls" @click="toggleCollapse">
<Icon
<AppIcon
:size="18"
:icon="collapse ? 'ant-design:menu-unfold-outlined' : 'ant-design:menu-fold-outlined'"
:icon="collapse ? AppIcons.menuOpen : AppIcons.menu"
:color="color"
class="cursor-pointer"
/>
@@ -4,6 +4,7 @@
import { ElCard, ElTooltip } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -24,7 +25,7 @@ defineProps({
<template #content>
<div class="max-w-200px">{{ message }}</div>
</template>
<Icon class="ml-5px" icon="bi:question-circle-fill" :size="14" />
<AppIcon class="ml-5px" :icon="AppIcons.help" :size="14" />
</ElTooltip>
</div>
</template>
@@ -66,7 +66,7 @@ defineExpose({
:disabled="item.disabled"
:command="item"
>
<Icon :icon="item.icon" /> {{ t(item.label) }}
<AppIcon :icon="item.icon" /> {{ t(item.label) }}
</ElDropdownItem>
</ElDropdownMenu>
</template>
@@ -7,8 +7,9 @@ import { propTypes } from '@/utils/propTypes'
import { ref, unref, PropType, computed, defineComponent } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { DescriptionsSchema } from './types'
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { get } from 'lodash-es'
import { AppIcons } from '@/constants/icons'
const appStore = useAppStore()
@@ -94,12 +95,14 @@ export default defineComponent({
{props.title}
{props.message ? (
<ElTooltip content={props.message} placement="right">
<Icon icon="bi:question-circle-fill" class="ml-5px" size={14} />
<AppIcon icon={AppIcons.help} class="ml-5px" size={14} />
</ElTooltip>
) : null}
</div>
</div>
{props.collapse ? <Icon icon={show.value ? 'ep:arrow-down' : 'ep:arrow-up'} /> : null}
{props.collapse ? (
<AppIcon icon={show.value ? AppIcons.chevronDown : AppIcons.chevronUp} />
) : null}
</div>
) : null}
+5 -4
View File
@@ -5,6 +5,7 @@ import { ElDialog, ElScrollbar } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { computed, useAttrs, ref, unref, useSlots, watch, nextTick } from 'vue'
import { isNumber } from '@/utils/is'
import { AppIcons } from '@/constants/icons'
const slots = useSlots()
@@ -77,17 +78,17 @@ const dialogStyle = computed(() => {
<div
class="h-54px flex justify-between items-center absolute top-[50%] right-15px translate-y-[-50%]"
>
<Icon
<AppIcon
v-if="fullscreen"
class="cursor-pointer is-hover !h-54px mr-10px"
:icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
:icon="isFullscreen ? AppIcons.fullscreenExit : AppIcons.fullscreen"
color="var(--el-color-info)"
hover-color="var(--el-color-primary)"
@click="toggleFull"
/>
<Icon
<AppIcon
class="cursor-pointer is-hover !h-54px"
icon="ep:close"
:icon="AppIcons.close"
hover-color="var(--el-color-primary)"
color="var(--el-color-info)"
@click="close"
+5 -3
View File
@@ -6,6 +6,7 @@ import { ElIcon } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { Icon } from '@iconify/vue'
import { normalizeAppIcon } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -22,9 +23,10 @@ const props = defineProps({
})
const isLocal = computed(() => props.icon.startsWith('svg-icon:'))
const normalizedIcon = computed(() => normalizeAppIcon(props.icon))
const symbolId = computed(() => {
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : unref(normalizedIcon)
})
// 是否使用在线图标
@@ -48,8 +50,8 @@ const getIconifyStyle = computed(() => {
</svg>
<template v-else>
<Icon v-if="isUseOnline" :icon="icon" :style="getIconifyStyle" />
<div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
<Icon v-if="isUseOnline" :icon="normalizedIcon" :style="getIconifyStyle" />
<div v-else :class="`${normalizedIcon} iconify`" :style="getIconifyStyle"></div>
</template>
</ElIcon>
</template>
@@ -7,6 +7,7 @@ import { useLocaleStore } from '@/store/modules/locale'
import { useLocale } from '@/hooks/web/useLocale'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -36,9 +37,9 @@ const setLang = (lang: LocaleType) => {
<template>
<ElDropdown :class="prefixCls" trigger="click" @command="setLang">
<Icon
<AppIcon
:size="18"
icon="ion:language-sharp"
:icon="AppIcons.language"
class="cursor-pointer !p-0"
:class="$attrs.class"
:color="color"
+2 -2
View File
@@ -67,8 +67,8 @@ watch(
to="/"
>
<img
src="@/assets/imgs/logo.png"
class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
src="@/assets/imgs/brand-mark.png"
class="w-[var(--brand-mark-size)] h-[var(--brand-mark-size)] rounded-[var(--brand-mark-radius)] object-cover"
/>
<div
v-if="show"
@@ -1,5 +1,7 @@
// Author: fruit.com.cn
import type { RouteMeta } from 'vue-router'
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { useI18n } from '@/hooks/web/useI18n'
export const useRenderMenuTitle = () => {
@@ -9,7 +11,7 @@ export const useRenderMenuTitle = () => {
return icon ? (
<>
<Icon icon={meta.icon}></Icon>
<AppIcon icon={meta.icon}></AppIcon>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
{t(title as string)}
</span>
+4 -3
View File
@@ -1,10 +1,11 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { useFullscreen } from '@vueuse/core'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -23,9 +24,9 @@ const toggleFullscreen = () => {
<template>
<div :class="prefixCls" @click="toggleFullscreen">
<Icon
<AppIcon
:size="18"
:icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'"
:icon="isFullscreen ? AppIcons.fullscreenExit : AppIcons.fullscreen"
:color="color"
/>
</div>
@@ -5,6 +5,7 @@ import { ElButton } from 'element-plus'
import { useIcon } from '@/hooks/web/useIcon'
import { propTypes } from '@/utils/propTypes'
import { useI18n } from '@/hooks/web/useI18n'
import { AppIcons } from '@/constants/icons'
const emit = defineEmits(['search', 'reset', 'expand'])
@@ -37,7 +38,7 @@ const onExpand = () => {
v-if="showSearch"
type="primary"
:loading="searchLoading"
:icon="useIcon({ icon: 'ep:search' })"
:icon="useIcon({ icon: AppIcons.search })"
@click="onSearch"
>
{{ t('common.query') }}
@@ -45,14 +46,14 @@ const onExpand = () => {
<ElButton
v-if="showReset"
:loading="resetLoading"
:icon="useIcon({ icon: 'ep:refresh-right' })"
:icon="useIcon({ icon: AppIcons.refresh })"
@click="onReset"
>
{{ t('common.reset') }}
</ElButton>
<ElButton
v-if="showExpand"
:icon="useIcon({ icon: visible ? 'ep:arrow-down' : 'ep:arrow-up' })"
:icon="useIcon({ icon: visible ? AppIcons.chevronDown : AppIcons.chevronUp })"
text
@click="onExpand"
>
+16 -6
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup lang="ts">
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
import { ElDrawer, ElDivider, ElMessage, ElMessageBox } from 'element-plus'
import { ref, unref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ThemeSwitch } from '@/components/ThemeSwitch'
@@ -14,6 +14,7 @@ import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
import { useStorage } from '@/hooks/web/useStorage'
import { useClipboard } from '@vueuse/core'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { clear: storageClear } = useStorage('localStorage')
@@ -145,9 +146,18 @@ const copyConfig = async () => {
}
// 清空缓存
const clear = () => {
storageClear()
window.location.reload()
const clear = async () => {
try {
await ElMessageBox.confirm(t('setting.clearCacheMessage'), t('setting.clearAndReset'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
storageClear()
window.location.reload()
} catch {
// 取消清除时保持当前状态
}
}
const themeChange = () => {
@@ -163,7 +173,7 @@ const themeChange = () => {
class="fixed top-[45%] right-0 w-40px h-40px flex items-center justify-center bg-[var(--el-color-primary)] cursor-pointer z-10"
@click="drawer = true"
>
<Icon icon="ant-design:setting-outlined" color="#fff" />
<AppIcon :icon="AppIcons.settings" color="#fff" />
</div>
<ElDrawer v-model="drawer" direction="rtl" size="350px" :z-index="4000">
@@ -254,6 +264,6 @@ const themeChange = () => {
@prefix-cls: ~'@{namespace}-setting';
.@{prefix-cls} {
border-radius: 6px 0 0 6px;
border-radius: var(--app-radius-md) 0 0 6px;
}
</style>
@@ -4,6 +4,7 @@
import { PropType, watch, unref, ref } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -51,7 +52,7 @@ watch(
}"
@click="colorVal = item"
>
<Icon v-if="colorVal === item" color="#fff" icon="ep:check" :size="16" />
<AppIcon v-if="colorVal === item" color="#fff" :icon="AppIcons.check" :size="16" />
</span>
</div>
</template>
@@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
.@{prefix-cls} {
&__classic {
border: 2px solid #e5e7eb;
border-radius: 4px;
border-radius: var(--app-radius-sm);
&::before {
position: absolute;
@@ -77,7 +77,7 @@ const layout = computed(() => appStore.getLayout)
width: 33%;
height: 100%;
background-color: #273352;
border-radius: 4px 0 0 4px;
border-radius: var(--app-radius-sm) 0 0 4px;
content: '';
}
@@ -88,14 +88,14 @@ const layout = computed(() => appStore.getLayout)
width: 100%;
height: 25%;
background-color: #fff;
border-radius: 4px 4px 0;
border-radius: var(--app-radius-sm) 4px 0;
content: '';
}
}
&__top-left {
border: 2px solid #e5e7eb;
border-radius: 4px;
border-radius: var(--app-radius-sm);
&::before {
position: absolute;
@@ -105,7 +105,7 @@ const layout = computed(() => appStore.getLayout)
width: 100%;
height: 33%;
background-color: #273352;
border-radius: 4px 4px 0 0;
border-radius: var(--app-radius-sm) 4px 0 0;
content: '';
}
@@ -116,14 +116,14 @@ const layout = computed(() => appStore.getLayout)
width: 33%;
height: 100%;
background-color: #fff;
border-radius: 4px 0 0 4px;
border-radius: var(--app-radius-sm) 0 0 4px;
content: '';
}
}
&__top {
border: 2px solid #e5e7eb;
border-radius: 4px;
border-radius: var(--app-radius-sm);
&::before {
position: absolute;
@@ -133,14 +133,14 @@ const layout = computed(() => appStore.getLayout)
width: 100%;
height: 33%;
background-color: #273352;
border-radius: 4px 4px 0 0;
border-radius: var(--app-radius-sm) 4px 0 0;
content: '';
}
}
&__cut-menu {
border: 2px solid #e5e7eb;
border-radius: 4px;
border-radius: var(--app-radius-sm);
&::before {
position: absolute;
@@ -150,7 +150,7 @@ const layout = computed(() => appStore.getLayout)
width: 100%;
height: 33%;
background-color: #273352;
border-radius: 4px 4px 0 0;
border-radius: var(--app-radius-sm) 4px 0 0;
content: '';
}
@@ -161,7 +161,7 @@ const layout = computed(() => appStore.getLayout)
width: 10%;
height: 100%;
background-color: #fff;
border-radius: 4px 0 0 4px;
border-radius: var(--app-radius-sm) 0 0 4px;
content: '';
}
}
@@ -7,6 +7,7 @@ import { useAppStore } from '@/store/modules/app'
import { useI18n } from '@/hooks/web/useI18n'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -29,7 +30,7 @@ const setCurrentSize = (size: ComponentSize) => {
<template>
<ElDropdown :class="prefixCls" trigger="click" @command="setCurrentSize">
<Icon :size="18" icon="mdi:format-size" :color="color" class="cursor-pointer" />
<AppIcon :size="18" :icon="AppIcons.formatSize" :color="color" class="cursor-pointer" />
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem v-for="item in sizeMap" :key="item" :command="item">
+4 -3
View File
@@ -6,7 +6,7 @@ import { useAppStore } from '@/store/modules/app'
import { computed, unref, defineComponent, watch, ref, onMounted } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ElScrollbar } from 'element-plus'
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { Menu } from '@/components/Menu'
import { useRouter } from 'vue-router'
import { pathResolve } from '@/utils/routerHelper'
@@ -14,6 +14,7 @@ import { cloneDeep } from 'lodash-es'
import { filterMenusPath, initTabMap, tabPathMap } from './helper'
import { useDesign } from '@/hooks/web/useDesign'
import { isUrl } from '@/utils/is'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls, variables } = useDesign()
@@ -180,7 +181,7 @@ export default defineComponent({
}}
>
<div>
<Icon icon={item?.meta?.icon}></Icon>
<AppIcon icon={item?.meta?.icon}></AppIcon>
</div>
{!unref(showTitle) ? undefined : (
<p class="break-words mt-5px px-2px">{t(item.meta?.title)}</p>
@@ -198,7 +199,7 @@ export default defineComponent({
]}
onClick={setCollapse}
>
<Icon icon={unref(collapse) ? 'ep:d-arrow-right' : 'ep:d-arrow-left'}></Icon>
<AppIcon icon={unref(collapse) ? AppIcons.chevronRight : AppIcons.chevronLeft}></AppIcon>
</div>
<Menu
class={[
+3 -2
View File
@@ -20,8 +20,9 @@ import { CSSProperties } from 'vue'
import { getSlot } from '@/utils/tsxHelper'
import TableActions from './components/TableActions.vue'
import { createVideoViewer } from '@/components/VideoPlayer'
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { BaseButton } from '@/components/Button'
import { AppIcons } from '@/constants/icons'
export default defineComponent({
name: 'Table',
@@ -408,7 +409,7 @@ export default defineComponent({
) : videoPreview.includes(field) ? (
<BaseButton
type="primary"
icon={<Icon icon="ep:video-play" />}
icon={<AppIcon icon={AppIcons.videoPlay} />}
onClick={() => {
createVideoViewer({
url
@@ -13,6 +13,7 @@ import { TableColumn } from '../types'
import { PropType, ref, watch, unref } from 'vue'
import { cloneDeep } from 'lodash-es'
import { DEFAULT_FILTER_COLUMN } from '@/constants'
import { AppIcons } from '@/constants/icons'
import { VueDraggable } from 'vue-draggable-plus'
const modelValue = defineModel<boolean>()
@@ -141,17 +142,19 @@ watch(
<div class="flex items-center">
<ElRadioGroup size="small" v-model="item.fixed">
<ElRadioButton label="left">
<Icon icon="ep:arrow-left" />
<AppIcon :icon="AppIcons.chevronLeft" />
</ElRadioButton>
<ElRadioButton :label="undefined">
<Icon icon="ep:close" />
<AppIcon :icon="AppIcons.close" />
</ElRadioButton>
<ElRadioButton label="right">
<Icon icon="ep:arrow-right" />
<AppIcon :icon="AppIcons.chevronRight" />
</ElRadioButton>
</ElRadioGroup>
<div class="ml-12px cursor-move handle"><Icon icon="ep:rank" /></div>
<div class="ml-12px cursor-move handle">
<AppIcon :icon="AppIcons.drag" />
</div>
</div>
</div>
</ElCheckboxGroup>
@@ -3,11 +3,12 @@
<script lang="tsx">
import { defineComponent, unref, computed, PropType, ref } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { useI18n } from '@/hooks/web/useI18n'
import { useAppStore } from '@/store/modules/app'
import { TableColumn } from '../types'
import ColumnSetting from './ColumnSetting.vue'
import { AppIcons } from '@/constants/icons'
export default defineComponent({
name: 'TableActions',
@@ -47,8 +48,8 @@ export default defineComponent({
<>
<div class="text-right h-28px flex items-center justify-end">
<div title="刷新" class="w-30px h-20px flex items-center justify-end" onClick={refresh}>
<Icon
icon="ant-design:sync-outlined"
<AppIcon
icon={AppIcons.refresh}
class="cursor-pointer"
hover-color="var(--el-color-primary)"
/>
@@ -59,8 +60,8 @@ export default defineComponent({
default: () => {
return (
<div title="尺寸" class="w-30px h-20px flex items-center justify-end">
<Icon
icon="ant-design:column-height-outlined"
<AppIcon
icon={AppIcons.viewColumn}
class="cursor-pointer"
hover-color="var(--el-color-primary)"
/>
@@ -92,8 +93,8 @@ export default defineComponent({
class="w-30px h-20px flex items-center justify-end"
onClick={showColumnSetting}
>
<Icon
icon="ant-design:setting-outlined"
<AppIcon
icon={AppIcons.settings}
class="cursor-pointer"
hover-color="var(--el-color-primary)"
/>
+28 -31
View File
@@ -16,6 +16,7 @@ import { ElScrollbar } from 'element-plus'
import { useScrollTo } from '@/hooks/event/useScrollTo'
import { useTagsView } from '@/hooks/web/useTagsView'
import { cloneDeep } from 'lodash-es'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -262,18 +263,14 @@ watch(
</script>
<template>
<div
:id="prefixCls"
:class="prefixCls"
class="flex w-full relative bg-[#fff] dark:bg-[var(--el-bg-color)]"
>
<div :id="prefixCls" :class="prefixCls" class="flex w-full relative bg-[var(--el-bg-color)]">
<span
:class="`${prefixCls}__tool ${prefixCls}__tool--first`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
@click="move(-200)"
>
<Icon
icon="ep:d-arrow-left"
<AppIcon
:icon="AppIcons.chevronLeft"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
/>
@@ -285,7 +282,7 @@ watch(
:ref="itemRefs.set"
:schema="[
{
icon: 'ant-design:sync-outlined',
icon: AppIcons.refresh,
label: t('common.reload'),
disabled: selectedTag?.fullPath !== item.fullPath,
command: () => {
@@ -293,7 +290,7 @@ watch(
}
},
{
icon: 'ant-design:close-outlined',
icon: AppIcons.close,
label: t('common.closeTab'),
disabled: !!visitedViews?.length && selectedTag?.meta.affix,
command: () => {
@@ -302,7 +299,7 @@ watch(
},
{
divided: true,
icon: 'ant-design:vertical-right-outlined',
icon: AppIcons.splitRight,
label: t('common.closeTheLeftTab'),
disabled:
!!visitedViews?.length &&
@@ -313,7 +310,7 @@ watch(
}
},
{
icon: 'ant-design:vertical-left-outlined',
icon: AppIcons.splitLeft,
label: t('common.closeTheRightTab'),
disabled:
!!visitedViews?.length &&
@@ -325,7 +322,7 @@ watch(
},
{
divided: true,
icon: 'ant-design:tag-outlined',
icon: AppIcons.tag,
label: t('common.closeOther'),
disabled: selectedTag?.fullPath !== item.fullPath,
command: () => {
@@ -333,7 +330,7 @@ watch(
}
},
{
icon: 'ant-design:line-outlined',
icon: AppIcons.line,
label: t('common.closeAll'),
command: () => {
closeAllTags()
@@ -358,7 +355,7 @@ watch(
@click="navigate"
class="h-full flex justify-center items-center whitespace-nowrap pl-15px"
>
<Icon
<AppIcon
v-if="
item?.matched &&
item?.matched[1] &&
@@ -370,10 +367,10 @@ watch(
class="mr-5px"
/>
{{ t(item?.meta?.title as string) }}
<Icon
<AppIcon
:class="`${prefixCls}__item--close`"
color="#333"
icon="ant-design:close-outlined"
color="var(--el-text-color-secondary)"
:icon="AppIcons.close"
:size="12"
@click.prevent.stop="closeSelectedTag(item)"
/>
@@ -389,8 +386,8 @@ watch(
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
@click="move(200)"
>
<Icon
icon="ep:d-arrow-right"
<AppIcon
:icon="AppIcons.chevronRight"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
/>
@@ -400,8 +397,8 @@ watch(
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
@click="refreshSelectedTag(selectedTag)"
>
<Icon
icon="ant-design:reload-outlined"
<AppIcon
:icon="AppIcons.refresh"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
/>
@@ -410,14 +407,14 @@ watch(
trigger="click"
:schema="[
{
icon: 'ant-design:sync-outlined',
icon: AppIcons.refresh,
label: t('common.reload'),
command: () => {
refreshSelectedTag(selectedTag)
}
},
{
icon: 'ant-design:close-outlined',
icon: AppIcons.close,
label: t('common.closeTab'),
disabled: !!visitedViews?.length && selectedTag?.meta.affix,
command: () => {
@@ -426,7 +423,7 @@ watch(
},
{
divided: true,
icon: 'ant-design:vertical-right-outlined',
icon: AppIcons.splitRight,
label: t('common.closeTheLeftTab'),
disabled: !!visitedViews?.length && selectedTag?.fullPath === visitedViews[0].fullPath,
command: () => {
@@ -434,7 +431,7 @@ watch(
}
},
{
icon: 'ant-design:vertical-left-outlined',
icon: AppIcons.splitLeft,
label: t('common.closeTheRightTab'),
disabled:
!!visitedViews?.length &&
@@ -445,14 +442,14 @@ watch(
},
{
divided: true,
icon: 'ant-design:tag-outlined',
icon: AppIcons.tag,
label: t('common.closeOther'),
command: () => {
closeOthersTags()
}
},
{
icon: 'ant-design:line-outlined',
icon: AppIcons.line,
label: t('common.closeAll'),
command: () => {
closeAllTags()
@@ -464,8 +461,8 @@ watch(
:class="`${prefixCls}__tool`"
class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer block"
>
<Icon
icon="ant-design:setting-outlined"
<AppIcon
:icon="AppIcons.settings"
color="var(--el-text-color-placeholder)"
:hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
/>
@@ -542,8 +539,8 @@ watch(
&__item.is-active {
color: var(--el-color-white);
background-color: var(--el-color-primary);
border: 1px solid var(--el-color-primary);
background-color: var(--app-primary-strong);
border: 1px solid var(--app-primary-strong);
.@{prefix-cls}__item--close {
:deep(svg) {
color: var(--el-color-white) !important;
+18 -10
View File
@@ -6,6 +6,7 @@ import { useAppStore } from '@/store/modules/app'
import { ElSwitch } from 'element-plus'
import { useIcon } from '@/hooks/web/useIcon'
import { useDesign } from '@/hooks/web/useDesign'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
@@ -13,18 +14,15 @@ const emit = defineEmits(['change'])
const prefixCls = getPrefixCls('theme-switch')
const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
const Sun = useIcon({ icon: AppIcons.lightMode, color: '#fde047' })
const CrescentMoon = useIcon({ icon: 'emojione-monotone:crescent-moon', color: '#fde047' })
const CrescentMoon = useIcon({ icon: AppIcons.darkMode, color: '#fde047' })
const appStore = useAppStore()
// 初始化获取是否是暗黑主题
const isDark = ref(appStore.getIsDark)
// 设置switch的背景颜色
const blackColor = 'var(--el-color-black)'
const themeChange = (val: boolean) => {
appStore.setIsDark(val)
emit('change', val)
@@ -36,9 +34,6 @@ const themeChange = (val: boolean) => {
:class="prefixCls"
v-model="isDark"
inline-prompt
:border-color="blackColor"
:inactive-color="blackColor"
:active-color="blackColor"
:active-icon="Sun"
:inactive-icon="CrescentMoon"
@change="themeChange"
@@ -46,7 +41,20 @@ const themeChange = (val: boolean) => {
</template>
<style lang="less" scoped>
:deep(.el-switch__core .el-switch__inner .is-icon) {
overflow: visible;
.v-theme-switch {
:deep(.el-switch__core .el-switch__inner .is-icon) {
overflow: visible;
}
:deep(.el-switch__core) {
min-width: 44px;
background: var(--el-fill-color-darker);
border-color: var(--el-text-color-secondary);
}
}
.v-theme-switch.is-checked :deep(.el-switch__core) {
background: var(--app-primary-strong) !important;
border-color: var(--app-primary-strong) !important;
}
</style>
+18 -3
View File
@@ -9,6 +9,8 @@ import { ref, computed } from 'vue'
import LockPage from './components/LockPage.vue'
import { useLockStore } from '@/store/modules/lock'
import { useUserStore } from '@/store/modules/user'
import { AppIcon } from '@/components/AppIcon'
import { AppIcons } from '@/constants/icons'
const userStore = useUserStore()
@@ -22,6 +24,12 @@ const prefixCls = getPrefixCls('user-info')
const { t } = useI18n()
const displayName = computed(
() => userStore.getUserInfo?.name || userStore.getUserInfo?.username || '-'
)
const avatarUrl = computed(() => userStore.getUserInfo?.avatar || '')
const loginOut = () => {
userStore.logoutConfirm()
}
@@ -38,12 +46,19 @@ const lockScreen = () => {
<ElDropdown class="custom-hover" :class="prefixCls" trigger="click">
<div class="flex items-center">
<img
src="@/assets/imgs/avatar.jpg"
v-if="avatarUrl"
:src="avatarUrl"
alt=""
class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
class="w-[calc(var(--logo-height)-25px)] h-[calc(var(--logo-height)-25px)] rounded-[50%] object-cover"
/>
<span
v-else
class="w-[calc(var(--logo-height)-25px)] h-[calc(var(--logo-height)-25px)] rounded-[50%] flex items-center justify-center bg-[var(--el-fill-color)]"
>
<AppIcon :icon="AppIcons.person" />
</span>
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">{{
userStore.getUserInfo?.username
displayName
}}</span>
</div>
<template #dropdown>
@@ -12,6 +12,9 @@ import { FormSchema } from '@/components/Form'
import { ElButton } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
import { useLockStore } from '@/store/modules/lock'
import { useUserStore } from '@/store/modules/user'
import { AppIcon } from '@/components/AppIcon'
import { AppIcons } from '@/constants/icons'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('lock-dialog')
@@ -21,6 +24,12 @@ const { required } = useValidator()
const { t } = useI18n()
const lockStore = useLockStore()
const userStore = useUserStore()
const displayName = computed(
() => userStore.getUserInfo?.name || userStore.getUserInfo?.username || '-'
)
const avatarUrl = computed(() => userStore.getUserInfo?.avatar || '')
const props = defineProps({
modelValue: {
@@ -84,8 +93,18 @@ const handleLock = async () => {
:title="dialogTitle"
>
<div class="flex flex-col items-center">
<img src="@/assets/imgs/avatar.jpg" alt="" class="w-70px h-70px rounded-[50%]" />
<span class="text-14px my-10px text-[var(--top-header-text-color)]">Archer</span>
<img
v-if="avatarUrl"
:src="avatarUrl"
alt=""
class="w-70px h-70px rounded-[50%] object-cover"
/>
<span
v-else
class="w-70px h-70px rounded-[50%] flex items-center justify-center bg-[var(--el-fill-color)]"
><AppIcon :icon="AppIcons.person" :size="30"
/></span>
<span class="text-14px my-10px text-[var(--top-header-text-color)]">{{ displayName }}</span>
</div>
<Form :is-col="false" :schema="schema" :rules="rules" @register="formRegister" />
<template #footer>
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script lang="ts" setup>
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { ElInput, ElButton } from 'element-plus'
import { resetRouter } from '@/router'
import { useRouter } from 'vue-router'
@@ -10,11 +10,14 @@ 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 { AppIcon } from '@/components/AppIcon'
import { loginOutApi } from '@/api/login'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { AppIcons } from '@/constants/icons'
import { useUserStore } from '@/store/modules/user'
const tagsViewStore = useTagsViewStore()
const userStore = useUserStore()
const { clear } = useStorage()
@@ -34,6 +37,11 @@ const { hour, month, minute, meridiem, year, day, week } = useNow(true)
const { t } = useI18n()
const displayName = computed(
() => userStore.getUserInfo?.name || userStore.getUserInfo?.username || '-'
)
const avatarUrl = computed(() => userStore.getUserInfo?.avatar || '')
// 解锁
async function unLock() {
if (!password.value) {
@@ -77,7 +85,7 @@ function handleShowForm(show = false) {
@click="handleShowForm(false)"
v-show="showDate"
>
<Icon icon="ep:lock" />
<AppIcon :icon="AppIcons.lock" />
<span>{{ t('lock.unlock') }}</span>
</div>
@@ -96,8 +104,20 @@ function handleShowForm(show = false) {
<div :class="`${prefixCls}-entry`" v-show="!showDate">
<div :class="`${prefixCls}-entry-content`">
<div class="flex flex-col items-center">
<img src="@/assets/imgs/avatar.jpg" alt="" class="w-70px h-70px rounded-[50%]" />
<span class="text-14px my-10px text-[var(--logo-title-text-color)]">Archer</span>
<img
v-if="avatarUrl"
:src="avatarUrl"
alt=""
class="w-70px h-70px rounded-[50%] object-cover"
/>
<span
v-else
class="w-70px h-70px rounded-[50%] flex items-center justify-center bg-[var(--el-fill-color)]"
><AppIcon :icon="AppIcons.person" :size="30"
/></span>
<span class="text-14px my-10px text-[var(--logo-title-text-color)]">{{
displayName
}}</span>
</div>
<ElInput
type="password"
@@ -4,7 +4,8 @@
import { VideoPlayer } from '@/components/VideoPlayer'
import { ElOverlay } from 'element-plus'
import { ref, nextTick } from 'vue'
import { Icon } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import { AppIcons } from '@/constants/icons'
const props = defineProps({
show: {
@@ -43,7 +44,7 @@ const close = async () => {
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" />
<AppIcon :icon="AppIcons.close" :size="24" />
</div>
<VideoPlayer :url="url" :poster="poster" />
</div>
+2
View File
@@ -2,9 +2,11 @@
import type { App } from 'vue'
import { Icon } from './Icon'
import { AppIcon } from './AppIcon'
import { BaseButton } from './Button'
export const setupGlobCom = (app: App<Element>): void => {
app.component('Icon', Icon)
app.component('AppIcon', AppIcon)
app.component('BaseButton', BaseButton)
}
+103
View File
@@ -0,0 +1,103 @@
// Author: fruit.com.cn
/**
* 后台统一图标语义表。所有值都来自 material-symbols-rounded。
*/
export const AppIcons = {
menu: 'material-symbols:menu-rounded',
menuOpen: 'material-symbols:menu-open-rounded',
dashboard: 'material-symbols:dashboard-rounded',
content: 'material-symbols:video-library-rounded',
settings: 'material-symbols:settings-rounded',
group: 'material-symbols:group-rounded',
movieEdit: 'material-symbols:movie-edit-rounded',
policy: 'material-symbols:policy-rounded',
payments: 'material-symbols:payments-rounded',
monitoring: 'material-symbols:monitoring-rounded',
videoCameraFront: 'material-symbols:video-camera-front-rounded',
campaign: 'material-symbols:campaign-rounded',
fullscreen: 'material-symbols:fullscreen-rounded',
fullscreenExit: 'material-symbols:fullscreen-exit-rounded',
formatSize: 'material-symbols:format-size-rounded',
language: 'material-symbols:translate-rounded',
darkMode: 'material-symbols:dark-mode-rounded',
lightMode: 'material-symbols:light-mode-rounded',
refresh: 'material-symbols:refresh-rounded',
restart: 'material-symbols:restart-alt-rounded',
close: 'material-symbols:close-rounded',
chevronLeft: 'material-symbols:chevron-left-rounded',
chevronRight: 'material-symbols:chevron-right-rounded',
chevronDown: 'material-symbols:keyboard-arrow-down-rounded',
chevronUp: 'material-symbols:keyboard-arrow-up-rounded',
search: 'material-symbols:search-rounded',
viewColumn: 'material-symbols:combine-columns-rounded',
help: 'material-symbols:help-rounded',
add: 'material-symbols:add-rounded',
edit: 'material-symbols:edit-rounded',
delete: 'material-symbols:delete-rounded',
upload: 'material-symbols:upload-rounded',
download: 'material-symbols:download-rounded',
visibility: 'material-symbols:visibility-rounded',
arrowUp: 'material-symbols:arrow-upward-rounded',
arrowDown: 'material-symbols:arrow-downward-rounded',
arrowBack: 'material-symbols:arrow-back-rounded',
arrowForward: 'material-symbols:arrow-forward-rounded',
check: 'material-symbols:check-rounded',
checkCircle: 'material-symbols:check-circle-rounded',
cancel: 'material-symbols:cancel-rounded',
lock: 'material-symbols:lock-person-rounded',
logout: 'material-symbols:logout-rounded',
person: 'material-symbols:person-rounded',
clock: 'material-symbols:schedule-rounded',
videoPlay: 'material-symbols:smart-display-rounded',
drag: 'material-symbols:drag-handle-rounded',
tag: 'material-symbols:label-rounded',
line: 'material-symbols:horizontal-rule-rounded',
splitLeft: 'material-symbols:split-scene-left-rounded',
splitRight: 'material-symbols:split-scene-right-rounded',
warning: 'material-symbols:warning-rounded',
error: 'material-symbols:error-rounded',
info: 'material-symbols:info-rounded'
} as const
/** 菜单管理提供的合法图标候选。 */
export const MenuIconOptions = [
AppIcons.dashboard,
AppIcons.content,
AppIcons.settings,
AppIcons.group,
AppIcons.movieEdit,
AppIcons.policy,
AppIcons.payments,
AppIcons.monitoring,
AppIcons.videoCameraFront,
AppIcons.campaign
] as const
const legacyIconAliases: Record<string, string> = {
Document: AppIcons.content,
DataAnalysis: AppIcons.monitoring,
Picture: AppIcons.content,
User: AppIcons.group,
Setting: AppIcons.settings,
VideoCamera: AppIcons.content,
Menu: AppIcons.menu,
Grid: AppIcons.dashboard,
'ep:setting': AppIcons.settings,
'ep:user': AppIcons.group,
'ep:user-filled': AppIcons.group,
'ep:chat-dot-round': AppIcons.policy,
'ep:coin': AppIcons.payments,
'ep:data-line': AppIcons.monitoring,
'ep:film': AppIcons.videoCameraFront,
'ep:bell': AppIcons.campaign,
'ep:video-camera': AppIcons.content,
'ant-design:dashboard-filled': AppIcons.dashboard
}
/** 兼容历史短名与旧 Iconify 名,统一返回 Material Symbols Rounded。 */
export function normalizeAppIcon(icon?: string): string {
if (!icon) return AppIcons.menu
if (icon.startsWith('material-symbols:') || icon.startsWith('svg-icon:')) return icon
return legacyIconAliases[icon] || AppIcons.menu
}
+3 -2
View File
@@ -2,8 +2,9 @@
import { h } from 'vue'
import type { VNode } from 'vue'
import { Icon, IconTypes } from '@/components/Icon'
import { AppIcon } from '@/components/AppIcon'
import type { IconTypes } from '@/components/Icon'
export const useIcon = (props: IconTypes): VNode => {
return h(Icon, props)
return h(AppIcon, props)
}
+23 -3
View File
@@ -56,14 +56,20 @@ export default defineComponent({
) : undefined}
<div class="h-full flex items-center">
{screenfull.value ? (
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
<Screenfull
class="<md:hidden custom-hover"
color="var(--top-header-text-color)"
></Screenfull>
) : undefined}
{size.value ? (
<SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown>
<SizeDropdown
class="<md:hidden custom-hover"
color="var(--top-header-text-color)"
></SizeDropdown>
) : undefined}
{locale.value ? (
<LocaleDropdown
class="custom-hover"
class="<sm:hidden custom-hover"
color="var(--top-header-text-color)"
></LocaleDropdown>
) : undefined}
@@ -79,6 +85,20 @@ export default defineComponent({
@prefix-cls: ~'@{namespace}-tool-header';
.@{prefix-cls} {
min-width: 0;
gap: 8px;
transition: left var(--transition-time-02);
:deep(.el-dropdown),
:deep(.v-user-info) {
flex-shrink: 0;
}
}
@media (max-width: 768px) {
.@{prefix-cls} {
padding-right: 8px;
padding-left: 8px;
}
}
</style>
+2
View File
@@ -86,6 +86,7 @@ export default {
cutMenu: 'Cut Menu',
copy: 'Copy',
clearAndReset: 'Clear cache and reset',
clearCacheMessage: 'Clear local cache and reload the admin console?',
copySuccess: 'Copy success',
copyFailed: 'Copy failed',
footer: 'Footer',
@@ -103,6 +104,7 @@ export default {
},
login: {
welcome: 'Short-drama workspace',
consoleLabel: 'Content Operations',
message: 'Manage dramas, episodes, publishing, and access.',
hint: 'Sign in with an admin account',
noAccount: 'No account yet?',
+2
View File
@@ -86,6 +86,7 @@ export default {
cutMenu: '切割菜单',
copy: '拷贝',
clearAndReset: '清除缓存并且重置',
clearCacheMessage: '确认清除本地缓存并重新加载后台吗?',
copySuccess: '拷贝成功',
copyFailed: '拷贝失败',
footer: '页脚',
@@ -103,6 +104,7 @@ export default {
},
login: {
welcome: '短剧内容管理',
consoleLabel: '内容运营后台',
message: '管理短剧、剧集、发布状态和后台权限。',
hint: '使用管理员账号进入工作台',
noAccount: '还没有账号?',
+4 -4
View File
@@ -75,9 +75,9 @@ export const useAppStore = defineStore('app', {
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: '#0f2438',
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: 'var(--el-color-primary)',
leftMenuBgActiveColor: 'var(--app-primary-strong)',
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
leftMenuCollapseBgActiveColor: 'var(--app-primary-strong)',
// 左侧菜单字体颜色
leftMenuTextColor: '#bfcbd9',
// 左侧菜单选中字体颜色
@@ -288,11 +288,11 @@ export const useAppStore = defineStore('app', {
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
? 'var(--app-primary-strong)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
? 'var(--app-primary-strong)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单字体颜色
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
+14 -2
View File
@@ -27,7 +27,7 @@ export const useUserStore = defineStore('user', {
token: '',
roleRouters: undefined,
// 记住我
rememberMe: true,
rememberMe: false,
loginInfo: undefined
}
},
@@ -87,6 +87,8 @@ export const useUserStore = defineStore('user', {
this.setToken('')
this.setUserInfo(undefined)
this.setRoleRouters([])
this.setLoginInfo(undefined)
this.setRememberMe(false)
tagsViewStore.delAllViews()
// 菜单/路由一起清:否则换账号后 permissionStore 里还是上一个账号的路由表
permissionStore.clearRoutes()
@@ -102,7 +104,17 @@ export const useUserStore = defineStore('user', {
this.loginInfo = loginInfo
}
},
persist: true
persist: {
afterRestore: (context) => {
const loginInfo = context.store.loginInfo as
(UserLoginType & { password?: string }) | undefined
if (loginInfo?.password !== undefined) {
context.store.setLoginInfo(
loginInfo.username ? { username: loginInfo.username } : undefined
)
}
}
}
})
export const useUserStoreWithOut = () => {
+3 -3
View File
@@ -4,7 +4,7 @@
height: 90px;
object-fit: cover;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #f0f2f4 !important;
}
@@ -13,7 +13,7 @@
place-content: center;
width: 160px;
height: 90px;
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #f0f2f4 !important;
color: #7e8791;
font-size: 12px;
@@ -37,7 +37,7 @@
flex: 0 0 200px;
object-fit: contain;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
border-radius: var(--app-radius-md);
background: #f0f2f4 !important;
}
+1 -1
View File
@@ -116,7 +116,7 @@ section .console-hint.el-alert {
min-height: 74px;
padding: 12px 14px;
border: 1px solid #e6e8eb;
border-radius: 6px;
border-radius: var(--app-radius-md);
background: #fff;
}
+4 -4
View File
@@ -51,7 +51,7 @@
width: 34px;
height: 46px;
overflow: hidden;
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #eef0f5;
color: #9a97a8;
font-size: 14px;
@@ -193,7 +193,7 @@
height: 76px;
overflow: hidden;
border: 1px solid #e6e8eb;
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #fafbfc;
color: #9a97a8;
font-size: 12px;
@@ -205,7 +205,7 @@
width: 48px;
height: 30px;
overflow: hidden;
border-radius: 3px;
border-radius: var(--app-radius-sm);
background: #eef0f5;
color: #9a97a8;
font-size: 12px;
@@ -226,7 +226,7 @@
padding: 10px 14px;
margin-bottom: 14px;
border: 1px solid #f0e6cf;
border-radius: 6px;
border-radius: var(--app-radius-md);
background: #fdf9ef;
}
+32 -9
View File
@@ -3,7 +3,6 @@ html.dark .panel-heading h2,
html.dark .episode-toolbar h2,
html.dark .stat-value,
html.dark .drama-cell b,
html.dark .brand b,
html.dark .role-card,
html.dark .cover-card-info b {
color: var(--el-text-color-primary);
@@ -22,19 +21,13 @@ html.dark .table-result-count {
html.dark .stat-card,
html.dark .panel,
html.dark .role-card,
html.dark .topbar,
html.dark .sidebar {
html.dark .role-card {
background: var(--el-bg-color-overlay);
border-color: var(--el-border-color-light);
}
html.dark .stat-icon,
html.dark .role-icon,
html.dark .help-icon,
html.dark .workspace-avatar,
html.dark .profile-avatar,
html.dark .top-avatar {
html.dark .role-icon {
background: rgb(64 158 255 / 16%) !important;
color: var(--el-color-primary) !important;
}
@@ -128,3 +121,33 @@ html.dark .el-tag.status-badge.is-offline {
border-color: rgba(245, 108, 108, 0.45) !important;
color: #f3a6a6 !important;
}
.v-tool-header {
color: var(--top-header-text-color);
}
.v-tags-view {
background: var(--el-bg-color);
border-color: var(--el-border-color-lighter);
}
.v-tags-view__item--close {
color: var(--el-text-color-secondary) !important;
}
html.dark .v-tool-header,
html.dark .v-tags-view {
background: var(--el-bg-color-overlay);
border-color: var(--el-border-color);
}
html.dark .v-tags-view__item {
color: var(--el-text-color-regular);
}
html.dark #v-menu .el-menu-item.is-active,
html.dark #v-menu .el-sub-menu.is-active > .el-sub-menu__title,
html.dark .v-tags-view__item.is-active {
background-color: var(--app-primary-strong) !important;
border-color: var(--app-primary-strong) !important;
}
+11 -11
View File
@@ -46,7 +46,7 @@
padding: 0 10px !important;
font-size: 13px !important;
font-weight: 500 !important;
border-radius: 4px;
border-radius: var(--app-radius-sm);
}
/* Element Plus 默认给相邻按钮加 12px 外边距,会和这里的 gap 叠加导致换行 */
@@ -64,7 +64,7 @@
width: 44px;
height: 56px;
flex: 0 0 44px;
border-radius: 4px;
border-radius: var(--app-radius-sm);
object-fit: cover;
}
.drama-cell .drama-cover {
@@ -109,7 +109,7 @@
flex: 0 0 84px;
object-fit: cover;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
border-radius: var(--app-radius-md);
background: #f0f2f4 !important;
}
.dark .form-cover-preview {
@@ -134,7 +134,7 @@
flex: 0 0 200px;
object-fit: contain;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
border-radius: var(--app-radius-md);
background: #000 !important;
}
@@ -144,7 +144,7 @@
width: 100%;
max-height: 60vh;
object-fit: contain;
border-radius: 8px;
border-radius: var(--app-radius-md);
background: #000 !important;
}
@@ -156,7 +156,7 @@
object-fit: contain;
cursor: pointer;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #000 !important;
}
@@ -231,10 +231,10 @@
color: #c0c6cf;
}
.table-actions .el-button--primary {
--el-button-bg-color: #409eff;
--el-button-border-color: #409eff;
--el-button-hover-bg-color: #79bbff;
--el-button-hover-border-color: #79bbff;
--el-button-bg-color: var(--el-color-primary);
--el-button-border-color: var(--el-color-primary);
--el-button-hover-bg-color: var(--el-color-primary-light-3);
--el-button-hover-border-color: var(--el-color-primary-light-3);
}
.table-actions .el-button--success {
--el-button-bg-color: #4d866a;
@@ -259,7 +259,7 @@
font-size: 13px !important;
--el-pagination-font-size: 13px;
--el-pagination-button-bg-color: #f1f3f6;
--el-pagination-hover-color: #409eff;
--el-pagination-hover-color: var(--el-color-primary);
--el-pagination-button-color: #4c5561;
gap: 5px;
}
+1 -1
View File
@@ -24,7 +24,7 @@
word-break: break-all;
background: #f6f7f9 !important;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
border-radius: var(--app-radius-md);
}
.dark .log-json {
+89 -374
View File
@@ -1,316 +1,12 @@
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=Noto+Sans+SC:wght@400;500;600;700&display=swap');
:root {
font-family: 'DM Sans', 'Noto Sans SC', sans-serif;
color: var(--el-text-color-primary);
background: var(--el-bg-color-page);
font-synthesis: none;
text-rendering: optimizeLegibility;
font-optical-sizing: auto;
font-weight: 400;
--purple: #409eff;
--muted: #9695a8;
--line: #eeedf3;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 980px;
min-width: 0;
min-height: 100vh;
}
.app-shell {
min-height: 100vh;
}
.sidebar {
width: 250px;
position: fixed;
inset: 0 auto 0 0;
background: #fff;
border-right: 1px solid #eeedf4;
padding: 27px 17px 16px;
display: flex;
flex-direction: column;
z-index: 4;
}
.brand {
display: flex;
align-items: center;
gap: 11px;
padding: 0 9px 28px;
}
.brand-mark {
width: 32px;
height: 32px;
border-radius: 10px;
background: #409eff;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
transform: rotate(-8deg);
}
.brand-mark span {
width: 4px;
background: white;
border-radius: 4px;
transform: skew(-15deg);
}
.brand-mark span:nth-child(1) {
height: 11px;
opacity: 0.72;
}
.brand-mark span:nth-child(2) {
height: 18px;
}
.brand-mark span:nth-child(3) {
height: 13px;
opacity: 0.85;
}
.brand b {
display: block;
font:
800 18px Manrope,
sans-serif;
letter-spacing: -0.8px;
color: #242139;
}
.brand small {
font-size: 8px;
letter-spacing: 1.5px;
color: #a3a1b1;
display: block;
margin-top: 1px;
}
.workspace {
height: 58px;
border: 1px solid #efedf4;
border-radius: 10px;
display: flex;
align-items: center;
padding: 0 10px;
margin-bottom: 27px;
gap: 10px;
}
.workspace-avatar,
.profile-avatar,
.top-avatar {
display: grid;
place-items: center;
background: #ecf5ff;
color: #409eff;
font-weight: 700;
}
.workspace-avatar {
width: 32px;
height: 32px;
border-radius: 8px;
}
.workspace-copy {
flex: 1;
}
.workspace-copy b,
.workspace-copy small {
display: block;
}
.workspace-copy b {
font-size: 11px;
}
.workspace-copy small {
color: #a09eae;
font-size: 10px;
margin-top: 3px;
}
.workspace > .el-icon {
color: #aaa8b7;
font-size: 13px;
}
.nav-label {
padding: 0 11px 10px;
color: #aaa8b7;
font-size: 10px;
letter-spacing: 0.5px;
}
.side-menu {
border: 0 !important;
}
.side-menu .el-menu-item {
height: 43px;
line-height: 43px;
border-radius: 8px;
margin: 2px 0;
color: #777589;
font-size: 12px;
padding: 0 11px !important;
}
.side-menu .el-menu-item .el-icon {
font-size: 16px;
margin-right: 12px;
}
.side-menu .el-menu-item:hover {
background: #ecf5ff;
color: #409eff;
}
.side-menu .el-menu-item.is-active {
background: #ecf5ff;
color: #409eff;
font-weight: 600;
}
.menu-count {
margin-left: auto;
border-radius: 10px;
background: #ecf5ff;
color: #79bbff;
padding: 2px 7px;
font-size: 10px;
}
.access-label {
margin-top: 27px;
}
.sidebar-bottom {
margin-top: auto;
}
.help-card {
background: #ecf5ff;
border: 1px solid #ecf5ff;
border-radius: 10px;
padding: 13px 13px 14px;
margin: 0 2px 17px;
}
.help-icon {
width: 23px;
height: 23px;
display: grid;
place-items: center;
background: #d9ecff;
border-radius: 7px;
color: #409eff;
margin-bottom: 9px;
font-size: 13px;
}
.help-card b {
font-size: 11px;
}
.help-card p {
font-size: 10px;
line-height: 1.6;
color: #9290a2;
margin: 5px 0 9px;
}
.help-card a {
font-size: 10px;
color: #409eff;
font-weight: 600;
}
.help-card a span {
margin-left: 4px;
}
.user-profile {
border-top: 1px solid #f0eff4;
padding: 15px 4px 0;
display: flex;
align-items: center;
gap: 9px;
}
.profile-avatar {
width: 33px;
height: 33px;
border-radius: 50%;
font-size: 12px;
}
.profile-info {
flex: 1;
}
.profile-info b,
.profile-info small {
display: block;
}
.profile-info b {
font-size: 11px;
}
.profile-info small {
font-size: 10px;
color: #a09eae;
margin-top: 3px;
}
.user-profile > .el-icon {
color: #aaa8b7;
transform: rotate(90deg);
}
.main-area {
margin-left: 250px;
min-height: 100vh;
}
.topbar {
height: 64px;
background: rgba(255, 255, 255, 0.92);
border-bottom: 1px solid #eeedf4;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 39px;
position: sticky;
top: 0;
z-index: 3;
}
.breadcrumb {
font-size: 11px;
color: #aaa8b6;
}
.breadcrumb .slash {
padding: 0 10px;
color: #d4d2dd;
}
.breadcrumb b {
font-weight: 600;
color: #555367;
}
.top-actions {
display: flex;
align-items: center;
gap: 18px;
}
.environment {
border: 1px solid #eeedf2;
border-radius: 15px;
padding: 6px 10px;
font-size: 10px;
color: #777589;
}
.environment i {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: #62c491;
margin-right: 6px;
}
.icon-button {
position: relative;
color: #777589;
}
.notification-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: #ef7d85;
position: absolute;
right: 6px;
top: 5px;
border: 1px solid white;
}
.top-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
font-size: 11px;
}
.page-content {
max-width: 1440px;
margin: 0 auto;
padding: 35px 40px 46px;
}
.page-heading {
.page-heading {
display: flex;
justify-content: space-between;
align-items: center;
@@ -323,7 +19,7 @@ body {
font-weight: 600;
}
.eyebrow span {
color: #79bbff;
color: var(--el-color-primary-light-3);
margin-left: 5px;
}
.page-heading h1 {
@@ -344,17 +40,17 @@ body {
font-size: 20px;
}
.primary-button {
background: #409eff !important;
border-color: #409eff !important;
border-radius: 7px !important;
background: var(--el-color-primary) !important;
border-color: var(--el-color-primary) !important;
border-radius: var(--app-radius-md) !important;
font-size: 11px !important;
font-weight: 600 !important;
height: 37px !important;
padding: 0 15px !important;
box-shadow: 0 4px 10px #409eff22;
box-shadow: 0 4px 10px color-mix(in srgb, var(--el-color-primary) 13%, transparent);
}
.primary-button:hover {
background: #337ecc !important;
background: var(--el-color-primary-dark-2) !important;
}
.primary-button .el-icon {
margin-right: 6px;
@@ -369,7 +65,7 @@ body {
.panel {
background: #fff;
border: 1px solid #efedf3;
border-radius: 10px;
border-radius: var(--app-radius-md);
}
.stat-card {
padding: 16px 17px 13px;
@@ -387,12 +83,12 @@ body {
height: 27px;
display: grid;
place-items: center;
border-radius: 8px;
border-radius: var(--app-radius-md);
font-size: 13px;
}
.lilac {
background: #ecf5ff;
color: #409eff;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.blush {
background: #fff0f2;
@@ -435,7 +131,7 @@ body {
color: #49b786;
background: #eef9f4;
padding: 3px 5px;
border-radius: 4px;
border-radius: var(--app-radius-sm);
}
.neutral {
color: #aaa8b6;
@@ -565,9 +261,9 @@ body {
.muted-count {
display: inline-grid;
place-items: center;
border-radius: 10px;
background: #ecf5ff;
color: #409eff;
border-radius: var(--app-radius-md);
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font:
600 9px 'DM Sans',
sans-serif;
@@ -578,7 +274,7 @@ body {
}
.view-all {
font-size: 10px !important;
color: #409eff !important;
color: var(--el-color-primary) !important;
}
.view-all span {
margin-left: 5px;
@@ -599,7 +295,7 @@ body {
.review-art {
width: 34px;
height: 37px;
border-radius: 6px;
border-radius: var(--app-radius-md);
display: grid;
place-items: center;
font-size: 17px;
@@ -608,7 +304,7 @@ body {
background: #fff0ed;
}
.art-two {
background: #ecf5ff;
background: var(--el-color-primary-light-9);
}
.art-three {
background: #eaf6f7;
@@ -661,7 +357,7 @@ body {
padding: 10px 11px;
}
.drama-table th:first-child {
border-radius: 5px 0 0 5px;
border-radius: var(--app-radius-sm) 0 0 5px;
padding-left: 12px;
}
.drama-table th:last-child {
@@ -689,7 +385,7 @@ body {
.drama-cover {
width: 31px;
height: 38px;
border-radius: 4px;
border-radius: var(--app-radius-sm);
display: grid;
place-items: center;
font-size: 16px;
@@ -700,7 +396,7 @@ body {
background: linear-gradient(145deg, #f8d6d7, #ffece4);
}
.purple {
background: linear-gradient(145deg, #d9ecff, #ecf5ff);
background: linear-gradient(145deg, var(--el-color-primary-light-8), var(--el-color-primary-light-9));
}
.peach {
background: linear-gradient(145deg, #ffd6bd, #fff0d7);
@@ -713,7 +409,7 @@ body {
}
.row-action {
font-size: 9px !important;
color: #409eff !important;
color: var(--el-color-primary) !important;
}
.inner-heading {
margin-bottom: 24px;
@@ -744,11 +440,11 @@ body {
}
.library-toolbar .el-tabs__item.is-active,
.cover-toolbar .el-tabs__item.is-active {
color: #409eff;
color: var(--el-color-primary);
}
.library-toolbar .el-tabs__active-bar,
.cover-toolbar .el-tabs__active-bar {
background: #409eff;
background: var(--el-color-primary);
}
.toolbar-filters {
display: flex;
@@ -795,7 +491,7 @@ body {
}
.pagination .el-pagination {
--el-pagination-button-bg-color: #fff;
--el-pagination-hover-color: #409eff;
--el-pagination-hover-color: var(--el-color-primary);
}
.pagination .el-pager li {
font-size: 10px;
@@ -818,7 +514,7 @@ body {
width: 48px;
height: 58px;
font-size: 22px;
border-radius: 6px;
border-radius: var(--app-radius-md);
}
.episode-context-copy {
margin-left: 12px;
@@ -854,7 +550,7 @@ body {
padding: 20px 0 14px;
}
.secondary-button {
border-radius: 6px !important;
border-radius: var(--app-radius-md) !important;
border-color: #eae8f0 !important;
color: #6e6c80 !important;
font-size: 10px !important;
@@ -866,7 +562,7 @@ body {
.episode-row {
height: 69px;
border: 1px solid #efedf4;
border-radius: 7px;
border-radius: var(--app-radius-md);
display: flex;
align-items: center;
gap: 12px;
@@ -881,7 +577,7 @@ body {
.episode-thumb {
width: 62px;
height: 45px;
border-radius: 5px;
border-radius: var(--app-radius-sm);
background: linear-gradient(135deg, #29273b, #55516e);
display: grid;
place-items: center;
@@ -946,7 +642,7 @@ body {
.add-cover {
background: white;
border: 1px solid #eeedf3;
border-radius: 9px;
border-radius: var(--app-radius-md);
overflow: hidden;
}
.poster-art {
@@ -1023,7 +719,7 @@ body {
border: 1px solid #ffffff8a;
padding: 4px 6px;
font-size: 8px;
border-radius: 3px;
border-radius: var(--app-radius-sm);
}
.cover-card-info {
padding: 10px;
@@ -1063,8 +759,8 @@ body {
border-radius: 50%;
display: grid;
place-items: center;
background: #ecf5ff;
color: #409eff;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-size: 17px;
margin-bottom: 11px;
}
@@ -1096,7 +792,7 @@ body {
width: 100%;
border: 1px solid #efedf4;
background: #fff;
border-radius: 8px;
border-radius: var(--app-radius-md);
display: flex;
align-items: center;
text-align: left;
@@ -1106,10 +802,10 @@ body {
font: inherit;
}
.role-card:hover {
border-color: #c6e2ff;
border-color: var(--el-color-primary-light-7);
}
.role-active {
border-color: #a0cfff;
border-color: var(--el-color-primary-light-5);
background: #f5faff;
}
.role-icon {
@@ -1117,7 +813,7 @@ body {
height: 34px;
display: grid;
place-items: center;
border-radius: 8px;
border-radius: var(--app-radius-md);
font-size: 15px;
}
.role-icon.red {
@@ -1125,12 +821,12 @@ body {
color: #e47b79;
}
.role-icon.purple {
background: #ecf5ff;
color: #409eff;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.role-icon.blue {
background: #edf5ff;
color: #409eff;
color: var(--el-color-primary);
}
.role-icon.green {
background: #eaf8f0;
@@ -1165,9 +861,9 @@ body {
}
.role-badge {
font-size: 8px;
color: #409eff;
background: #ecf5ff;
border-radius: 10px;
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
border-radius: var(--app-radius-md);
padding: 4px 7px;
vertical-align: 2px;
margin-left: 5px;
@@ -1193,7 +889,7 @@ body {
font-size: 12px;
vertical-align: middle;
margin-right: 5px;
color: #409eff;
color: var(--el-color-primary);
}
.permission-group .el-checkbox {
height: auto;
@@ -1204,13 +900,13 @@ body {
color: #858397;
}
.permission-group .el-checkbox__input.is-checked .el-checkbox__inner {
background: #409eff;
border-color: #409eff;
background: var(--el-color-primary);
border-color: var(--el-color-primary);
}
.permission-note {
padding: 10px;
background: #f8f7fc;
border-radius: 6px;
border-radius: var(--app-radius-md);
font-size: 8px;
color: #9997a8;
display: flex;
@@ -1218,7 +914,7 @@ body {
gap: 7px;
}
.permission-note .el-icon {
color: #79bbff;
color: var(--el-color-primary-light-3);
}
.placeholder-panel {
text-align: center;
@@ -1229,10 +925,10 @@ body {
place-items: center;
width: 45px;
height: 45px;
border-radius: 12px;
border-radius: var(--app-radius-lg);
margin: auto;
background: #ecf5ff;
color: #409eff;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-size: 20px;
}
.placeholder-panel h2 {
@@ -1244,7 +940,7 @@ body {
color: #a09eae;
}
.el-dialog {
border-radius: 11px !important;
border-radius: var(--app-radius-md) !important;
}
.el-dialog__header {
padding: 20px 22px 12px !important;
@@ -1296,13 +992,13 @@ body {
.upload-area .el-upload-dragger {
height: 112px;
padding: 14px;
border-radius: 7px;
border-radius: var(--app-radius-md);
border-color: #e8e5ef;
background: #fcfbfe;
}
.upload-icon {
font-size: 20px;
color: #409eff;
color: var(--el-color-primary);
margin-bottom: 6px;
}
.upload-area .el-upload__text {
@@ -1311,7 +1007,7 @@ body {
}
.upload-area .el-upload__text em {
font-style: normal;
color: #409eff;
color: var(--el-color-primary);
}
.upload-hint {
font-size: 8px;
@@ -1325,10 +1021,6 @@ body {
width: 100%;
}
@media (min-width: 1500px) {
.page-content {
padding-left: 48px;
padding-right: 48px;
}
.stats-grid {
gap: 18px;
}
@@ -1341,17 +1033,15 @@ body {
}
}
@media (max-width: 1200px) {
.sidebar {
width: 220px;
.page-heading {
gap: 14px;
}
.main-area {
margin-left: 220px;
}
.page-content {
padding: 30px 26px;
}
.topbar {
padding: 0 26px;
.library-toolbar,
.admin-user-toolbar,
.app-user-toolbar,
.console-toolbar {
flex-wrap: wrap;
gap: 10px;
}
.stats-grid {
gap: 10px;
@@ -1367,3 +1057,28 @@ body {
grid-template-columns: repeat(4, minmax(130px, 1fr));
}
}
@media (max-width: 768px) {
.page-heading {
flex-direction: column;
align-items: stretch;
}
.page-heading h1 {
font-size: 20px;
}
.panel {
padding: 14px;
}
.library-toolbar > .el-input,
.library-toolbar > .el-select,
.admin-user-toolbar > .el-input,
.admin-user-toolbar > .el-select,
.app-user-toolbar > .el-input,
.app-user-toolbar > .el-select {
width: 100%;
}
.stats-grid,
.dashboard-grid {
grid-template-columns: 1fr;
}
}
+48 -112
View File
@@ -1,5 +1,18 @@
:root {
font-family: Arial, 'Noto Sans SC', sans-serif;
--font-display: 'Manrope', 'Noto Sans SC', sans-serif;
--font-body: 'DM Sans', 'Noto Sans SC', sans-serif;
--app-radius-sm: 4px;
--app-radius-md: 6px;
--app-radius-lg: 12px;
--app-brand: #c4322a;
--app-brand-pressed: #8e221c;
--app-brand-soft: #ff8a72;
--app-primary-strong: color-mix(in srgb, var(--el-color-primary) 72%, #000 28%);
--brand-mark-size: 38px;
--brand-mark-radius: var(--app-radius-lg);
--el-border-radius-base: var(--app-radius-md);
--el-border-radius-small: var(--app-radius-sm);
font-family: var(--font-body);
color: var(--el-text-color-primary);
background: var(--el-bg-color-page);
--el-color-primary: #409eff;
@@ -17,83 +30,6 @@ body {
background: var(--el-bg-color-page);
color: var(--el-text-color-primary);
}
.sidebar {
background: #fff;
border-right-color: #e7e9ec;
padding-top: 22px;
}
.brand {
padding-bottom: 23px;
}
.brand-mark {
background: #409eff;
border-radius: 7px;
transform: none;
}
.brand b {
font:
700 16px Arial,
sans-serif;
letter-spacing: -0.4px;
color: #29313b;
}
.brand small {
letter-spacing: 0.9px;
}
.workspace {
border-radius: 5px;
border-color: #e5e7eb;
margin-bottom: 22px;
}
.workspace-avatar,
.profile-avatar,
.top-avatar {
background: #ecf5ff;
color: #409eff;
border-radius: 5px;
}
.nav-label {
color: #9298a1;
font-size: 10px;
}
.side-menu .el-menu-item {
border-radius: 5px;
color: #616873;
}
.side-menu .el-menu-item:hover {
background: #ecf5ff;
color: #409eff;
}
.side-menu .el-menu-item.is-active {
background: #ecf5ff;
color: #409eff;
}
.menu-count {
background: #eef1f4;
color: #747d88;
}
.help-card {
background: #f7f8fa;
border-color: #eceef0;
border-radius: 5px;
}
.help-icon {
background: #ecf5ff;
color: #409eff;
border-radius: 5px;
}
.help-card a {
color: #409eff;
}
.topbar {
background: #fff;
}
.environment {
border-radius: 4px;
}
.page-content {
padding-top: 30px;
}
.eyebrow {
letter-spacing: 0.7px;
color: #9298a1;
@@ -110,14 +46,14 @@ body {
color: #818791;
}
.primary-button {
background: #409eff !important;
border-color: #409eff !important;
border-radius: 5px !important;
background: var(--el-color-primary) !important;
border-color: var(--el-color-primary) !important;
border-radius: var(--app-radius-sm) !important;
box-shadow: none !important;
}
.primary-button:hover {
background: #79bbff !important;
border-color: #79bbff !important;
background: var(--el-color-primary-light-3) !important;
border-color: var(--el-color-primary-light-3) !important;
}
.stats-grid {
gap: 12px;
@@ -125,7 +61,7 @@ body {
.stat-card,
.panel {
border-color: #e6e8eb;
border-radius: 6px;
border-radius: var(--app-radius-md);
box-shadow: none;
}
.stat-card {
@@ -136,12 +72,12 @@ body {
color: #727983;
}
.stat-icon {
border-radius: 5px;
border-radius: var(--app-radius-sm);
background: #ecf5ff !important;
color: #409eff !important;
color: var(--el-color-primary) !important;
}
.stat-value {
font-family: Arial, sans-serif;
font-family: var(--font-display);
color: #303640;
}
.stat-foot .positive {
@@ -164,15 +100,15 @@ body {
fill: #d9ecff;
}
.chart-svg path:last-of-type {
stroke: #409eff;
stroke: var(--el-color-primary);
}
.view-all,
.row-action {
color: #409eff !important;
color: var(--el-color-primary) !important;
}
.review-art {
background: #f1f3f5 !important;
border-radius: 5px;
border-radius: var(--app-radius-sm);
font-size: 0;
}
.review-art:after {
@@ -183,12 +119,12 @@ body {
.review-row .el-tag,
.drama-table .el-tag,
.episode-row .el-tag {
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #f0f2f4 !important;
color: #606b78 !important;
}
.drama-cover {
border-radius: 4px;
border-radius: var(--app-radius-sm);
box-shadow: none;
font-size: 14px;
background: #eceff2 !important;
@@ -220,16 +156,16 @@ body {
}
.library-toolbar .el-tabs__item.is-active,
.cover-toolbar .el-tabs__item.is-active {
color: #409eff;
color: var(--el-color-primary);
}
.library-toolbar .el-tabs__active-bar,
.cover-toolbar .el-tabs__active-bar {
background: #409eff;
background: var(--el-color-primary);
}
.toolbar-filters .el-input__wrapper,
.toolbar-filters .el-select__wrapper,
.drama-select .el-input__wrapper {
border-radius: 5px;
border-radius: var(--app-radius-sm);
}
.episode-panel {
padding-left: 20px;
@@ -237,10 +173,10 @@ body {
}
.episode-row {
border-color: #e7e9ec;
border-radius: 5px;
border-radius: var(--app-radius-sm);
}
.episode-thumb {
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #e9ecef;
color: #7d8792;
}
@@ -256,7 +192,7 @@ body {
.cover-card,
.add-cover {
border-color: #e6e8eb;
border-radius: 5px;
border-radius: var(--app-radius-sm);
}
.poster-art {
background: #e9ecef !important;
@@ -296,12 +232,12 @@ body {
color: #656d77;
}
.add-cover > span {
border-radius: 5px;
border-radius: var(--app-radius-sm);
background: #edf0f3;
color: #68798d;
}
.role-card {
border-radius: 5px;
border-radius: var(--app-radius-sm);
border-color: #e7e9ec;
}
.role-active {
@@ -309,48 +245,48 @@ body {
background: #ecf5ff;
}
.role-icon {
border-radius: 5px;
border-radius: var(--app-radius-sm);
background: #ecf5ff !important;
color: #409eff !important;
color: var(--el-color-primary) !important;
}
.role-badge {
border-radius: 4px;
border-radius: var(--app-radius-sm);
background: #eef1f4;
color: #65758a;
}
.permission-group h3 .el-icon {
color: #409eff;
color: var(--el-color-primary);
}
.permission-group .el-checkbox__input.is-checked .el-checkbox__inner {
background: #409eff;
border-color: #409eff;
background: var(--el-color-primary);
border-color: var(--el-color-primary);
}
.permission-note {
background: #f6f7f8;
border-radius: 4px;
border-radius: var(--app-radius-sm);
}
.el-dialog {
border-radius: 6px !important;
border-radius: var(--app-radius-md) !important;
}
.el-dialog__header,
.el-dialog__footer {
border-color: #e8eaed;
}
.upload-area .el-upload-dragger {
border-radius: 5px;
border-radius: var(--app-radius-sm);
background: #fafbfc;
}
.upload-icon,
.upload-area .el-upload__text em {
color: #409eff;
color: var(--el-color-primary);
}
.el-tabs__item,
.el-input__inner,
.el-select__placeholder {
font-family: Arial, 'Noto Sans SC', sans-serif;
font-family: var(--font-body);
}
.el-tag.status-badge {
border-radius: 4px;
border-radius: var(--app-radius-sm);
font-weight: 500;
}
.el-tag.status-badge.is-online {
+1 -1
View File
@@ -4,7 +4,7 @@
flex: 0 0 30px;
display: grid;
place-items: center;
border-radius: 5px;
border-radius: var(--app-radius-sm);
}
.stat-top > .stat-icon .el-icon {
font-size: 16px;
+10 -7
View File
@@ -6,17 +6,17 @@
--left-menu-min-width: 64px;
--left-menu-bg-color: #001529;
--left-menu-bg-color: #17110f;
--left-menu-bg-light-color: #0f2438;
--left-menu-bg-light-color: #221815;
--left-menu-bg-active-color: var(--el-color-primary);
--left-menu-bg-active-color: var(--app-primary-strong);
--left-menu-text-color: #bfcbd9;
--left-menu-text-color: #d7cbc4;
--left-menu-text-active-color: #fff;
--left-menu-collapse-bg-active-color: var(--el-color-primary);
--left-menu-collapse-bg-active-color: var(--app-primary-strong);
/* left menu end */
/* logo start */
@@ -26,9 +26,9 @@
/* logo end */
/* header start */
--top-header-bg-color: '#fff';
--top-header-bg-color: #fff;
--top-header-text-color: 'inherit';
--top-header-text-color: #303133;
--top-header-hover-color: #f6f6f6;
@@ -58,4 +58,7 @@
.dark {
--app-content-bg-color: var(--el-bg-color);
--top-header-bg-color: var(--el-bg-color-overlay);
--top-header-text-color: var(--el-text-color-primary);
--top-header-hover-color: var(--el-fill-color);
}
+67 -22
View File
@@ -29,11 +29,11 @@ const themeChange = () => {
<div class="login-shell">
<section class="login-hero">
<div class="hero-brand">
<img src="@/assets/imgs/logo.png" alt="" />
<img src="@/assets/imgs/brand-mark.png" alt="" />
<span>{{ underlineToHump(appStore.getTitle) }}</span>
</div>
<div class="hero-copy">
<p class="hero-kicker">FruitShorts Studio</p>
<p class="hero-kicker">{{ t('login.consoleLabel') }}</p>
<h1>{{ t('login.welcome') }}</h1>
<p>{{ t('login.message') }}</p>
<ul>
@@ -63,7 +63,7 @@ const themeChange = () => {
.login-page {
height: 100%;
color: var(--el-text-color-primary);
background: #eef3f9;
background: var(--el-bg-color-page);
}
.login-scroll {
@@ -82,9 +82,9 @@ const themeChange = () => {
overflow: hidden;
color: #fff;
background:
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 16%), transparent 28%),
radial-gradient(circle at 82% 78%, rgb(64 158 255 / 45%), transparent 32%),
linear-gradient(155deg, #0d2344 0%, #1a4f96 52%, #409eff 100%);
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 10%), transparent 28%),
radial-gradient(circle at 82% 78%, rgb(196 50 42 / 34%), transparent 34%),
linear-gradient(155deg, #17100f 0%, #241714 55%, #3b1d19 100%);
flex: 1.05;
flex-direction: column;
justify-content: space-between;
@@ -111,8 +111,10 @@ const themeChange = () => {
font-weight: 700;
img {
width: 42px;
height: 42px;
width: var(--brand-mark-size);
height: var(--brand-mark-size);
border-radius: var(--brand-mark-radius);
box-shadow: 0 10px 24px rgb(196 50 42 / 28%);
}
}
@@ -169,7 +171,7 @@ const themeChange = () => {
align-items: center;
justify-content: center;
padding: 88px 32px 48px;
background: #f4f7fb;
background: var(--el-bg-color-page);
}
.login-tools {
@@ -184,28 +186,28 @@ const themeChange = () => {
.login-card {
width: min(420px, 100%);
padding: 36px 32px 28px;
background: #fff;
border: 1px solid #e6edf5;
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
border-radius: 18px;
box-shadow: 0 18px 50px rgb(15 39 68 / 8%);
box-shadow: 0 18px 50px rgb(42 24 16 / 10%);
}
.is-dark {
background: #0e1116;
background: var(--el-bg-color-page);
.login-hero {
background:
radial-gradient(circle at 18% 18%, rgb(64 158 255 / 18%), transparent 30%),
linear-gradient(155deg, #07111f 0%, #0e2744 58%, #163e73 100%);
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 8%), transparent 30%),
linear-gradient(155deg, #0e0a09 0%, #1a1110 58%, #2c1513 100%);
}
.login-panel {
background: #0e1116;
background: var(--el-bg-color-page);
}
.login-card {
background: #161b22;
border-color: #2a313c;
background: var(--el-bg-color-overlay);
border-color: var(--el-border-color);
box-shadow: none;
}
}
@@ -217,16 +219,46 @@ const themeChange = () => {
.login-hero {
flex: none;
min-height: 280px;
padding: 28px 24px 32px;
min-height: 132px;
padding: 16px 24px 20px;
}
.hero-copy h1 {
font-size: 30px;
margin: 6px 0 8px;
font-size: 28px;
}
.hero-copy ul {
display: none;
}
.login-panel {
padding: 72px 16px 32px;
padding: 44px 16px 12px;
}
.login-card {
padding-top: 28px;
padding-bottom: 20px;
}
.login-tools {
top: 8px;
right: 16px;
}
}
@media (width <= 600px) {
.login-hero {
min-height: 118px;
padding: 16px 18px 18px;
}
.hero-copy p {
font-size: 13px;
}
.login-card {
padding: 28px 20px 24px;
}
}
</style>
@@ -269,6 +301,19 @@ const themeChange = () => {
}
}
.login-card :deep(.el-button--primary) {
--el-button-bg-color: var(--app-brand);
--el-button-border-color: var(--app-brand);
--el-button-hover-bg-color: var(--app-brand-pressed);
--el-button-hover-border-color: var(--app-brand-pressed);
--el-button-active-bg-color: var(--app-brand-pressed);
--el-button-active-border-color: var(--app-brand-pressed);
}
.login-card :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px var(--app-brand) inset;
}
body:has(.login-page) {
min-width: 0;
background: transparent;
+7 -7
View File
@@ -64,7 +64,7 @@ const schema = reactive<FormSchema[]>([
span: 24
},
componentProps: {
placeholder: 'admin'
placeholder: t('login.usernamePlaceholder')
}
},
{
@@ -79,7 +79,7 @@ const schema = reactive<FormSchema[]>([
style: {
width: '100%'
},
placeholder: 'admin123'
placeholder: t('login.passwordPlaceholder')
}
},
{
@@ -143,8 +143,7 @@ const remember = ref(userStore.getRememberMe)
const initLoginInfo = () => {
const loginInfo = userStore.getLoginInfo
if (loginInfo) {
const { username, password } = loginInfo
setValues({ username, password })
setValues({ username: loginInfo.username })
}
}
onMounted(() => {
@@ -170,6 +169,7 @@ watch(
// 登录
const signIn = async () => {
if (loading.value) return
const formRef = await getElFormExpose()
await formRef?.validate(async (isValid) => {
if (isValid) {
@@ -183,8 +183,7 @@ const signIn = async () => {
// 是否记住我
if (unref(remember)) {
userStore.setLoginInfo({
username: formData.username,
password: formData.password
username: formData.username
})
} else {
userStore.setLoginInfo(undefined)
@@ -196,7 +195,7 @@ const signIn = async () => {
}
// 是否使用动态路由
if (appStore.getDynamicRouter) {
getRole()
await getRole()
} else {
await permissionStore.generateRoutes('static').catch(() => {})
permissionStore.getAddRouters.forEach((route) => {
@@ -254,6 +253,7 @@ const toRegister = () => {
hide-required-asterisk
size="large"
class="login-form"
@keyup.enter="signIn"
@register="formRegister"
/>
</template>
+2 -2
View File
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createProductApi,
deleteProductApi,
@@ -245,7 +245,7 @@ onMounted(load)
class="primary-button"
@click="openCreate"
>
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建商品
</el-button>
</PageHeading>
+6 -10
View File
@@ -3,7 +3,7 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { VideoCamera, CaretRight, Clock, CircleCheck, Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { pageDramasApi } from '@/api/admin/drama'
import { useRbac } from '@/stores/rbac'
import { formatCount } from '@/utils/format'
@@ -52,36 +52,32 @@ onMounted(async () => {
type="primary"
class="primary-button"
@click="router.push({ name: 'dramas' })"
><el-icon><Plus /></el-icon>上传新短剧</el-button
><AppIcon :icon="AppIcons.add" />上传新短剧</el-button
></div
>
<div v-loading="loading" class="stats-grid">
<div class="stat-card"
><div class="stat-top"
><span>短剧总数</span
><span class="stat-icon"
><el-icon><VideoCamera /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.content" /></span></div
><div class="stat-value">{{ total }} <small>部</small></div></div
>
<div class="stat-card"
><div class="stat-top"
><span>累计播放量</span
><span class="stat-icon"
><el-icon><CaretRight /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.videoPlay" /></span></div
><div class="stat-value">{{ formatCount(totalViews) }}</div></div
>
<div class="stat-card"
><div class="stat-top"
><span>待审核内容</span
><span class="stat-icon"
><el-icon><Clock /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.clock" /></span></div
><div class="stat-value">{{ reviewingCount }} <small>项</small></div></div
>
<div class="stat-card"
><div class="stat-top"
><span>已上线</span
><span class="stat-icon"
><el-icon><CircleCheck /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.checkCircle" /></span></div
><div class="stat-value">{{ publishedCount }} <small>部</small></div></div
>
</div>
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createBannerApi,
deleteBannerApi,
@@ -219,7 +219,7 @@ onMounted(async () => {
class="primary-button"
@click="openCreate"
>
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建轮播图
</el-button>
</PageHeading>
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createDramaCategoryApi,
deleteDramaCategoryApi,
@@ -100,7 +100,7 @@ onMounted(load)
description="维护短剧类型,供新建短剧选择,也作为客户端分类筛选的依据。"
>
<el-button v-if="can('drama:create')" type="primary" class="primary-button" @click="openCreate">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建类型
</el-button>
</PageHeading>
@@ -4,7 +4,7 @@
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
changeDramaStatusApi,
createDramaApi,
@@ -242,7 +242,7 @@ onMounted(async () => {
><div class="eyebrow">内容管理</div><h1>短剧管理</h1
><p>管理短剧信息、封面、剧集和发布状态。</p></div
><el-button v-if="can('drama:create')" type="primary" class="primary-button" @click="openCreate"
><el-icon><Plus /></el-icon>新建短剧</el-button
><AppIcon :icon="AppIcons.add" />新建短剧</el-button
></div
>
<section class="panel library-panel">
@@ -251,11 +251,12 @@ onMounted(async () => {
><el-input
v-model="keyword"
placeholder="搜索短剧名称"
:prefix-icon="Search"
clearable
@keyup.enter="search"
@clear="search"
/><el-select v-model="statusFilter" clearable placeholder="全部状态" @change="search"
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template> </el-input
><el-select v-model="statusFilter" clearable placeholder="全部状态" @change="search"
><el-option
v-for="item in statusOptions"
:key="item.code"
@@ -4,7 +4,7 @@
import { computed, onMounted, reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { pageDramasApi } from '@/api/admin/drama'
import {
createEpisodeApi,
@@ -232,7 +232,7 @@ onMounted(async () => {
class="primary-button"
:disabled="!selectedDramaId"
@click="openCreate"
><el-icon><Plus /></el-icon>新增剧集</el-button
><AppIcon :icon="AppIcons.add" />新增剧集</el-button
></div
>
<section class="panel episode-panel">
+4 -4
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { ArrowLeft, Upload } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import CreatorCenterPage from './components/CreatorCenterPage.vue'
import { useRbac } from '@/stores/rbac'
@@ -72,7 +72,7 @@ function requestDramaCover(options) {
title="作品编辑"
:description="drama ? `${drama.title}(${drama.code})` : ''"
>
<el-button :icon="ArrowLeft" @click="back">返回</el-button>
<el-button @click="back"><AppIcon :icon="AppIcons.arrowBack" />返回</el-button>
</PageHeading>
<section v-loading="loading" class="panel">
@@ -145,7 +145,7 @@ function requestDramaCover(options) {
:http-request="requestDramaCover"
:disabled="uploadingId !== null"
>
<el-button size="small" :icon="Upload">上传封面</el-button>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
</div>
</el-form-item>
@@ -341,7 +341,7 @@ function requestDramaCover(options) {
accept="image/*"
:http-request="(options) => requestEpisodeCover({ id: episodeEditingId }, options)"
>
<el-button size="small" :icon="Upload">上传封面</el-button>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
</div>
</el-form-item>
+4 -3
View File
@@ -3,7 +3,7 @@
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import CreatorCenterPage from './components/CreatorCenterPage.vue'
import { useCreatorAccess } from './composables/useCreatorAccess'
@@ -112,12 +112,13 @@ function openEdit(row) {
<el-input
v-model="dramas.keyword"
class="console-keyword"
:prefix-icon="Search"
clearable
placeholder="搜索作品名称"
@keyup.enter="queryDramas"
@clear="queryDramas"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select
v-model="dramas.status"
class="console-select"
+4 -3
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import { useRbac } from '@/stores/rbac'
import { dateCell } from '@/utils/format'
@@ -72,12 +72,13 @@ const {
<el-input
v-model="keyword"
class="console-keyword"
:prefix-icon="Search"
clearable
placeholder="搜索名称 / 编号 / 联系人 / 联系方式"
@keyup.enter="query"
@clear="query"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select
v-model="statusFilter"
class="console-select"
+2 -2
View File
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createNoticeApi,
deleteNoticeApi,
@@ -184,7 +184,7 @@ onMounted(load)
class="primary-button"
@click="openCreate"
>
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建公告
</el-button>
</PageHeading>
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { menuIconNames, useMenuManagement } from '@/views/permission/composables/useMenuManagement'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -12,7 +12,7 @@ const { treeData, loading, form, parentOptions, resetForm, selectNode, saveMenu,
<template>
<PageHeading eyebrow="后台管理" title="菜单管理" description="维护系统菜单结构、路由与按钮权限。">
<el-button type="primary" @click="resetForm(0)">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新增菜单
</el-button>
</PageHeading>
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { useRoleCategories } from '@/views/permission/composables/useRoleCategories'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -25,7 +25,7 @@ const {
description="维护角色分类,方便按职责整理权限角色。"
>
<el-button type="primary" class="primary-button" @click="openCreateDialog">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新增分类
</el-button>
</PageHeading>
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { useRolePermissionEditor } from '@/views/permission/composables/useRolePermissionEditor'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -36,7 +36,7 @@ function removeRoleRow(row) {
description="角色等级数字越小权限越高,低等级角色不能修改高等级角色。"
>
<el-button type="primary" class="primary-button" @click="dialogVisible = true">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新增角色
</el-button>
</PageHeading>
@@ -3,17 +3,9 @@
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { createMenuApi, deleteMenuApi, getMenuTreeApi, updateMenuApi } from '@/api/admin/role'
import { MenuIconOptions, normalizeAppIcon } from '@/constants/icons'
export const menuIconNames = [
'Document',
'DataAnalysis',
'Picture',
'User',
'Setting',
'VideoCamera',
'Menu',
'Grid'
]
export const menuIconNames = MenuIconOptions
/** 菜单管理:直接读写后端 sys_menu,变更后重新登录生效。 */
export function useMenuManagement() {
@@ -101,7 +93,7 @@ export function useMenuManagement() {
routePath: raw.path || '',
routeName: raw.name || '',
component: raw.component === '#' ? '' : raw.component || '',
icon: raw.icon || '',
icon: normalizeAppIcon(raw.icon),
permissionCode: raw.permissionCode || '',
type: raw.type || 'MENU',
sort: raw.sort ?? 1,
@@ -121,7 +113,7 @@ export function useMenuManagement() {
routePath: form.routePath.trim(),
routeName: form.routeName.trim(),
component: form.type === 'CATALOG' ? '#' : form.component.trim(),
icon: form.icon,
icon: form.type === 'BUTTON' ? '' : normalizeAppIcon(form.icon),
permissionCode: form.permissionCode.trim(),
type: form.type,
sort: Number(form.sort) || 1,
@@ -2,7 +2,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { getOperationLogApi, pageOperationLogsApi } from '@/api/admin/log'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -103,12 +103,13 @@ onMounted(load)
<div class="drama-filters">
<el-input
v-model="filters.username"
:prefix-icon="Search"
clearable
placeholder="操作人"
@keyup.enter="search"
@clear="search"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select v-model="filters.module" clearable placeholder="全部模块" @change="search">
<el-option
v-for="item in moduleOptions"
+5 -4
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus, Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { useAdminUsers } from '@/views/userManagement/composables/useAdminUsers'
import { useRbac } from '@/stores/rbac'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -44,18 +44,19 @@ const {
<div class="admin-user-toolbar">
<el-input
v-model="keyword"
:prefix-icon="Search"
clearable
placeholder="搜索登录账号"
@keyup.enter="queryUsers"
@clear="queryUsers"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select v-model="roleFilter" clearable placeholder="全部角色" @change="queryUsers">
<el-option v-for="role in roles" :key="role.id" :label="role.name" :value="role.id" />
</el-select>
<el-button type="primary" @click="queryUsers">查询</el-button>
<el-button v-if="can('system:admin-user')" type="primary" @click="openCreate">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
添加用户
</el-button>
</div>
+4 -3
View File
@@ -2,7 +2,7 @@
<script setup>
import { computed } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import { useRbac } from '@/stores/rbac'
import {
@@ -87,12 +87,13 @@ function canBeBanned(row) {
<el-input
v-model="keyword"
class="app-user-keyword"
:prefix-icon="Search"
clearable
placeholder="搜索 uid / 昵称 / 手机号 / 邮箱"
@keyup.enter="queryUsers"
@clear="queryUsers"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select
v-model="statusFilter"
class="app-user-status"