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

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

No files matched your search

+67 -22
View File
@@ -29,11 +29,11 @@ const themeChange = () => {
<div class="login-shell">
<section class="login-hero">
<div class="hero-brand">
<img src="@/assets/imgs/logo.png" alt="" />
<img src="@/assets/imgs/brand-mark.png" alt="" />
<span>{{ underlineToHump(appStore.getTitle) }}</span>
</div>
<div class="hero-copy">
<p class="hero-kicker">FruitShorts Studio</p>
<p class="hero-kicker">{{ t('login.consoleLabel') }}</p>
<h1>{{ t('login.welcome') }}</h1>
<p>{{ t('login.message') }}</p>
<ul>
@@ -63,7 +63,7 @@ const themeChange = () => {
.login-page {
height: 100%;
color: var(--el-text-color-primary);
background: #eef3f9;
background: var(--el-bg-color-page);
}
.login-scroll {
@@ -82,9 +82,9 @@ const themeChange = () => {
overflow: hidden;
color: #fff;
background:
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 16%), transparent 28%),
radial-gradient(circle at 82% 78%, rgb(64 158 255 / 45%), transparent 32%),
linear-gradient(155deg, #0d2344 0%, #1a4f96 52%, #409eff 100%);
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 10%), transparent 28%),
radial-gradient(circle at 82% 78%, rgb(196 50 42 / 34%), transparent 34%),
linear-gradient(155deg, #17100f 0%, #241714 55%, #3b1d19 100%);
flex: 1.05;
flex-direction: column;
justify-content: space-between;
@@ -111,8 +111,10 @@ const themeChange = () => {
font-weight: 700;
img {
width: 42px;
height: 42px;
width: var(--brand-mark-size);
height: var(--brand-mark-size);
border-radius: var(--brand-mark-radius);
box-shadow: 0 10px 24px rgb(196 50 42 / 28%);
}
}
@@ -169,7 +171,7 @@ const themeChange = () => {
align-items: center;
justify-content: center;
padding: 88px 32px 48px;
background: #f4f7fb;
background: var(--el-bg-color-page);
}
.login-tools {
@@ -184,28 +186,28 @@ const themeChange = () => {
.login-card {
width: min(420px, 100%);
padding: 36px 32px 28px;
background: #fff;
border: 1px solid #e6edf5;
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
border-radius: 18px;
box-shadow: 0 18px 50px rgb(15 39 68 / 8%);
box-shadow: 0 18px 50px rgb(42 24 16 / 10%);
}
.is-dark {
background: #0e1116;
background: var(--el-bg-color-page);
.login-hero {
background:
radial-gradient(circle at 18% 18%, rgb(64 158 255 / 18%), transparent 30%),
linear-gradient(155deg, #07111f 0%, #0e2744 58%, #163e73 100%);
radial-gradient(circle at 18% 18%, rgb(255 255 255 / 8%), transparent 30%),
linear-gradient(155deg, #0e0a09 0%, #1a1110 58%, #2c1513 100%);
}
.login-panel {
background: #0e1116;
background: var(--el-bg-color-page);
}
.login-card {
background: #161b22;
border-color: #2a313c;
background: var(--el-bg-color-overlay);
border-color: var(--el-border-color);
box-shadow: none;
}
}
@@ -217,16 +219,46 @@ const themeChange = () => {
.login-hero {
flex: none;
min-height: 280px;
padding: 28px 24px 32px;
min-height: 132px;
padding: 16px 24px 20px;
}
.hero-copy h1 {
font-size: 30px;
margin: 6px 0 8px;
font-size: 28px;
}
.hero-copy ul {
display: none;
}
.login-panel {
padding: 72px 16px 32px;
padding: 44px 16px 12px;
}
.login-card {
padding-top: 28px;
padding-bottom: 20px;
}
.login-tools {
top: 8px;
right: 16px;
}
}
@media (width <= 600px) {
.login-hero {
min-height: 118px;
padding: 16px 18px 18px;
}
.hero-copy p {
font-size: 13px;
}
.login-card {
padding: 28px 20px 24px;
}
}
</style>
@@ -269,6 +301,19 @@ const themeChange = () => {
}
}
.login-card :deep(.el-button--primary) {
--el-button-bg-color: var(--app-brand);
--el-button-border-color: var(--app-brand);
--el-button-hover-bg-color: var(--app-brand-pressed);
--el-button-hover-border-color: var(--app-brand-pressed);
--el-button-active-bg-color: var(--app-brand-pressed);
--el-button-active-border-color: var(--app-brand-pressed);
}
.login-card :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px var(--app-brand) inset;
}
body:has(.login-page) {
min-width: 0;
background: transparent;
+7 -7
View File
@@ -64,7 +64,7 @@ const schema = reactive<FormSchema[]>([
span: 24
},
componentProps: {
placeholder: 'admin'
placeholder: t('login.usernamePlaceholder')
}
},
{
@@ -79,7 +79,7 @@ const schema = reactive<FormSchema[]>([
style: {
width: '100%'
},
placeholder: 'admin123'
placeholder: t('login.passwordPlaceholder')
}
},
{
@@ -143,8 +143,7 @@ const remember = ref(userStore.getRememberMe)
const initLoginInfo = () => {
const loginInfo = userStore.getLoginInfo
if (loginInfo) {
const { username, password } = loginInfo
setValues({ username, password })
setValues({ username: loginInfo.username })
}
}
onMounted(() => {
@@ -170,6 +169,7 @@ watch(
// 登录
const signIn = async () => {
if (loading.value) return
const formRef = await getElFormExpose()
await formRef?.validate(async (isValid) => {
if (isValid) {
@@ -183,8 +183,7 @@ const signIn = async () => {
// 是否记住我
if (unref(remember)) {
userStore.setLoginInfo({
username: formData.username,
password: formData.password
username: formData.username
})
} else {
userStore.setLoginInfo(undefined)
@@ -196,7 +195,7 @@ const signIn = async () => {
}
// 是否使用动态路由
if (appStore.getDynamicRouter) {
getRole()
await getRole()
} else {
await permissionStore.generateRoutes('static').catch(() => {})
permissionStore.getAddRouters.forEach((route) => {
@@ -254,6 +253,7 @@ const toRegister = () => {
hide-required-asterisk
size="large"
class="login-form"
@keyup.enter="signIn"
@register="formRegister"
/>
</template>
+2 -2
View File
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createProductApi,
deleteProductApi,
@@ -245,7 +245,7 @@ onMounted(load)
class="primary-button"
@click="openCreate"
>
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建商品
</el-button>
</PageHeading>
+6 -10
View File
@@ -3,7 +3,7 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { VideoCamera, CaretRight, Clock, CircleCheck, Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { pageDramasApi } from '@/api/admin/drama'
import { useRbac } from '@/stores/rbac'
import { formatCount } from '@/utils/format'
@@ -52,36 +52,32 @@ onMounted(async () => {
type="primary"
class="primary-button"
@click="router.push({ name: 'dramas' })"
><el-icon><Plus /></el-icon>上传新短剧</el-button
><AppIcon :icon="AppIcons.add" />上传新短剧</el-button
></div
>
<div v-loading="loading" class="stats-grid">
<div class="stat-card"
><div class="stat-top"
><span>短剧总数</span
><span class="stat-icon"
><el-icon><VideoCamera /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.content" /></span></div
><div class="stat-value">{{ total }} <small>部</small></div></div
>
<div class="stat-card"
><div class="stat-top"
><span>累计播放量</span
><span class="stat-icon"
><el-icon><CaretRight /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.videoPlay" /></span></div
><div class="stat-value">{{ formatCount(totalViews) }}</div></div
>
<div class="stat-card"
><div class="stat-top"
><span>待审核内容</span
><span class="stat-icon"
><el-icon><Clock /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.clock" /></span></div
><div class="stat-value">{{ reviewingCount }} <small>项</small></div></div
>
<div class="stat-card"
><div class="stat-top"
><span>已上线</span
><span class="stat-icon"
><el-icon><CircleCheck /></el-icon></span></div
><span class="stat-icon"><AppIcon :icon="AppIcons.checkCircle" /></span></div
><div class="stat-value">{{ publishedCount }} <small>部</small></div></div
>
</div>
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createBannerApi,
deleteBannerApi,
@@ -219,7 +219,7 @@ onMounted(async () => {
class="primary-button"
@click="openCreate"
>
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建轮播图
</el-button>
</PageHeading>
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createDramaCategoryApi,
deleteDramaCategoryApi,
@@ -100,7 +100,7 @@ onMounted(load)
description="维护短剧类型,供新建短剧选择,也作为客户端分类筛选的依据。"
>
<el-button v-if="can('drama:create')" type="primary" class="primary-button" @click="openCreate">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建类型
</el-button>
</PageHeading>
@@ -4,7 +4,7 @@
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
changeDramaStatusApi,
createDramaApi,
@@ -242,7 +242,7 @@ onMounted(async () => {
><div class="eyebrow">内容管理</div><h1>短剧管理</h1
><p>管理短剧信息、封面、剧集和发布状态。</p></div
><el-button v-if="can('drama:create')" type="primary" class="primary-button" @click="openCreate"
><el-icon><Plus /></el-icon>新建短剧</el-button
><AppIcon :icon="AppIcons.add" />新建短剧</el-button
></div
>
<section class="panel library-panel">
@@ -251,11 +251,12 @@ onMounted(async () => {
><el-input
v-model="keyword"
placeholder="搜索短剧名称"
:prefix-icon="Search"
clearable
@keyup.enter="search"
@clear="search"
/><el-select v-model="statusFilter" clearable placeholder="全部状态" @change="search"
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template> </el-input
><el-select v-model="statusFilter" clearable placeholder="全部状态" @change="search"
><el-option
v-for="item in statusOptions"
:key="item.code"
@@ -4,7 +4,7 @@
import { computed, onMounted, reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { pageDramasApi } from '@/api/admin/drama'
import {
createEpisodeApi,
@@ -232,7 +232,7 @@ onMounted(async () => {
class="primary-button"
:disabled="!selectedDramaId"
@click="openCreate"
><el-icon><Plus /></el-icon>新增剧集</el-button
><AppIcon :icon="AppIcons.add" />新增剧集</el-button
></div
>
<section class="panel episode-panel">
+4 -4
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { ArrowLeft, Upload } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import CreatorCenterPage from './components/CreatorCenterPage.vue'
import { useRbac } from '@/stores/rbac'
@@ -72,7 +72,7 @@ function requestDramaCover(options) {
title="作品编辑"
:description="drama ? `${drama.title}(${drama.code})` : ''"
>
<el-button :icon="ArrowLeft" @click="back">返回</el-button>
<el-button @click="back"><AppIcon :icon="AppIcons.arrowBack" />返回</el-button>
</PageHeading>
<section v-loading="loading" class="panel">
@@ -145,7 +145,7 @@ function requestDramaCover(options) {
:http-request="requestDramaCover"
:disabled="uploadingId !== null"
>
<el-button size="small" :icon="Upload">上传封面</el-button>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
</div>
</el-form-item>
@@ -341,7 +341,7 @@ function requestDramaCover(options) {
accept="image/*"
:http-request="(options) => requestEpisodeCover({ id: episodeEditingId }, options)"
>
<el-button size="small" :icon="Upload">上传封面</el-button>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
</div>
</el-form-item>
+4 -3
View File
@@ -3,7 +3,7 @@
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import CreatorCenterPage from './components/CreatorCenterPage.vue'
import { useCreatorAccess } from './composables/useCreatorAccess'
@@ -112,12 +112,13 @@ function openEdit(row) {
<el-input
v-model="dramas.keyword"
class="console-keyword"
:prefix-icon="Search"
clearable
placeholder="搜索作品名称"
@keyup.enter="queryDramas"
@clear="queryDramas"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select
v-model="dramas.status"
class="console-select"
+4 -3
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import { useRbac } from '@/stores/rbac'
import { dateCell } from '@/utils/format'
@@ -72,12 +72,13 @@ const {
<el-input
v-model="keyword"
class="console-keyword"
:prefix-icon="Search"
clearable
placeholder="搜索名称 / 编号 / 联系人 / 联系方式"
@keyup.enter="query"
@clear="query"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select
v-model="statusFilter"
class="console-select"
+2 -2
View File
@@ -3,7 +3,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import {
createNoticeApi,
deleteNoticeApi,
@@ -184,7 +184,7 @@ onMounted(load)
class="primary-button"
@click="openCreate"
>
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新建公告
</el-button>
</PageHeading>
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { menuIconNames, useMenuManagement } from '@/views/permission/composables/useMenuManagement'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -12,7 +12,7 @@ const { treeData, loading, form, parentOptions, resetForm, selectNode, saveMenu,
<template>
<PageHeading eyebrow="后台管理" title="菜单管理" description="维护系统菜单结构、路由与按钮权限。">
<el-button type="primary" @click="resetForm(0)">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新增菜单
</el-button>
</PageHeading>
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { useRoleCategories } from '@/views/permission/composables/useRoleCategories'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -25,7 +25,7 @@ const {
description="维护角色分类,方便按职责整理权限角色。"
>
<el-button type="primary" class="primary-button" @click="openCreateDialog">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新增分类
</el-button>
</PageHeading>
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { useRolePermissionEditor } from '@/views/permission/composables/useRolePermissionEditor'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -36,7 +36,7 @@ function removeRoleRow(row) {
description="角色等级数字越小权限越高,低等级角色不能修改高等级角色。"
>
<el-button type="primary" class="primary-button" @click="dialogVisible = true">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
新增角色
</el-button>
</PageHeading>
@@ -3,17 +3,9 @@
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { createMenuApi, deleteMenuApi, getMenuTreeApi, updateMenuApi } from '@/api/admin/role'
import { MenuIconOptions, normalizeAppIcon } from '@/constants/icons'
export const menuIconNames = [
'Document',
'DataAnalysis',
'Picture',
'User',
'Setting',
'VideoCamera',
'Menu',
'Grid'
]
export const menuIconNames = MenuIconOptions
/** 菜单管理:直接读写后端 sys_menu,变更后重新登录生效。 */
export function useMenuManagement() {
@@ -101,7 +93,7 @@ export function useMenuManagement() {
routePath: raw.path || '',
routeName: raw.name || '',
component: raw.component === '#' ? '' : raw.component || '',
icon: raw.icon || '',
icon: normalizeAppIcon(raw.icon),
permissionCode: raw.permissionCode || '',
type: raw.type || 'MENU',
sort: raw.sort ?? 1,
@@ -121,7 +113,7 @@ export function useMenuManagement() {
routePath: form.routePath.trim(),
routeName: form.routeName.trim(),
component: form.type === 'CATALOG' ? '#' : form.component.trim(),
icon: form.icon,
icon: form.type === 'BUTTON' ? '' : normalizeAppIcon(form.icon),
permissionCode: form.permissionCode.trim(),
type: form.type,
sort: Number(form.sort) || 1,
@@ -2,7 +2,7 @@
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { getOperationLogApi, pageOperationLogsApi } from '@/api/admin/log'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -103,12 +103,13 @@ onMounted(load)
<div class="drama-filters">
<el-input
v-model="filters.username"
:prefix-icon="Search"
clearable
placeholder="操作人"
@keyup.enter="search"
@clear="search"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select v-model="filters.module" clearable placeholder="全部模块" @change="search">
<el-option
v-for="item in moduleOptions"
+5 -4
View File
@@ -1,7 +1,7 @@
<!-- Author: fruit.com.cn -->
<script setup>
import { Plus, Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import { useAdminUsers } from '@/views/userManagement/composables/useAdminUsers'
import { useRbac } from '@/stores/rbac'
import PageHeading from '@/views/components/PageHeading.vue'
@@ -44,18 +44,19 @@ const {
<div class="admin-user-toolbar">
<el-input
v-model="keyword"
:prefix-icon="Search"
clearable
placeholder="搜索登录账号"
@keyup.enter="queryUsers"
@clear="queryUsers"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select v-model="roleFilter" clearable placeholder="全部角色" @change="queryUsers">
<el-option v-for="role in roles" :key="role.id" :label="role.name" :value="role.id" />
</el-select>
<el-button type="primary" @click="queryUsers">查询</el-button>
<el-button v-if="can('system:admin-user')" type="primary" @click="openCreate">
<el-icon><Plus /></el-icon>
<AppIcon :icon="AppIcons.add" />
添加用户
</el-button>
</div>
+4 -3
View File
@@ -2,7 +2,7 @@
<script setup>
import { computed } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { AppIcons } from '@/constants/icons'
import PageHeading from '@/views/components/PageHeading.vue'
import { useRbac } from '@/stores/rbac'
import {
@@ -87,12 +87,13 @@ function canBeBanned(row) {
<el-input
v-model="keyword"
class="app-user-keyword"
:prefix-icon="Search"
clearable
placeholder="搜索 uid / 昵称 / 手机号 / 邮箱"
@keyup.enter="queryUsers"
@clear="queryUsers"
/>
>
<template #prefix><AppIcon :icon="AppIcons.search" /></template>
</el-input>
<el-select
v-model="statusFilter"
class="app-user-status"