|
|
<template>
|
|
|
<div class="admin-layout">
|
|
|
<header class="admin-header">
|
|
|
|
|
|
<!-- 管理端:显示 Logo / 医院端:显示医院名 -->
|
|
|
<div class="logo-bar">
|
|
|
<span v-if="!isHospitalMode" class="logo-text admin"> 康策CSM系统管理平台<span class="version">v1.0</span> </span>
|
|
|
<span v-else class="logo-text hospital">{{ userStore.userInfo?.hospitalName || "医院端" }}</span>
|
|
|
</div>
|
|
|
|
|
|
<div class="header-middle">
|
|
|
<!-- 顶部水平菜单:管理端 / 医院端共用,按当前顶层路由动态生成 -->
|
|
|
<el-menu class="header-menu" mode="horizontal" :default-active="$route.path" :ellipsis="false" router>
|
|
|
<el-menu-item v-for="item in sideRoutes" :key="item.path" :index="item.path">
|
|
|
<SvgIcon :name="item.iconName" class="nav-icon" />
|
|
|
<template #title>
|
|
|
<span>{{ item.title }}</span>
|
|
|
</template>
|
|
|
</el-menu-item>
|
|
|
</el-menu>
|
|
|
|
|
|
<!-- 医院端:顶部菜单旁的快速提交按钮 -->
|
|
|
<div v-if="isHospitalMode" class="header-quick-submit">
|
|
|
<el-button type="primary" class="quick-submit-btn" @click="orderDialogStore.open()">
|
|
|
<img src="@/assets/image/hospital/quick.svg" class="quick-icon" alt="" />
|
|
|
<span>快速提交</span>
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="header-right">
|
|
|
<el-dropdown trigger="hover" @command="handleCommand">
|
|
|
<div class="user-trigger">
|
|
|
<!-- <SvgIcon name="bell-filled" :size="20" color="#fff" /> -->
|
|
|
<span class="username">{{
|
|
|
userStore.userInfo?.name ||
|
|
|
userStore.userInfo?.userName ||
|
|
|
"管理员"
|
|
|
}}</span>
|
|
|
<el-avatar :size="40" :src="avatarSrc"></el-avatar>
|
|
|
|
|
|
<!-- <el-icon>
|
|
|
<ArrowDown />
|
|
|
</el-icon> -->
|
|
|
</div>
|
|
|
<template #dropdown>
|
|
|
<el-dropdown-menu>
|
|
|
<el-dropdown-item command="password">修改密码</el-dropdown-item>
|
|
|
<el-dropdown-item command="theme">更换主题</el-dropdown-item>
|
|
|
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
|
|
</el-dropdown-menu>
|
|
|
</template>
|
|
|
</el-dropdown>
|
|
|
</div>
|
|
|
</header>
|
|
|
|
|
|
<main class="admin-content">
|
|
|
<slot />
|
|
|
</main>
|
|
|
|
|
|
<!-- 更换主题弹窗 -->
|
|
|
<el-dialog v-model="themeDialogVisible" class="theme-dialog" :close-on-click-modal="false" align-center
|
|
|
destroy-on-close>
|
|
|
<div class="theme-body">
|
|
|
<div class="theme-header">
|
|
|
<span class="theme-title">更换主题</span>
|
|
|
<img src="@/assets/image/close-btn.svg" alt="" class="close-btn" @click="themeDialogVisible = false">
|
|
|
</div>
|
|
|
<div class="theme-grid">
|
|
|
<div v-for="t in presetThemes" :key="t.value" class="theme-card"
|
|
|
:class="{ active: themeStore.themeValue === t.value }" :style="{
|
|
|
'--brand-color': t.borderColor
|
|
|
}" @click="handlePresetTheme(t.value)">
|
|
|
<div class="theme-preview" :style="{ background: t.preview, boxShadow: t.boxShadow }"></div>
|
|
|
<div class="theme-name">{{ t.name }}</div>
|
|
|
<div class="theme-desc">{{ t.desc }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 全局工单表单弹窗(医院端快速提交 / 列表编辑统一使用) -->
|
|
|
<OrderFormDialog v-model="orderDialogStore.visible" :order-id="orderDialogStore.orderId"
|
|
|
@success="onOrderDialogSuccess" />
|
|
|
|
|
|
<!-- 修改密码弹窗(管理端 / 医院端共用,根据 role 走对应接口) -->
|
|
|
<PasswordDialog v-model="passwordDialogVisible" :role="passwordRole" />
|
|
|
|
|
|
<!-- 全局签到弹窗(医院端登录后自动触发,未签到时弹出) -->
|
|
|
<SignInDialog v-model="signInDialogStore.visible" :signed-in="signInDialogStore.summary.isSignIn"
|
|
|
:total-points="signInDialogStore.summary.totalPoints"
|
|
|
:consecutive-sign-in-days="signInDialogStore.summary.consecutiveSignInDays"
|
|
|
:sign-in-rank="signInDialogStore.summary.signInRank" :beat-percent="signInDialogStore.summary.beatPercent"
|
|
|
:has-drawn="signInDialogStore.summary.isDraw" @success="onSignInDialogSuccess" />
|
|
|
|
|
|
<!-- 全局奖励结果弹窗(抽奖/签到/提交工单 共用) -->
|
|
|
<RewardResultDialog />
|
|
|
|
|
|
<!-- 全局查看我的资产弹窗(由 RewardResultDialog 中点击"查看我的资产"触发) -->
|
|
|
<AssetsDialog v-model="assetDialogStore.visible" :total-points="assetDialogStore.totalPoints"
|
|
|
:exchange-goal="assetDialogStore.exchangeGoal" :reward-name="assetDialogStore.rewardName"
|
|
|
:today-order="assetDialogStore.todayOrder" :today-sign-in="assetDialogStore.todaySignIn"
|
|
|
:today-draw="assetDialogStore.todayDraw" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { computed, onMounted, ref } from "vue";
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
import { ElMessageBox } from "element-plus";
|
|
|
import { ArrowDown } from "@element-plus/icons-vue";
|
|
|
import SvgIcon from "@/components/SvgIcon/index.vue";
|
|
|
import { useUserStore } from "@/stores/api/user";
|
|
|
import { useThemeStore } from "@/stores/theme";
|
|
|
import { PRESET_THEMES } from "@/stores/theme/buildThemeVars.js";
|
|
|
import { useOrderDialogStore } from "@/stores/orderDialog";
|
|
|
import { useSignInDialogStore } from "@/stores/signInDialog";
|
|
|
import { useRewardResultStore } from "@/stores/rewardResult";
|
|
|
import { useAssetDialogStore } from "@/stores/assetDialog";
|
|
|
import OrderFormDialog from "@/views/hospital/orders/OrderFormDialog.vue";
|
|
|
import PasswordDialog from "@/components/PasswordDialog.vue";
|
|
|
import SignInDialog from "@/views/hospital/SignInDialog.vue";
|
|
|
import RewardResultDialog from "@/components/RewardResultDialog.vue";
|
|
|
import AssetsDialog from "@/views/hospital/AssetsDialog.vue";
|
|
|
import { getPoints, getPointsDetails } from "@/service/modular/hospital";
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
|
// 头像(按性别)
|
|
|
import avatarMale from "@/assets/image/avatar-male.svg";
|
|
|
import avatarFemale from "@/assets/image/avatar-female.svg";
|
|
|
|
|
|
const route = useRoute();
|
|
|
const router = useRouter();
|
|
|
const userStore = useUserStore();
|
|
|
const themeStore = useThemeStore();
|
|
|
const orderDialogStore = useOrderDialogStore();
|
|
|
const signInDialogStore = useSignInDialogStore();
|
|
|
const rewardResultStore = useRewardResultStore();
|
|
|
const assetDialogStore = useAssetDialogStore();
|
|
|
|
|
|
// 当前是否为医院端(用于切换头部布局样式)
|
|
|
const isHospitalMode = computed(() => route.path.startsWith("/hospital"));
|
|
|
|
|
|
// 头部快速提交 / 列表编辑成功后通知当前路由对应的列表页刷新
|
|
|
const onOrderDialogSuccess = () => {
|
|
|
// 触发全局事件,由 list.vue / detail.vue 监听
|
|
|
window.dispatchEvent(new CustomEvent("hospital-order-saved"));
|
|
|
};
|
|
|
|
|
|
// 全局签到弹窗:签到成功后(已由 SignInDialog 自己 fireSigned + open reward result)
|
|
|
// 这里只负责同步刷新 summary 给后续 /hospital/points 页面用
|
|
|
const onSignInDialogSuccess = () => {
|
|
|
signInDialogStore.refreshSummary();
|
|
|
// 通知积分页同步刷新(向后兼容)
|
|
|
window.dispatchEvent(new CustomEvent("hospital-signin-success"));
|
|
|
};
|
|
|
|
|
|
// 主题弹窗显隐
|
|
|
const themeDialogVisible = ref(false);
|
|
|
|
|
|
// 修改密码弹窗显隐
|
|
|
const passwordDialogVisible = ref(false);
|
|
|
// 修改密码:根据当前所在端选择对应接口
|
|
|
const passwordRole = computed(() => (isHospitalMode.value ? "hospital" : "admin"));
|
|
|
|
|
|
// 注册全局回调(只在第一次挂载时注册)
|
|
|
onMounted(() => {
|
|
|
// 1) 签到成功(外层) -> 关闭签到弹窗 + 打开全局奖励结果弹窗
|
|
|
// (SignInDialog 内部已经自己关闭 + 打开 result; 这里只刷新 summary)
|
|
|
if (typeof signInDialogStore.onSigned !== "function" || !signInDialogStore.onSigned.__layoutHook) {
|
|
|
const hook = async () => {
|
|
|
await signInDialogStore.refreshSummary();
|
|
|
};
|
|
|
hook.__layoutHook = true;
|
|
|
signInDialogStore.onSigned = hook;
|
|
|
}
|
|
|
|
|
|
// 2) 奖励结果弹窗"查看我的资产" -> 取最新数据后打开 AssetsDialog
|
|
|
if (typeof rewardResultStore.onViewAccount !== "function" || !rewardResultStore.onViewAccount.__layoutHook) {
|
|
|
const hook = async () => {
|
|
|
await openAssetsDialogWithFreshData();
|
|
|
};
|
|
|
hook.__layoutHook = true;
|
|
|
rewardResultStore.onViewAccount = hook;
|
|
|
}
|
|
|
|
|
|
// 3) 医院端:第一次进入 layout 时检查一次今日签到状态
|
|
|
if (!userStore.token) return;
|
|
|
if (userStore.userInfo?.userType !== "Hospital") return;
|
|
|
signInDialogStore.checkAndShow();
|
|
|
});
|
|
|
|
|
|
// 打开 AssetsDialog 时先拉最新积分和今日明细
|
|
|
const openAssetsDialogWithFreshData = async () => {
|
|
|
try {
|
|
|
const [summaryRes, detailsRes] = await Promise.all([
|
|
|
getPoints(),
|
|
|
getPointsDetails({ page: 1, pageSize: 50 }),
|
|
|
]);
|
|
|
const summary = summaryRes?.data ?? summaryRes ?? {};
|
|
|
const detailsData = detailsRes?.data ?? detailsRes ?? {};
|
|
|
const list = detailsData.list || [];
|
|
|
|
|
|
const today = dayjs().format("YYYY-MM-DD");
|
|
|
const sumBy = (test) =>
|
|
|
list
|
|
|
.filter(
|
|
|
(r) =>
|
|
|
r.createdAt &&
|
|
|
String(r.createdAt).startsWith(today) &&
|
|
|
test(r.source),
|
|
|
)
|
|
|
.reduce(
|
|
|
(s, r) => s + Math.abs(Number(r.changeAmount) || 0),
|
|
|
0,
|
|
|
);
|
|
|
|
|
|
assetDialogStore.open({
|
|
|
totalPoints: Number(summary.totalPoints) || 0,
|
|
|
todayOrder: sumBy((s) => /工单|order/i.test(s || "")),
|
|
|
todaySignIn: sumBy((s) => /签到|签/i.test(s || "")),
|
|
|
todayDraw: sumBy((s) => /抽奖|抽/i.test(s || "")),
|
|
|
});
|
|
|
} catch (_) {
|
|
|
assetDialogStore.open();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 预设主题配置:颜色 / 渐变端点 / 阴影 / 名称 / 描述均来自 buildThemeVars.PRESET_THEMES;
|
|
|
// preview 按当前 brandColor + gradientTo 组装,保持 to bottom(卡片圆球视觉)。
|
|
|
const presetThemes = computed(() =>
|
|
|
Object.entries(PRESET_THEMES).map(
|
|
|
([value, { borderColor, gradientTo, boxShadow, name, desc }]) => ({
|
|
|
value,
|
|
|
borderColor,
|
|
|
preview: `linear-gradient(to bottom, ${borderColor} 0%, ${gradientTo} 100%)`,
|
|
|
boxShadow,
|
|
|
name,
|
|
|
desc,
|
|
|
}),
|
|
|
),
|
|
|
);
|
|
|
|
|
|
// 选择预设主题
|
|
|
const handlePresetTheme = (value) => {
|
|
|
themeStore.setTheme(value);
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 顶部水平菜单:根据当前顶层路由(/admin 或 /hospital)动态生成
|
|
|
// 图标 name 直接使用 meta.icon(去掉 admin-/hospital- 前缀后即为 SvgIcon 的 name)
|
|
|
const sideRoutes = computed(() => {
|
|
|
const groupPath = isHospitalMode.value ? "/hospital" : "/admin";
|
|
|
const groupRoute = route.matched.find((r) => r.path === groupPath);
|
|
|
if (!groupRoute || !groupRoute.children) return [];
|
|
|
return groupRoute.children
|
|
|
.filter((c) => c.meta?.title && !c.meta?.hidden && !c.path.includes(":"))
|
|
|
.map((c) => ({
|
|
|
path: `${groupPath}/${c.path}`,
|
|
|
title: c.meta.title,
|
|
|
iconName: (c.meta?.icon || "").replace(/^(admin|hospital)-/, ""),
|
|
|
}));
|
|
|
});
|
|
|
|
|
|
const currentTitle = computed(() => {
|
|
|
const last = [...route.matched].reverse().find((r) => r.meta?.title);
|
|
|
return last?.meta?.title || "管理端";
|
|
|
});
|
|
|
|
|
|
// 头像:sex === 1 用男,否则用女
|
|
|
const avatarSrc = computed(() => {
|
|
|
return userStore.userInfo?.gender === '女' ? avatarFemale : avatarMale;
|
|
|
});
|
|
|
|
|
|
const handleCommand = async (cmd) => {
|
|
|
if (cmd === "password") {
|
|
|
passwordDialogVisible.value = true;
|
|
|
return;
|
|
|
}
|
|
|
if (cmd === "theme") {
|
|
|
themeDialogVisible.value = true;
|
|
|
return;
|
|
|
}
|
|
|
if (cmd === "logout") {
|
|
|
try {
|
|
|
await ElMessageBox.confirm("确定要退出登录吗?", "提示", {
|
|
|
confirmButtonText: "确定",
|
|
|
cancelButtonText: "取消",
|
|
|
type: "warning",
|
|
|
});
|
|
|
userStore.clear();
|
|
|
router.push("/login");
|
|
|
} catch (_) {
|
|
|
/* canceled */
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
.admin-layout {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
width: 100vw;
|
|
|
height: 100vh;
|
|
|
background-color: #f9faff;
|
|
|
padding: 32px;
|
|
|
gap: 23px;
|
|
|
background-image: url("@/assets/image/layout-bg.svg");
|
|
|
background-repeat: no-repeat;
|
|
|
background-size: 100% auto;
|
|
|
background-position: right top;
|
|
|
|
|
|
.admin-header {
|
|
|
flex-shrink: 0;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
padding: 0;
|
|
|
height: 96px;
|
|
|
border-radius: 16px 16px 16px 16px;
|
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
|
background: linear-gradient(to right, #f9faff 0, #f9faff 75%, #a6cffe 100%);
|
|
|
background: linear-gradient(to right, #f9faff 0, #f9faff 75%, var(--el-color-primary-light-8) 100%);
|
|
|
|
|
|
.logo-bar {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
padding: 0 20px;
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
white-space: nowrap;
|
|
|
height: 100%;
|
|
|
|
|
|
.logo-text {
|
|
|
background: var(--linear-right);
|
|
|
-webkit-background-clip: text;
|
|
|
-webkit-text-fill-color: transparent;
|
|
|
font-size: 22px;
|
|
|
|
|
|
.version {
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.header-middle {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
height: 100%;
|
|
|
min-width: 0;
|
|
|
}
|
|
|
|
|
|
.header-quick-submit {
|
|
|
margin-left: 14px;
|
|
|
}
|
|
|
|
|
|
.quick-submit-btn {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
height: 36px;
|
|
|
padding: 0 16px;
|
|
|
border-radius: 18px;
|
|
|
font-weight: 500;
|
|
|
// background: linear-gradient(90deg, #3289FB 0%, #00B2FF 100%);
|
|
|
background: var(--linear-right);
|
|
|
border-radius: 28px 28px 28px 28px !important;
|
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
|
width: 155px;
|
|
|
height: 43px;
|
|
|
font-weight: 400;
|
|
|
font-size: 14px;
|
|
|
color: #FFFFFF;
|
|
|
|
|
|
.quick-icon {
|
|
|
width: 16px;
|
|
|
height: 16px;
|
|
|
margin-right: 4px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.header-right {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
height: 100%;
|
|
|
padding-right: 30px;
|
|
|
|
|
|
.user-trigger {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
cursor: pointer;
|
|
|
font-weight: 600;
|
|
|
font-size: 16px;
|
|
|
color: #000000;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.header-menu {
|
|
|
height: 100%;
|
|
|
flex-shrink: 0;
|
|
|
flex-grow: 0;
|
|
|
min-width: 0;
|
|
|
|
|
|
background: transparent !important;
|
|
|
border-bottom: none !important;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
align-items: center !important;
|
|
|
|
|
|
// 顶部一级菜单
|
|
|
:deep(.el-menu-item) {
|
|
|
font-weight: 400;
|
|
|
font-size: 16px;
|
|
|
color: #1d2129 !important;
|
|
|
height: 60px;
|
|
|
line-height: 60px;
|
|
|
border-bottom: none !important;
|
|
|
padding: 0 14px !important;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
|
|
|
&:hover {
|
|
|
// background-color: rgba(50, 137, 251, 0.08) !important;
|
|
|
color: var(--brand-color) !important;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 一级激活
|
|
|
:deep(.el-menu-item.is-active) {
|
|
|
font-weight: 400;
|
|
|
font-size: 16px;
|
|
|
color: var(--brand-color) !important;
|
|
|
background-color: transparent !important;
|
|
|
border-bottom: none !important;
|
|
|
|
|
|
&::after {
|
|
|
display: none !important;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.admin-content {
|
|
|
flex: 1;
|
|
|
overflow-x: hidden;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.nav-icon {
|
|
|
width: 18px;
|
|
|
height: 18px;
|
|
|
margin-right: 6px;
|
|
|
vertical-align: middle;
|
|
|
object-fit: contain;
|
|
|
flex-shrink: 0;
|
|
|
color: currentColor;
|
|
|
}
|
|
|
|
|
|
// 医院端 Element Plus 图标尺寸
|
|
|
.el-icon.nav-icon {
|
|
|
width: 18px;
|
|
|
height: 18px;
|
|
|
font-size: 18px;
|
|
|
line-height: 18px;
|
|
|
color: inherit;
|
|
|
|
|
|
:deep(svg) {
|
|
|
width: 18px;
|
|
|
height: 18px;
|
|
|
}
|
|
|
}
|
|
|
</style>
|
|
|
|
|
|
<style lang="scss">
|
|
|
.admin-layout {
|
|
|
|
|
|
.el-menu-item:not(.is-disabled):focus,
|
|
|
.el-menu--horizontal .el-menu-item:not(.is-disabled):hover {
|
|
|
background-color: transparent !important;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.theme-dialog {
|
|
|
width: max-content;
|
|
|
border-radius: 24px 24px 24px 24px;
|
|
|
|
|
|
.theme-body {
|
|
|
padding: 24px 24px 33px;
|
|
|
border-radius: 24px 24px 24px 24px;
|
|
|
background-color: #fff;
|
|
|
width: max-content;
|
|
|
overflow: hidden;
|
|
|
|
|
|
.theme-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
font-weight: 600;
|
|
|
font-size: 20px;
|
|
|
color: #1D2129;
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
.theme-title {
|
|
|
font-weight: 600;
|
|
|
font-size: 20px;
|
|
|
color: #1D2129;
|
|
|
}
|
|
|
|
|
|
.close-btn {
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.theme-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
gap: 16px;
|
|
|
|
|
|
.theme-card {
|
|
|
width: 126px;
|
|
|
height: 162px;
|
|
|
border-radius: 12px 12px 12px 12px;
|
|
|
border: 1px solid #E0E0E0;
|
|
|
padding: 15px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.25s ease;
|
|
|
text-align: center;
|
|
|
background: #fff;
|
|
|
white-space: nowrap;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
|
|
|
&:hover {
|
|
|
border-color: var(--brand-color);
|
|
|
}
|
|
|
|
|
|
&.active {
|
|
|
border-color: var(--brand-color);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.theme-preview {
|
|
|
width: 64px;
|
|
|
height: 64px;
|
|
|
border: 4px solid #FFFFFF;
|
|
|
border-radius: 50%;
|
|
|
margin-bottom: 12px;
|
|
|
}
|
|
|
|
|
|
.theme-name {
|
|
|
font-weight: 600;
|
|
|
font-size: 14px;
|
|
|
color: #1D2129;
|
|
|
margin-bottom: 4px;
|
|
|
}
|
|
|
|
|
|
.theme-desc {
|
|
|
font-weight: 400;
|
|
|
font-size: 12px;
|
|
|
color: #666666;
|
|
|
white-space: pre-line;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</style>
|