feat: 添加主题色设置功能

This commit is contained in:
pikachu1995@126.com
2026-07-07 10:56:14 +08:00
parent df35a26f9e
commit 295aaf1a01
16 changed files with 275 additions and 33 deletions

View File

@@ -6,7 +6,9 @@
<script> <script>
import storage from "@/plugins/storage"; import storage from "@/plugins/storage";
import { getBaseSite } from "@/api/common.js"; import { getBaseSite, getThemeSetting } from "@/api/common.js";
import { fetchAndApplyTheme } from "@/utils/theme";
export default { export default {
name: "App", name: "App",
mounted() { mounted() {
@@ -42,6 +44,7 @@ export default {
}); });
} }
this.getSite(); this.getSite();
this.getTheme();
}, },
getSite() { getSite() {
getBaseSite().then((res) => { getBaseSite().then((res) => {
@@ -55,6 +58,9 @@ export default {
} }
}).catch(() => {}); }).catch(() => {});
}, },
getTheme() {
fetchAndApplyTheme(getThemeSetting).catch(() => {});
},
}, },
}; };
</script> </script>

View File

@@ -94,3 +94,12 @@ export function getBaseSite(){
needToken: false needToken: false
}) })
} }
// 获取主题色配置
export function getThemeSetting() {
return request({
url: `${commonUrl}/common/common/site/theme`,
method: Method.GET,
needToken: false,
});
}

View File

@@ -1,18 +1,9 @@
:root { :root {
// --el-color-primary: #ff5c58; --el-color-primary: var(--theme-color, #ff3c2a);
// --el-color-success: #68cabe; --el-color-danger: var(--theme-color, #ff3c2a);
// --el-color-warning: #fa6419;
// --el-color-danger: #ff3c2a;
--el-font-size-base: 14px; --el-font-size-base: 14px;
} }
// .el-button--primary {
// --el-button-bg-color: #ff5c58;
// --el-button-border-color: #ff5c58;
// --el-button-hover-bg-color: #ff7875;
// --el-button-hover-border-color: #ff7875;
// }
.el-table--border { .el-table--border {
.el-table__cell { .el-table__cell {
border-right: none !important; border-right: none !important;
@@ -20,12 +11,12 @@
} }
.link-text { .link-text {
color: #409eff; color: var(--theme-color, #ff3c2a);
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
&:hover { &:hover {
color: #66b1ff; opacity: 0.85;
} }
} }
@@ -34,16 +25,16 @@
color: #dcdfe6; color: #dcdfe6;
} }
/* 与「我的订单」确认收货按钮一致的橙色操作按钮 */ /* 次要强调按钮,使用高亮主题色(如「立即购买」) */
.confirm-receipt-btn { .confirm-receipt-btn {
background-color: #ff9900 !important; background-color: var(--theme-light, #ff6b35) !important;
border-color: #ff9900 !important; border-color: var(--theme-light, #ff6b35) !important;
color: #fff !important; color: #fff !important;
&:hover, &:hover,
&:focus { &:focus {
background-color: #e68a00 !important; background-color: color-mix(in srgb, var(--theme-light, #ff6b35) 88%, black) !important;
border-color: #e68a00 !important; border-color: color-mix(in srgb, var(--theme-light, #ff6b35) 88%, black) !important;
color: #fff !important; color: #fff !important;
} }
} }

View File

@@ -10,7 +10,7 @@ $warning_color: #ff9900;
$error_color: #ed3f14; $error_color: #ed3f14;
$handle-btn-color: #438cde; $handle-btn-color: #438cde;
$theme_color: #F31947; $theme_color: var(--theme-color, #ff3c2a);
$border_color: #dddee1; $border_color: #dddee1;
$title_color: #8c8c8c; $title_color: #8c8c8c;

View File

