+
+
{{ item.title }}
{{ row.title }}
+ />
{{ row.title }}
@@ -341,7 +357,9 @@
v-model="editVisible"
:schedule="editingItem"
:default-date="selectedDate"
+ :draft="pendingDraft"
@saved="reloadAll"
+ @draft-applied="pendingDraft = null"
/>
@@ -648,6 +666,8 @@ const editVisible = ref(false);
const editingItem = ref(null);
const detailVisible = ref(false);
const detailItem = ref(null);
+// 从通知设置页返回时恢复的草稿(含提醒设置)
+const pendingDraft = ref(null);
function openCreate(date) {
editingItem.value = null;
@@ -842,6 +862,22 @@ function priorityTagType(priority) {
return { 1: "warning", 2: "danger" }[priority] || "info";
}
+// 判断日程是否关联了图片(与详情页 parseImages 语义一致),用于列表标题前展示图片图标
+function hasImages(item) {
+ const imgs = item?.images;
+ if (!imgs) return false;
+ if (Array.isArray(imgs)) return imgs.some(img => img && img.url);
+ if (typeof imgs === "string" && imgs.trim()) {
+ try {
+ const parsed = JSON.parse(imgs);
+ return Array.isArray(parsed) && parsed.some(img => img && img.url);
+ } catch {
+ return false;
+ }
+ }
+ return false;
+}
+
// 排序权重:紧急(2) > 重要(1) > 普通(0)
function priorityWeight(priority) {
return { 2: 3, 1: 2 }[priority] || 1;
@@ -864,10 +900,23 @@ function compareByPriority(a, b) {
onMounted(() => {
loadStats();
loadCalendarData();
+ // 从通知设置页返回:恢复上次暂存的新建日程草稿
+ const raw = sessionStorage.getItem("oa_schedule_draft");
+ if (raw) {
+ try {
+ pendingDraft.value = JSON.parse(raw);
+ sessionStorage.removeItem("oa_schedule_draft");
+ editingItem.value = null;
+ editVisible.value = true;
+ ElMessage.info("已从暂存恢复未提交的新建日程");
+ } catch {
+ sessionStorage.removeItem("oa_schedule_draft");
+ }
+ }
});
-
diff --git a/backend/src/views/apps/organization/components/OrganizationPage.vue b/backend/src/views/apps/organization/components/OrganizationPage.vue
index 0d1f79f..1a97534 100644
--- a/backend/src/views/apps/organization/components/OrganizationPage.vue
+++ b/backend/src/views/apps/organization/components/OrganizationPage.vue
@@ -614,4 +614,32 @@ onMounted(() => {
width: 100%;
}
}
+
+// ===== 暗色主题适配 =====
+html.dark & {
+ .page-header {
+ h2 {
+ color: var(--el-text-color-primary);
+ }
+ p {
+ color: var(--el-text-color-secondary);
+ }
+ }
+
+ .stat-value {
+ color: var(--el-text-color-primary);
+ }
+
+ .stat-label {
+ color: var(--el-text-color-secondary);
+ }
+
+ .node-label {
+ color: var(--el-text-color-primary);
+ }
+
+ .section-title {
+ color: var(--el-text-color-primary);
+ }
+}
diff --git a/backend/src/views/basicSettings/roles/components/detail.vue b/backend/src/views/basicSettings/roles/components/detail.vue
index 188f23c..c9a97dd 100644
--- a/backend/src/views/basicSettings/roles/components/detail.vue
+++ b/backend/src/views/basicSettings/roles/components/detail.vue
@@ -13,12 +13,17 @@
-
- {{ roleDetail.cid === 1 ? "平台角色" : "租户角色" }}
+ 平台角色
+
+ 自定义角色
+ 租户角色
-
+
+ 全权限(未做限制)
+
+
import { ref, watch, computed } from "vue";
import { ElMessage } from "element-plus";
-import { getPlatformRoleById } from "@/api/platformRole";
+import { getRoleById } from "@/api/role";
import { getAllMenus } from "@/api/menu";
interface Props {
@@ -76,7 +81,7 @@ watch(
visible.value = val;
if (val && props.roleId) {
await loadRoleDetail();
- await loadMenus(roleDetail.value.cid);
+ await loadMenus();
}
}
);
@@ -132,7 +137,7 @@ const loadRoleDetail = async () => {
loading.value = true;
try {
- const res = await getPlatformRoleById(props.roleId);
+ const res = await getRoleById(props.roleId);
if (res.code === 200) {
roleDetail.value = res.data || {};
} else {
@@ -146,12 +151,10 @@ const loadRoleDetail = async () => {
}
};
-// 加载菜单树:租户角色(cid=2)只展示租户端菜单;平台角色展示全部权限
-const loadMenus = async (cid?: number) => {
+// 加载菜单树:租户端只展示租户端菜单(cid=2)
+const loadMenus = async () => {
try {
- const isTenant = cid === undefined || cid === 2;
- const params = isTenant ? { cid: 2 } : {};
- const res = await getAllMenus(params);
+ const res = await getAllMenus({ cid: 2 });
if (res.code === 200) {
allMenus.value = res.data || [];
}
diff --git a/backend/src/views/basicSettings/roles/components/edit.vue b/backend/src/views/basicSettings/roles/components/edit.vue
index d3bbe49..f28e048 100644
--- a/backend/src/views/basicSettings/roles/components/edit.vue
+++ b/backend/src/views/basicSettings/roles/components/edit.vue
@@ -1,5 +1,10 @@
-
+
@@ -12,12 +17,13 @@
-
-
- 租户角色
- 平台角色
-
- 租户角色仅显示租户端菜单;平台角色显示全部权限
+
+
+ {{ form.is_custom ? "自定义角色" : "系统预置租户角色" }}
+
+
+ 当前为租户端,仅可维护本租户的自定义角色:权限范围限定在租户端菜单,数据按租户隔离,平台端不可见。
+
@@ -46,7 +52,7 @@
@@ -339,6 +354,32 @@ onMounted(() => {
font-weight: 600;
color: @text-main;
}
+
+ .tenant-divider {
+ width: 1px;
+ height: 16px;
+ background: @text-regular;
+ opacity: 0.25;
+ margin: 0 4px;
+ }
+
+ .tenant-name {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 15px;
+ font-weight: 500;
+ color: @text-regular;
+ max-width: 220px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ .tenant-icon {
+ font-size: 16px;
+ color: #3973ff;
+ }
+ }
}
.toolbar-right {
@@ -738,6 +779,12 @@ onMounted(() => {
}
}
}
+
+ .toolbar-left {
+ .tenant-name {
+ display: none;
+ }
+ }
}
}
@@ -791,6 +838,14 @@ onMounted(() => {
color: #e0e0e0;
}
+ .toolbar-left .tenant-name {
+ color: #b0b0b0;
+
+ .tenant-icon {
+ color: #5a8dff;
+ }
+ }
+
.toolbar-right {
.toolbar-btn {
background: #2d2d2d;
diff --git a/go/controllers/backend_oa_schedule.go b/go/controllers/backend_oa_schedule.go
index 0de76aa..c1c5f24 100644
--- a/go/controllers/backend_oa_schedule.go
+++ b/go/controllers/backend_oa_schedule.go
@@ -2,6 +2,7 @@ package controllers
import (
"encoding/json"
+ "fmt"
"io"
"regexp"
"strconv"
@@ -289,14 +290,103 @@ func (c *BackendOaScheduleController) Stats() {
}
type oaSchedulePayload struct {
- Title string `json:"title"`
- Content string `json:"content"`
- ScheduleDate string `json:"schedule_date"`
- StartTime string `json:"start_time"`
- EndTime string `json:"end_time"`
- AllDay int8 `json:"all_day"`
- Color string `json:"color"`
- Priority int8 `json:"priority"`
+ Title string `json:"title"`
+ Content string `json:"content"`
+ ScheduleDate string `json:"schedule_date"`
+ StartTime string `json:"start_time"`
+ EndTime string `json:"end_time"`
+ AllDay int8 `json:"all_day"`
+ Color string `json:"color"`
+ Priority int8 `json:"priority"`
+ // 提醒绑定:开启后会在提醒工具生成一条提醒任务,并把其 ID 回写到 ReminderID。
+ ReminderEnabled bool `json:"reminder_enabled"`
+ ReminderTime string `json:"reminder_time"`
+ RemindChannels []string `json:"remind_channels"`
+ AdvanceMinutes int `json:"advance_minutes"`
+}
+
+// oaReminderChannels 允许的提醒渠道集合。
+var oaReminderChannels = map[string]bool{
+ "SMS": true, "EMAIL": true, "BARK": true, "SITE_MSG": true,
+}
+
+// createOaScheduleReminder 为 OA 日程创建一条绑定的提醒任务。
+// 复用提醒工具的数据模型(yz_backend_schedule + yz_backend_schedule_reminder):
+// 生成一条 BackendSchedule,再按渠道逐条生成提醒记录,返回 BackendSchedule.ID
+// 作为 OA 日程的 reminder_id。EMAIL/BARK 渠道会附带确认回执令牌与重复发送配置。
+func createOaScheduleReminder(tid int, userID uint64, title, content, reminderTime string, channels []string, advanceMinutes int) (uint64, error) {
+ if strings.TrimSpace(reminderTime) == "" {
+ return 0, fmt.Errorf("提醒时间不能为空")
+ }
+ schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", reminderTime, time.Local)
+ if err != nil {
+ return 0, fmt.Errorf("提醒时间格式不合法")
+ }
+ if advanceMinutes < 0 {
+ advanceMinutes = 0
+ }
+ title = strings.TrimSpace(title)
+ if title == "" {
+ title = "日程提醒"
+ }
+
+ bs := models.BackendSchedule{
+ Tid: tid,
+ Title: title,
+ Content: content,
+ ScheduleTime: schedTime,
+ UserID: userID,
+ }
+ schedID, err := models.Orm.Insert(&bs)
+ if err != nil {
+ return 0, fmt.Errorf("创建提醒任务失败: %v", err)
+ }
+
+ now := time.Now()
+ for _, ch := range channels {
+ ch = strings.ToUpper(strings.TrimSpace(ch))
+ if !oaReminderChannels[ch] {
+ continue
+ }
+ firstSend := schedTime.Add(-time.Duration(advanceMinutes) * time.Minute)
+ reminder := models.BackendScheduleReminder{
+ Tid: tid,
+ ScheduleID: uint64(schedID),
+ RemindChannel: ch,
+ AdvanceMinutes: advanceMinutes,
+ NextRemindTime: firstSend,
+ ReceiverUserID: userID,
+ RemindStatus: 0,
+ CreateTime: now,
+ UpdateTime: now,
+ }
+ if ch == "EMAIL" || ch == "BARK" {
+ token := backendGenerateToken()
+ reminder.AckToken = &token
+ reminder.RepeatIntervalMinutes = 10
+ reminder.MaxSendCount = 5
+ } else {
+ reminder.RepeatIntervalMinutes = 0
+ reminder.MaxSendCount = 1
+ }
+ if _, err := models.Orm.Insert(&reminder); err != nil {
+ return 0, fmt.Errorf("创建提醒失败: %v", err)
+ }
+ }
+ return uint64(schedID), nil
+}
+
+// deleteBoundReminder 软删除 OA 日程绑定的提醒任务及其提醒记录。
+// 删除日程或关闭提醒时使用,避免提醒工具里残留孤立任务。
+func deleteBoundReminder(scheduleID uint64) {
+ if scheduleID == 0 {
+ return
+ }
+ _, _ = models.Orm.QueryTable(new(models.BackendSchedule)).
+ Filter("id", scheduleID).Delete()
+ _, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
+ Filter("schedule_id", scheduleID).
+ Update(map[string]interface{}{"IsDeleted": 1, "UpdateTime": time.Now()})
}
// oaScheduleImage 日程相关图片(截图/照片),只存文件引用,不存二进制。
@@ -393,6 +483,29 @@ func (c *BackendOaScheduleController) parseOaSchedulePayload() (oaSchedulePayloa
if payload.Color == "" {
payload.Color = "#409EFF"
}
+
+ // 提醒绑定校验:开启提醒时必须选择渠道且提醒时间合法。
+ if payload.ReminderEnabled {
+ validChannels := make([]string, 0, len(payload.RemindChannels))
+ for _, ch := range payload.RemindChannels {
+ ch = strings.ToUpper(strings.TrimSpace(ch))
+ if oaReminderChannels[ch] {
+ validChannels = append(validChannels, ch)
+ }
+ }
+ payload.RemindChannels = validChannels
+ if len(validChannels) == 0 {
+ c.oasJsonErr(400, 400, "请至少选择一个提醒渠道")
+ return payload, false
+ }
+ if _, err := time.ParseInLocation("2006-01-02 15:04:05", payload.ReminderTime, time.Local); err != nil {
+ c.oasJsonErr(400, 400, "提醒时间格式不合法,支持 YYYY-MM-DD HH:mm:ss")
+ return payload, false
+ }
+ if payload.AdvanceMinutes < 0 {
+ payload.AdvanceMinutes = 0
+ }
+ }
return payload, true
}
@@ -432,6 +545,30 @@ func (c *BackendOaScheduleController) Create() {
return
}
+ // 开启提醒:生成绑定的提醒任务,并把其 ID 回写到日程。
+ if payload.ReminderEnabled {
+ rid, rerr := createOaScheduleReminder(
+ claims.TenantId,
+ uint64(claims.UserID),
+ payload.Title,
+ payload.Content,
+ payload.ReminderTime,
+ payload.RemindChannels,
+ payload.AdvanceMinutes,
+ )
+ if rerr != nil {
+ // 回滚已创建的日程,避免产生孤立日程
+ _, _ = models.Orm.QueryTable(new(models.OaSchedule)).Filter("id", item.ID).Delete()
+ c.oasJsonErr(500, 500, rerr.Error())
+ return
+ }
+ item.ReminderID = rid
+ if _, err := models.Orm.Update(item, "ReminderID"); err != nil {
+ c.oasJsonErr(500, 500, "保存失败")
+ return
+ }
+ }
+
c.oasOk(item)
}
@@ -477,9 +614,34 @@ func (c *BackendOaScheduleController) Update() {
item.Priority = payload.Priority
item.UpdateTime = &now
+ // 重新绑定提醒:先创建新提醒,成功后再删除旧提醒(关闭提醒则清掉绑定)。
+ // 顺序反过来可避免"新提醒创建失败却删掉了原提醒"导致提醒丢失。
+ if payload.ReminderEnabled {
+ rid, rerr := createOaScheduleReminder(
+ claims.TenantId,
+ uint64(claims.UserID),
+ payload.Title,
+ payload.Content,
+ payload.ReminderTime,
+ payload.RemindChannels,
+ payload.AdvanceMinutes,
+ )
+ if rerr != nil {
+ c.oasJsonErr(500, 500, rerr.Error())
+ return
+ }
+ if item.ReminderID > 0 {
+ deleteBoundReminder(item.ReminderID)
+ }
+ item.ReminderID = rid
+ } else if item.ReminderID > 0 {
+ deleteBoundReminder(item.ReminderID)
+ item.ReminderID = 0
+ }
+
if _, err := models.Orm.Update(&item,
"Title", "Content", "ScheduleDate", "StartTime", "EndTime",
- "AllDay", "Color", "Priority", "UpdateTime"); err != nil {
+ "AllDay", "Color", "Priority", "ReminderID", "UpdateTime"); err != nil {
c.oasJsonErr(500, 500, "保存失败")
return
}
@@ -555,21 +717,31 @@ func (c *BackendOaScheduleController) Delete() {
}
now := time.Now()
- n, err := models.Orm.QueryTable(new(models.OaSchedule)).
+ var item models.OaSchedule
+ err = models.Orm.QueryTable(new(models.OaSchedule)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
- Update(map[string]interface{}{
- "IsDeleted": int8(1),
- "DeleteTime": now,
- "UpdateTime": now,
- })
- if err != nil || n == 0 {
+ One(&item)
+ if err != nil {
c.oasJsonErr(404, 404, "日程不存在或已删除")
return
}
+ // 先删除绑定的提醒任务,避免提醒工具里残留孤立任务
+ if item.ReminderID > 0 {
+ deleteBoundReminder(item.ReminderID)
+ }
+
+ item.IsDeleted = 1
+ item.DeleteTime = &now
+ item.UpdateTime = &now
+ if _, err := models.Orm.Update(&item, "IsDeleted", "DeleteTime", "UpdateTime"); err != nil {
+ c.oasJsonErr(500, 500, "删除失败")
+ return
+ }
+
c.oasOk(nil)
}
diff --git a/go/controllers/backend_role.go b/go/controllers/backend_role.go
index 0baaa0e..8771d3c 100644
--- a/go/controllers/backend_role.go
+++ b/go/controllers/backend_role.go
@@ -138,9 +138,11 @@ func (c *BackendRoleController) CreateRole() {
status = *p.Status
}
rights := normalizeRights(p.Rights)
+ // 租户端创建的角色一律标记为“自定义角色”,与系统/平台预置角色区分,并按租户隔离
role := &models.AdminRole{
TenantID: tid,
Cid: 2,
+ IsCustom: 1,
Name: p.Name,
Status: status,
Rights: rights,
diff --git a/go/models/admin_role.go b/go/models/admin_role.go
index 2148189..f9f5bf4 100644
--- a/go/models/admin_role.go
+++ b/go/models/admin_role.go
@@ -7,6 +7,7 @@ type AdminRole struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID uint64 `orm:"column(tenant_id);default(0)" json:"tenant_id"` // 租户ID:0=平台/全局,>0=具体租户(仅 cid=2 租户角色使用)
Cid uint8 `orm:"column(cid);default(1)" json:"cid"` // 1平台角色 2租户角色
+ IsCustom uint8 `orm:"column(is_custom);default(0)" json:"is_custom"` // 是否租户自定义角色:0=系统/平台预置 1=租户在租户端自建(数据按租户隔离)
Name string `orm:"column(name);size(32)" json:"name"`
Status uint8 `orm:"column(status);default(1)" json:"status"`
Rights *string `orm:"column(rights);type(text);null" json:"rights"`
diff --git a/go/models/admin_role_default.go b/go/models/admin_role_default.go
index 2b251f0..8e4701a 100644
--- a/go/models/admin_role_default.go
+++ b/go/models/admin_role_default.go
@@ -7,6 +7,7 @@ import (
)
var adminRoleTenantColOnce sync.Once
+var adminRoleCustomColOnce sync.Once
// EnsureAdminRoleTenantColumn 为角色表补齐 tenant_id 列(历史库可能早于租户隔离特性建表)。
// 字段已存在时 MySQL 报 duplicate column,统一忽略,不阻断主流程。
@@ -22,6 +23,20 @@ func EnsureAdminRoleTenantColumn() {
})
}
+// EnsureAdminRoleCustomColumn 为角色表补齐 is_custom 列(历史库可能早于自定义角色特性建表)。
+// 字段已存在时 MySQL 报 duplicate column,统一忽略,不阻断主流程。
+func EnsureAdminRoleCustomColumn() {
+ if Orm == nil {
+ return
+ }
+ adminRoleCustomColOnce.Do(func() {
+ _, _ = Orm.Raw(`
+ ALTER TABLE yz_system_admin_role
+ ADD COLUMN is_custom tinyint unsigned NOT NULL DEFAULT 0 COMMENT '自定义角色 0-系统/平台预置 1-租户自建'
+ `).Exec()
+ })
+}
+
// EnsureDefaultTenantRoles 保证指定租户(cid=2)的默认角色齐全:
// - 租户管理员:全权限(rights 为空,见 permission.go 空权限即放行)
// - 部门负责人
@@ -33,8 +48,9 @@ func EnsureDefaultTenantRoles(tid uint64) {
if Orm == nil || tid == 0 {
return
}
- // 确保 tenant_id 列存在(兼容老表)
+ // 确保 tenant_id / is_custom 列存在(兼容老表)
EnsureAdminRoleTenantColumn()
+ EnsureAdminRoleCustomColumn()
defaults := []string{"租户管理员", "部门负责人", "员工"}
for _, name := range defaults {
diff --git a/go/models/init.go b/go/models/init.go
index 0410a71..ebd6114 100644
--- a/go/models/init.go
+++ b/go/models/init.go
@@ -123,4 +123,8 @@ func Init(_ string) {
// 创建全局 Ormer
Orm = orm.NewOrm()
+
+ // 补齐历史库缺失的列(已存在时 MySQL 报 duplicate column,统一忽略)
+ EnsureAdminRoleTenantColumn()
+ EnsureAdminRoleCustomColumn()
}
diff --git a/go/models/oa_schedule.go b/go/models/oa_schedule.go
index 982563c..f03fd3e 100644
--- a/go/models/oa_schedule.go
+++ b/go/models/oa_schedule.go
@@ -35,6 +35,9 @@ type OaSchedule struct {
CarryOver int8 `orm:"column(carry_over);default(0)" json:"carry_over"`
CarryCount int `orm:"column(carry_count);default(0)" json:"carry_count"`
CarryToDate string `orm:"column(carry_to_date);size(10);default()" json:"carry_to_date"`
+ // ReminderID 绑定到提醒工具的提醒任务(yz_backend_schedule.id),0 表示未开启提醒。
+ // 用于把 OA 日程与提醒任务关联,从而在日程详情回显提醒状态(如"提醒已关闭")。
+ ReminderID uint64 `orm:"column(reminder_id);default(0)" json:"reminder_id"`
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"`
@@ -62,6 +65,7 @@ func EnsureOaScheduleCarryColumns() error {
"ALTER TABLE yz_backend_oa_schedule ADD COLUMN carry_count int NOT NULL DEFAULT 0 COMMENT '累计顺延次数'",
"ALTER TABLE yz_backend_oa_schedule ADD COLUMN carry_to_date varchar(10) NOT NULL DEFAULT '' COMMENT '顺延目标日期(YYYY-MM-DD)'",
"ALTER TABLE yz_backend_oa_schedule ADD COLUMN images text NULL COMMENT '相关图片JSON数组 [{id,url,name}]'",
+ "ALTER TABLE yz_backend_oa_schedule ADD COLUMN reminder_id bigint unsigned NOT NULL DEFAULT 0 COMMENT '绑定的提醒任务ID(对应yz_backend_schedule.id) 0-无提醒'",
}
for _, sql := range alters {
if _, e := Orm.Raw(sql).Exec(); e != nil {
diff --git a/platform/src/views/apps/erp/dashboard/index.vue b/platform/src/views/apps/erp/dashboard/index.vue
index 9983b54..563d21b 100644
--- a/platform/src/views/apps/erp/dashboard/index.vue
+++ b/platform/src/views/apps/erp/dashboard/index.vue
@@ -111,6 +111,10 @@ const getMonthLabels = (): string[] => {
const initChart = (refEl: HTMLElement, title: string, color: string) => {
const chart = echarts.init(refEl);
const months = getMonthLabels();
+ const isDark = document.documentElement.classList.contains('dark');
+ const palette = isDark
+ ? { axis: '#4c4d4f', label: '#a8abb2', split: '#2b2b2d' }
+ : { axis: '#E0E6ED', label: '#606266', split: '#F2F6FC' };
chart.setOption({
tooltip: {
@@ -128,15 +132,15 @@ const initChart = (refEl: HTMLElement, title: string, color: string) => {
type: 'category',
boundaryGap: false,
data: months,
- axisLine: { lineStyle: { color: '#E0E6ED' } },
- axisLabel: { color: '#606266', fontSize: 11 }
+ axisLine: { lineStyle: { color: palette.axis } },
+ axisLabel: { color: palette.label, fontSize: 11 }
},
yAxis: {
type: 'value',
axisLine: { show: false },
axisTick: { show: false },
- splitLine: { lineStyle: { color: '#F2F6FC' } },
- axisLabel: { color: '#606266', fontSize: 11 }
+ splitLine: { lineStyle: { color: palette.split } },
+ axisLabel: { color: palette.label, fontSize: 11 }
},
series: [
{
@@ -326,4 +330,40 @@ onUnmounted(() => {
min-height: 0;
}
}
+// ===== 暗色主题适配 =====
+html.dark & {
+ .erp-dashboard {
+ background-color: var(--el-bg-color-page);
+ }
+
+ .stat-card {
+ background: var(--el-bg-color);
+
+ .stat-header {
+ .stat-label {
+ color: var(--el-text-color-regular);
+ }
+
+ .stat-icon {
+ color: var(--el-text-color-secondary);
+ }
+ }
+
+ .stat-value {
+ .currency,
+ .number {
+ color: var(--el-text-color-primary);
+ }
+ }
+ }
+
+ .chart-card {
+ background: var(--el-bg-color);
+
+ .chart-title {
+ color: var(--el-text-color-primary);
+ border-bottom-color: var(--el-border-color-lighter);
+ }
+ }
+}