chore(web): 合并 K22 后台视觉统一
This commit is contained in:
commit
8f3447e528
67 files changed
+737
-744
No files matched your search
@@ -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 |
@@ -0,0 +1,5 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import AppIcon from './src/AppIcon.vue'
|
||||
|
||||
export { AppIcon }
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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={[
|
||||
|
||||
@@ -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)"
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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,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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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?',
|
||||
|
||||
@@ -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: '还没有账号?',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user