@@ -814,7 +814,7 @@ export default {
color: $theme_color; color: $theme_color;
margin: 5px 0; margin: 5px 0;
font-size: 12px; font-size: 12px;
background-color: #ffdedf; background-color: var(--theme-tint-12, #ffdedf);
border: 1px dotted $theme_color; border: 1px dotted $theme_color;
cursor: pointer; cursor: pointer;
@@ -839,7 +839,7 @@ export default {
padding: 0 5px; padding: 0 5px;
color: $theme_color; color: $theme_color;
font-size: 12px; font-size: 12px;
background-color: #ffdedf; background-color: var(--theme-tint-12, #ffdedf);
border: 1px dotted $theme_color; border: 1px dotted $theme_color;
cursor: pointer; cursor: pointer;

View File

@@ -130,7 +130,7 @@ export default {
width: 190px; width: 190px;
height: 100%; height: 100%;
color: #fff; color: #fff;
background: linear-gradient(180deg, #ff5c58 0%, #e84a46 100%); background: linear-gradient(180deg, var(--theme-color, #F31947) 0%, var(--theme-light, #ff6b35) 100%);
.title { .title {
width: 100%; width: 100%;
text-align: center; text-align: center;

View File

@@ -173,7 +173,7 @@ export default {
text-align: center; text-align: center;
letter-spacing: 0px; letter-spacing: 0px;
color: #f31947; color: $theme_color;
-webkit-text-stroke: #979797 0.7px; /* 浏览器可能不支持 */ -webkit-text-stroke: #979797 0.7px; /* 浏览器可能不支持 */
} }
.goods-type-line { .goods-type-line {
@@ -190,7 +190,7 @@ export default {
color: #333333; color: #333333;
} }
.active { .active {
color: #f31947; color: $theme_color;
} }
.goods-type-labels { .goods-type-labels {
cursor: pointer; cursor: pointer;

View File

@@ -85,7 +85,7 @@ export default {
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
line-height: 17px; line-height: 17px;
color: #f31947; color: $theme_color;
margin-top: 8px; margin-top: 8px;
} }
@@ -139,7 +139,7 @@ export default {
height: 27px; height: 27px;
line-height: 27px; line-height: 27px;
border-radius: 13.3px 0 0 13.3px; border-radius: 13.3px 0 0 13.3px;
background: #f31947; background: $theme_color;
font-size: 12.6px; font-size: 12.6px;
text-align: center; text-align: center;
color: #ffffff; color: #ffffff;

View File

@@ -10,6 +10,8 @@ import { setupLegacyMessage } from "@/utils/message";
import { registerGlobalComponents } from "@/components/global.js"; import { registerGlobalComponents } from "@/components/global.js";
import * as filters from "./plugins/filters"; import * as filters from "./plugins/filters";
import storage from "@/plugins/storage"; import storage from "@/plugins/storage";
import { fetchAndApplyTheme } from "@/utils/theme";
import { getThemeSetting } from "@/api/common.js";
const { aMapSecurityJsCode, inputMaxLength } = require("@/config"); const { aMapSecurityJsCode, inputMaxLength } = require("@/config");
@@ -49,6 +51,7 @@ app.config.globalProperties.connectCs = function (
window.open(url, "_blank"); window.open(url, "_blank");
}; };
router.isReady().then(() => { router.isReady().then(async () => {
await fetchAndApplyTheme(getThemeSetting);
app.mount("#app"); app.mount("#app");
}); });

95
buyer/src/utils/theme.js Normal file
View File

@@ -0,0 +1,95 @@
/**
* 平台主题色工具
*/
const DEFAULT_THEME = {
themeColor: "#ff3c2a",
lightColor: "#ff6b35",
aiderLightColor: "#ff9f28",
};
const STORAGE_KEY = "theme_setting";
const EXPIRATION_KEY = "theme_setting_expiration_time";
export function getDefaultTheme() {
return { ...DEFAULT_THEME };
}
function applyElementColorVars(style, prefix, color) {
const key = `--el-color-${prefix}`;
style.setProperty(key, color);
style.setProperty(`${key}-light-3`, `color-mix(in srgb, ${color} 70%, white)`);
style.setProperty(`${key}-light-5`, `color-mix(in srgb, ${color} 50%, white)`);
style.setProperty(`${key}-light-7`, `color-mix(in srgb, ${color} 30%, white)`);
style.setProperty(`${key}-light-8`, `color-mix(in srgb, ${color} 20%, white)`);
style.setProperty(`${key}-light-9`, `color-mix(in srgb, ${color} 10%, white)`);
style.setProperty(`${key}-dark-2`, `color-mix(in srgb, ${color} 80%, black)`);
}
export function applyThemeToDocument(theme) {
if (typeof document === "undefined") return;
const primary = theme.themeColor || DEFAULT_THEME.themeColor;
const light = theme.lightColor || DEFAULT_THEME.lightColor;
const aider = theme.aiderLightColor || DEFAULT_THEME.aiderLightColor;
const root = document.documentElement.style;
root.setProperty("--theme-color", primary);
root.setProperty("--theme-light", light);
root.setProperty("--theme-aider", aider);
root.setProperty("--theme-tint-12", `color-mix(in srgb, ${primary} 12%, white)`);
applyElementColorVars(root, "primary", primary);
// 买家端大量按钮使用 type="danger",统一映射到主题色
applyElementColorVars(root, "danger", primary);
}
export function cacheTheme(theme) {
const expirationTime = new Date().setHours(new Date().getHours() + 1);
localStorage.setItem(EXPIRATION_KEY, String(expirationTime));
localStorage.setItem(STORAGE_KEY, JSON.stringify(theme));
}
export function loadCachedTheme() {
const expirationTime = localStorage.getItem(EXPIRATION_KEY);
const cacheValid =
expirationTime && new Date() <= new Date(Number(expirationTime));
if (!cacheValid) return null;
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : null;
} catch {
return null;
}
}
export function normalizeTheme(data = {}) {
return {
themeColor: data.themeColor || DEFAULT_THEME.themeColor,
lightColor: data.lightColor || DEFAULT_THEME.lightColor,
aiderLightColor: data.aiderLightColor || DEFAULT_THEME.aiderLightColor,
};
}
export async function fetchAndApplyTheme(requestFn) {
const cachedTheme = loadCachedTheme();
if (cachedTheme) {
applyThemeToDocument(normalizeTheme(cachedTheme));
}
try {
const res = await requestFn();
if (res?.success && res?.result?.settingValue) {
const data = JSON.parse(res.result.settingValue);
const normalized = normalizeTheme(data);
applyThemeToDocument(normalized);
cacheTheme(normalized);
return normalized;
}
} catch (error) {
console.warn(
"[theme] 主题色加载失败,请确认 common-api(8890) 已启动且已执行 version4.9to4.10.sql",
error
);
}
return cachedTheme ? normalizeTheme(cachedTheme) : normalizeTheme();
}

View File

@@ -3,10 +3,10 @@ var BASE = {
* @description api请求基础路径 * @description api请求基础路径
*/ */
API_DEV: { API_DEV: {
common: "https://common-api.pickmall.cn", common: "http://127.0.0.1:8890",
buyer: "https://buyer-api.pickmall.cn", buyer: "http://127.0.0.1:8888",
seller: "https://store-api.pickmall.cn", seller: "http://127.0.0.1:8889",
manager: "https://admin-api.pickmall.cn", manager: "http://127.0.0.1:8887",
}, },
API_PROD: { API_PROD: {
common: "https://common-api.pickmall.cn", common: "https://common-api.pickmall.cn",

View File

@@ -349,6 +349,12 @@ export const otherRouter = {
name: "main", name: "main",
component: () => import("@/views/page-decoration/wap/main.vue") component: () => import("@/views/page-decoration/wap/main.vue")
}, },
{
path: "/floorList/theme-setting",
title: "主题色设置",
name: "theme-setting",
component: () => import("@/views/page-decoration/theme-setting.vue")
},
{ {
path: "add-sms-sign", path: "add-sms-sign",
title: "短信签名", title: "短信签名",

View File

@@ -377,7 +377,7 @@ export default {
transition: background-color 0.2s ease, opacity 0.2s ease; transition: background-color 0.2s ease, opacity 0.2s ease;
&:hover { &:hover {
background: darken($theme_color, 4%); background: color-mix(in srgb, #{$theme_color} 96%, black);
opacity: 1; opacity: 1;
} }
} }

View File

@@ -11,6 +11,7 @@
</el-tabs> </el-tabs>
<div class="operation mb_10"> <div class="operation mb_10">
<el-button type="primary" @click="createTemp">添加页面</el-button> <el-button type="primary" @click="createTemp">添加页面</el-button>
<el-button @click="goThemeSetting">主题色设置</el-button>
</div> </div>
<el-table v-loading="loading" :data="list" style="width: 100%"> <el-table v-loading="loading" :data="list" style="width: 100%">
<el-table-column prop="name" label="页面名称" min-width="200" show-overflow-tooltip> <el-table-column prop="name" label="页面名称" min-width="200" show-overflow-tooltip>
@@ -168,6 +169,10 @@ export default {
}); });
}, },
goThemeSetting() {
this.$router.push({ name: "theme-setting" });
},
changePageNum(val) { changePageNum(val) {
this.searchForm.pageNumber = val; this.searchForm.pageNumber = val;
this.getTemplateList(); this.getTemplateList();

View File

@@ -0,0 +1,123 @@
<template>
<div class="theme-setting">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>主题色设置</span>
<span class="desc">配置买家端 PC 与移动端小程序的主题色保存后前台自动生效</span>
</div>
</template>
<el-form
ref="formValidate"
v-loading="loading"
label-width="150px"
label-position="right"
:model="formValidate"
:rules="ruleValidate"
>
<el-form-item label="主题色" prop="themeColor">
<el-color-picker v-model="formValidate.themeColor" />
<span class="field-desc">买家端主色用于按钮价格标签等</span>
</el-form-item>
<el-form-item label="高亮主题色" prop="lightColor">
<el-color-picker v-model="formValidate.lightColor" />
<span class="field-desc">用于渐变高亮背景等</span>
</el-form-item>
<el-form-item label="辅助高亮色" prop="aiderLightColor">
<el-color-picker v-model="formValidate.aiderLightColor" />
<span class="field-desc">用于渐变辅助色次要强调</span>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saveLoading" @click="submit">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import { getSetting, setSetting } from "@/api/index";
const SETTING_KEY = "THEME_SETTING";
export default {
name: "theme-setting",
data() {
return {
loading: false,
saveLoading: false,
formValidate: {
themeColor: "#ff3c2a",
lightColor: "#ff6b35",
aiderLightColor: "#ff9f28",
},
ruleValidate: {
themeColor: [{ required: true, message: "请选择主题色", trigger: "change" }],
lightColor: [{ required: true, message: "请选择高亮主题色", trigger: "change" }],
aiderLightColor: [
{ required: true, message: "请选择辅助高亮色", trigger: "change" },
],
},
};
},
mounted() {
this.loadSetting();
},
methods: {
loadSetting() {
this.loading = true;
getSetting(SETTING_KEY)
.then((res) => {
if (res.success && res.result) {
this.formValidate = {
...this.formValidate,
...res.result,
};
}
})
.finally(() => {
this.loading = false;
});
},
submit() {
this.$refs.formValidate.validate((valid) => {
if (!valid) return;
this.saveLoading = true;
setSetting(SETTING_KEY, this.formValidate)
.then((res) => {
if (res.success) {
this.$Message.success("保存成功");
} else {
this.$Message.error("保存失败");
}
})
.finally(() => {
this.saveLoading = false;
});
});
},
},
};
</script>
<style scoped lang="scss">
.theme-setting {
.card-header {
display: flex;
flex-direction: column;
gap: 4px;
.desc {
font-size: 12px;
color: #999;
font-weight: normal;
}
}
.field-desc {
margin-left: 10px;
color: #999;
font-size: 12px;
}
}
</style>

View File

@@ -11,6 +11,7 @@
</el-tabs> </el-tabs>
<div class="operation mb_10"> <div class="operation mb_10">
<el-button type="primary" @click="handleAdd()">添加页面</el-button> <el-button type="primary" @click="handleAdd()">添加页面</el-button>
<el-button @click="goThemeSetting">主题色设置</el-button>
</div> </div>
<el-table v-loading="loading" :data="list" style="width: 100%"> <el-table v-loading="loading" :data="list" style="width: 100%">
<el-table-column prop="name" label="页面名称" min-width="200" show-overflow-tooltip> <el-table-column prop="name" label="页面名称" min-width="200" show-overflow-tooltip>
@@ -149,6 +150,9 @@ export default {
query: { pagetype: this.params.pageType }, query: { pagetype: this.params.pageType },
}); });
}, },
goThemeSetting() {
this.$router.push({ name: "theme-setting" });
},
changePageSize(v) { changePageSize(v) {
this.params.pageNumber = 1; this.params.pageNumber = 1;
this.params.pageSize = v; this.params.pageSize = v;