This commit is contained in:
Ryan Ran
2026-07-07 16:40:19 +08:00
31 changed files with 1201 additions and 350 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

@@ -660,5 +660,45 @@ export const modelData = [
options: { options: {
list: [] list: []
} }
},
{
type: "video",
name: "视频",
img: "md-image",
notAdd: true,
notImg: true,
notLink: true,
notTitle: true,
options: {
list: [
{
videoUrl: "",
poster: "",
autoplay: false,
aspectRatio: "16:9",
style: {
bgType: "color",
bgColorStart: "#F5F5F5",
bgColorEnd: "#F5F5F5",
bgGradientDir: "horizontal",
bgRadius: 0,
bottomBgColor: "#F5F5F5",
bgImage: "",
margin: 0,
padding: 0,
borderShow: false,
borderStyle: "solid",
borderWidth: 1,
borderColor: "#e5e5e5",
shadowShow: false,
shadowColor: "rgba(0,0,0,0.1)",
shadowX: 0,
shadowY: 0,
shadowBlur: 10,
shadowSpread: 0,
},
},
],
}
} }
]; ];

View File

@@ -51,15 +51,15 @@
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 14px;
>.drawer-item { >.drawer-item {
cursor: pointer; cursor: pointer;
border: 1px solid #ededed; border: 1px solid #ededed;
background: #f9f0ff; background: #f9f0ff;
width: 170px; width: calc(50% - 7px);
margin-right: 14px; box-sizing: border-box;
color: #9254de; color: #9254de;
margin-bottom: 14px;
border-radius: 0.8em; border-radius: 0.8em;
height: 60px; height: 60px;
display: flex; display: flex;

View File

@@ -107,7 +107,7 @@
v-for="(item, index) in res.options.list" v-for="(item, index) in res.options.list"
:key="index" :key="index"
> >
<div class="decorate-item-title"> <div class="decorate-item-title" v-if="res.type != 'video'">
<div>卡片</div> <div>卡片</div>
<el-icon <el-icon
v-if="res.close || res.type == 'promotionDetail'" v-if="res.close || res.type == 'promotionDetail'"
@@ -373,6 +373,13 @@
</div> </div>
</div> </div>
<!-- 视频楼层配置 -->
<video-config
v-if="res.type == 'video'"
:item="item"
@select-file="(field) => handleClickFile(item, index, field)"
/>
<!-- 填写链接 --> <!-- 填写链接 -->
<div class="decorate-view" v-if="res.onlyImg"> <div class="decorate-view" v-if="res.onlyImg">
@@ -531,12 +538,14 @@ import { modelData } from "./config";
import hotzone from "@/components/hotzone"; import hotzone from "@/components/hotzone";
import ways from "@/components/lili-dialog/wap.js"; import ways from "@/components/lili-dialog/wap.js";
import categoryTemplate from "@/components/lili-dialog/template/category"; import categoryTemplate from "@/components/lili-dialog/template/category";
import VideoConfig from "./video-config";
export default { export default {
components: { components: {
ossManage, ossManage,
hotzone, hotzone,
categoryTemplate, categoryTemplate,
CircleClose, CircleClose,
VideoConfig,
}, },
data() { data() {
return { return {
@@ -556,6 +565,7 @@ export default {
enableSelectCategory: false, //商品是否绑定分类 enableSelectCategory: false, //商品是否绑定分类
pendingCategory: null, // 选择分类弹窗草稿 pendingCategory: null, // 选择分类弹窗草稿
goodsSelectedIndex: 0, //绑定商品分类的索引 goodsSelectedIndex: 0, //绑定商品分类的索引
pictureField: "img", //图片选择器回写的字段
}; };
}, },
watch: { watch: {
@@ -800,16 +810,30 @@ export default {
callbackSelected(val) { callbackSelected(val) {
this.picModelFlag = false; this.picModelFlag = false;
if (!val?.url) return; if (!val?.url) return;
this.selectedGoods.img = val.url; if (this.pictureField === "bgImage") {
if (!this.selectedGoods.style) {
this.selectedGoods.style = {};
}
this.selectedGoods.style.bgImage = val.url;
return;
}
this.selectedGoods[this.pictureField] = val.url;
}, },
// 点击选择图片 // 点击选择图片
handleClickFile(item, index) { handleClickFile(item, index, field = "img") {
this.pictureField = field;
this.selectedGoods = item; this.selectedGoods = item;
this.picModelFlag = true; this.picModelFlag = true;
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.ossManage) { const oss = this.$refs.ossManage;
this.$refs.ossManage.selectImage = true; if (!oss) return;
oss.selectImage = true;
if (field === "videoUrl") {
oss.fileType = "video";
} else {
oss.fileType = "pic";
} }
oss.changeFileType();
}); });
}, },
removeNotice(index) { removeNotice(index) {

View File

@@ -19,6 +19,7 @@ import tpl_group from "./tpl_group";
import tpl_ad_list from "./tpl_view_list"; import tpl_ad_list from "./tpl_view_list";
import tpl_activity_list from './tpl_view_list'; import tpl_activity_list from './tpl_view_list';
// import tpl_hot_area from './tpl_hot_area'; // import tpl_hot_area from './tpl_hot_area';
import tpl_video from "./tpl_video";
import tpl_goods from "./tpl_goods"; import tpl_goods from "./tpl_goods";
import tpl_notice from "./tpl_notice"; import tpl_notice from "./tpl_notice";
import tpl_promotions from "./tpl_promotions"; import tpl_promotions from "./tpl_promotions";
@@ -45,6 +46,7 @@ export default {
spike: tpl_spike, spike: tpl_spike,
group: tpl_group, group: tpl_group,
// hotArea: tpl_hot_area, // hotArea: tpl_hot_area,
video: tpl_video,
tpl_ad_list, tpl_ad_list,
promotions: tpl_promotions, promotions: tpl_promotions,
tpl_activity_list, tpl_activity_list,

View File

@@ -2,7 +2,7 @@
<div class="layout"> <div class="layout">
<div class="hot-area"> <div class="hot-area">
<!-- <img :src="res.list[0].img" alt="" /> --> <!-- <img :src="res.list[0].img" alt="" /> -->
<hotzone @change="changeHotzone" :zonesInit="res.zoneInfo" :image="res.list[0].img"></hotzone> <hotzone @change="changeHotzone" :zonesInit="res.list[0].zoneInfo" :image="res.list[0].img"></hotzone>
</div> </div>
</div> </div>
</template> </template>
@@ -16,10 +16,7 @@ export default {
props: ["res"], props: ["res"],
methods: { methods: {
changeHotzone(info) { changeHotzone(info) {
console.log(info); this.res.list[0].zoneInfo = info;
console.log(this.res);
this.res.zoneInfo = info;
// this.$emit("change", res);
}, },
}, },
}; };

View File

@@ -1,89 +0,0 @@
<template>
<div class="layout">
<div class="join-list">
<div class="join-title">
<div>{{ res.list[0].title }}</div>
<div>更多</div>
</div>
<div class="join-box">
<div class="join-item" v-for="item in 4" :key="item">
<div class="item-img-box">
<img
class="item-img"
src="https://picsum.photos/id/268/200/200"
alt
/>
<div class="item-position-tips">2人团</div>
</div>
<div class="item-price">
<span>120.00</span>
</div>
<div class="item-line-through">
<span>120.00</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ["res"]
};
</script>
<style lang="scss" scoped>
@import "./tpl.scss";
.join-box {
display: flex;
}
.item-price {
> span {
font-size: 15px;
font-weight: 500;
color: #e1212b;
}
}
.join-item {
flex: 1;
}
.item-img {
width: 75px;
height: 75px;
margin: 0 auto;
display: block;
}
.item-img-box {
position: relative;
}
.item-line-through {
> span {
font-size: 10px;
font-weight: 400;
text-decoration: line-through;
color: #999;
}
}
.item-position-tips {
position: absolute;
right: 0;
color: #fff;
font-size: 12px;
bottom: 0;
}
.join-title {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
height: 50px;
> div:nth-of-type(1) {
font-size: 16px;
font-weight: bold;
}
> div:nth-of-type(2) {
font-size: 12px;
color: #999;
}
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div class="layout" :style="wrapperStyle">
<div class="video-shell" :style="boxStyle">
<div
class="video-ratio"
:style="{ paddingBottom: aspectPadding + '%' }"
>
<div class="video-ratio-inner">
<video
v-if="item.videoUrl"
class="video-media"
:src="item.videoUrl"
:poster="item.poster"
muted
/>
<template v-else>
<img
v-if="item.poster"
class="video-media"
:src="item.poster"
alt=""
/>
<div v-else class="video-placeholder">视频楼层</div>
</template>
<div class="play-icon"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
ensureVideoItem,
getVideoWrapperStyle,
getVideoBoxStyle,
getAspectPadding,
} from "../video-style";
export default {
props: ["res"],
computed: {
item() {
const data = this.res.list[0] || {};
ensureVideoItem(data);
return data;
},
wrapperStyle() {
return getVideoWrapperStyle(this.item);
},
boxStyle() {
return getVideoBoxStyle(this.item);
},
aspectPadding() {
return getAspectPadding(this.item.aspectRatio);
},
},
};
</script>
<style lang="scss" scoped>
.layout {
padding: 0;
margin: 0;
background: transparent;
}
.video-shell {
width: 100%;
}
.video-ratio {
width: 100%;
height: 0;
position: relative;
}
.video-ratio-inner {
position: absolute;
inset: 0;
}
.video-media {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
background: #000;
}
.video-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 14px;
background: #f5f5f5;
}
.play-icon {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 44px;
height: 44px;
line-height: 44px;
text-align: center;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border-radius: 50%;
font-size: 18px;
pointer-events: none;
}
</style>

View File

@@ -0,0 +1,392 @@
<template>
<div class="video-config">
<el-tabs v-model="activeTab" class="video-config-tabs">
<el-tab-pane label="内容设置" name="content">
<div class="video-config-section-title">内容设置</div>
<div class="video-config-row">
<div class="video-config-label">上传视频</div>
<div
class="video-upload-box"
@click="$emit('select-file', 'videoUrl')"
>
<video
v-if="item.videoUrl"
class="video-upload-preview"
:src="item.videoUrl"
muted
/>
<el-icon v-else class="video-upload-plus" :size="28">
<Plus />
</el-icon>
</div>
</div>
<div class="video-config-row">
<div class="video-config-label">视频封面</div>
<div
class="video-upload-box"
@click="$emit('select-file', 'poster')"
>
<img
v-if="item.poster"
class="video-upload-preview"
:src="item.poster"
alt=""
/>
<el-icon v-else class="video-upload-plus" :size="28">
<Plus />
</el-icon>
</div>
</div>
<div class="video-config-row">
<div class="video-config-label">视频比例</div>
<el-radio-group v-model="item.aspectRatio" class="video-ratio-group">
<el-radio value="16:9">16:9</el-radio>
<el-radio value="4:3">4:3</el-radio>
<el-radio value="1:1">1:1</el-radio>
</el-radio-group>
</div>
<div class="video-config-row">
<div class="video-config-label">自动播放</div>
<el-switch v-model="item.autoplay" />
</div>
</el-tab-pane>
<el-tab-pane label="样式设置" name="style">
<div class="video-config-section-title">通用样式</div>
<div class="video-config-block">
<div class="video-config-block-title">背景设置</div>
<div class="video-config-row">
<el-radio-group v-model="item.style.bgType">
<el-radio value="color">颜色</el-radio>
<el-radio value="image">图片</el-radio>
</el-radio-group>
</div>
<template v-if="item.style.bgType === 'color'">
<div class="video-config-row">
<div class="video-config-label">背景颜色</div>
<div class="video-color-pair">
<el-color-picker v-model="item.style.bgColorStart" />
<el-color-picker v-model="item.style.bgColorEnd" />
<el-button link type="primary" @click="resetBgColors">重置</el-button>
</div>
</div>
<div class="video-config-row">
<div class="video-config-label">渐变方向</div>
<el-radio-group v-model="item.style.bgGradientDir">
<el-radio value="horizontal">横向</el-radio>
<el-radio value="vertical">纵向</el-radio>
<el-radio value="skewLeft">左斜</el-radio>
<el-radio value="skewRight">右斜</el-radio>
</el-radio-group>
</div>
</template>
<template v-else>
<div class="video-config-row">
<div class="video-config-label">背景图片</div>
<div
class="video-upload-box video-upload-box--small"
@click="$emit('select-file', 'bgImage')"
>
<img
v-if="item.style.bgImage"
class="video-upload-preview"
:src="item.style.bgImage"
alt=""
/>
<el-icon v-else class="video-upload-plus" :size="24">
<Plus />
</el-icon>
</div>
</div>
</template>
<div class="video-config-row">
<div class="video-config-label">背景圆角</div>
<el-slider
v-model="item.style.bgRadius"
:min="0"
:max="40"
show-input
:show-input-controls="false"
/>
</div>
<div class="video-config-row">
<div class="video-config-label">底部背景</div>
<div class="video-color-pair">
<el-color-picker v-model="item.style.bottomBgColor" />
<el-button link type="primary" @click="item.style.bottomBgColor = '#F5F5F5'">
重置
</el-button>
</div>
</div>
</div>
<div class="video-config-block">
<div class="video-config-row">
<div class="video-config-label">外边距</div>
<el-slider
v-model="item.style.margin"
:min="0"
:max="40"
show-input
:show-input-controls="false"
/>
</div>
<div class="video-config-row">
<div class="video-config-label">内边距</div>
<el-slider
v-model="item.style.padding"
:min="0"
:max="40"
show-input
:show-input-controls="false"
/>
</div>
</div>
<div class="video-config-block">
<div class="video-config-block-title">边框设置</div>
<div class="video-config-row">
<el-radio-group v-model="item.style.borderShow">
<el-radio :value="false">隐藏</el-radio>
<el-radio :value="true">显示</el-radio>
</el-radio-group>
</div>
<template v-if="item.style.borderShow">
<div class="video-config-row">
<div class="video-config-label">边框样式</div>
<el-radio-group v-model="item.style.borderStyle">
<el-radio value="solid">实线</el-radio>
<el-radio value="dashed">虚线</el-radio>
<el-radio value="dotted">点状</el-radio>
</el-radio-group>
</div>
<div class="video-config-row">
<div class="video-config-label">边框粗细</div>
<el-slider
v-model="item.style.borderWidth"
:min="1"
:max="10"
show-input
:show-input-controls="false"
/>
</div>
<div class="video-config-row">
<div class="video-config-label">边框颜色</div>
<div class="video-color-pair">
<el-color-picker v-model="item.style.borderColor" />
<el-button link type="primary" @click="item.style.borderColor = '#e5e5e5'">
重置
</el-button>
</div>
</div>
</template>
</div>
<div class="video-config-block">
<div class="video-config-block-title">阴影设置</div>
<div class="video-config-row">
<el-radio-group v-model="item.style.shadowShow">
<el-radio :value="false">隐藏</el-radio>
<el-radio :value="true">显示</el-radio>
</el-radio-group>
</div>
<template v-if="item.style.shadowShow">
<div class="video-config-row">
<div class="video-config-label">阴影颜色</div>
<div class="video-color-pair">
<el-color-picker v-model="item.style.shadowColor" show-alpha />
<el-button
link
type="primary"
@click="item.style.shadowColor = 'rgba(0,0,0,0.1)'"
>
重置
</el-button>
</div>
</div>
<div class="video-config-row">
<div class="video-config-label">X轴偏移</div>
<el-slider
v-model="item.style.shadowX"
:min="-20"
:max="20"
show-input
:show-input-controls="false"
/>
</div>
<div class="video-config-row">
<div class="video-config-label">Y轴偏移</div>
<el-slider
v-model="item.style.shadowY"
:min="-20"
:max="20"
show-input
:show-input-controls="false"
/>
</div>
<div class="video-config-row">
<div class="video-config-label">模糊半径</div>
<el-slider
v-model="item.style.shadowBlur"
:min="0"
:max="40"
show-input
:show-input-controls="false"
/>
</div>
<div class="video-config-row">
<div class="video-config-label">扩展半径</div>
<el-slider
v-model="item.style.shadowSpread"
:min="0"
:max="40"
show-input
:show-input-controls="false"
/>
</div>
</template>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { Plus } from "@element-plus/icons-vue";
import { ensureVideoItem } from "./video-style";
export default {
name: "VideoConfig",
components: { Plus },
props: {
item: {
type: Object,
required: true,
},
},
emits: ["select-file"],
data() {
return {
activeTab: "content",
};
},
created() {
ensureVideoItem(this.item);
},
watch: {
item: {
handler(val) {
if (val) ensureVideoItem(val);
},
immediate: true,
deep: true,
},
},
methods: {
resetBgColors() {
this.item.style.bgColorStart = "#F5F5F5";
this.item.style.bgColorEnd = "#F5F5F5";
},
},
};
</script>
<style scoped lang="scss">
.video-config-tabs {
:deep(.el-tabs__header) {
margin-bottom: 16px;
}
}
.video-config-section-title {
font-size: 15px;
font-weight: 600;
margin-bottom: 16px;
}
.video-config-block {
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.video-config-block-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
}
.video-config-row {
display: flex;
align-items: flex-start;
margin-bottom: 16px;
gap: 12px;
}
.video-config-label {
width: 72px;
flex-shrink: 0;
line-height: 32px;
color: #606266;
font-size: 13px;
}
.video-upload-box {
width: 88px;
height: 88px;
border: 1px dashed #dcdfe6;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
overflow: hidden;
background: #fafafa;
&:hover {
border-color: #409eff;
}
}
.video-upload-box--small {
width: 72px;
height: 72px;
}
.video-upload-preview {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.video-upload-plus {
color: #c0c4cc;
}
.video-ratio-group {
flex: 1;
padding-top: 6px;
}
.video-color-pair {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
:deep(.el-slider) {
flex: 1;
min-width: 0;
}
</style>

View File

@@ -0,0 +1,102 @@
export const DEFAULT_VIDEO_STYLE = {
bgType: "color",
bgColorStart: "#F5F5F5",
bgColorEnd: "#F5F5F5",
bgGradientDir: "horizontal",
bgRadius: 0,
bottomBgColor: "#F5F5F5",
bgImage: "",
margin: 0,
padding: 0,
borderShow: false,
borderStyle: "solid",
borderWidth: 1,
borderColor: "#e5e5e5",
shadowShow: false,
shadowColor: "rgba(0,0,0,0.1)",
shadowX: 0,
shadowY: 0,
shadowBlur: 10,
shadowSpread: 0,
};
export const ASPECT_RATIO_MAP = {
"16:9": 56.25,
"4:3": 75,
"1:1": 100,
};
export function ensureVideoItem(item) {
if (!item.aspectRatio) item.aspectRatio = "16:9";
if (!item.style) {
item.style = { ...DEFAULT_VIDEO_STYLE };
return;
}
Object.keys(DEFAULT_VIDEO_STYLE).forEach((key) => {
if (item.style[key] === undefined) {
item.style[key] = DEFAULT_VIDEO_STYLE[key];
}
});
}
export function getVideoGradient(style) {
const start = style.bgColorStart || "#F5F5F5";
const end = style.bgColorEnd || start;
const colors = `${start}, ${end}`;
const dirMap = {
horizontal: `linear-gradient(to right, ${colors})`,
vertical: `linear-gradient(to bottom, ${colors})`,
skewLeft: `linear-gradient(135deg, ${colors})`,
skewRight: `linear-gradient(45deg, ${colors})`,
};
return dirMap[style.bgGradientDir] || dirMap.horizontal;
}
export function getVideoWrapperStyle(item) {
ensureVideoItem(item);
const style = item.style;
const wrapper = {
margin: `${style.margin || 0}px`,
padding: `${style.padding || 0}px`,
background: style.bottomBgColor || "#F5F5F5",
boxSizing: "border-box",
};
return wrapper;
}
export function getVideoBoxStyle(item) {
ensureVideoItem(item);
const style = item.style;
const box = {
borderRadius: `${style.bgRadius || 0}px`,
overflow: "hidden",
position: "relative",
width: "100%",
};
if (style.bgType === "image" && style.bgImage) {
box.backgroundImage = `url(${style.bgImage})`;
box.backgroundSize = "cover";
box.backgroundPosition = "center";
} else {
box.background = getVideoGradient(style);
}
if (style.borderShow) {
box.border = `${style.borderWidth || 1}px ${style.borderStyle || "solid"} ${
style.borderColor || "#e5e5e5"
}`;
}
if (style.shadowShow) {
box.boxShadow = `${style.shadowX || 0}px ${style.shadowY || 0}px ${
style.shadowBlur || 10
}px ${style.shadowSpread || 0}px ${style.shadowColor || "rgba(0,0,0,0.1)"}`;
}
return box;
}
export function getAspectPadding(ratio) {
return ASPECT_RATIO_MAP[ratio] || ASPECT_RATIO_MAP["16:9"];
}

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;

View File

@@ -140,7 +140,7 @@
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
border-right: 1px solid #e5e6eb; border-right: 1px solid #e5e6eb;
padding: 16px 0 24px; padding: 20px 12px 28px;
} }
.pic-list { .pic-list {
height: 100%; height: 100%;
@@ -172,11 +172,21 @@
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
img { img,
.card-media {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.card-file {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
color: #909399;
}
.card-checkbox { .card-checkbox {
position: absolute; position: absolute;
top: 8px; top: 8px;

View File

@@ -2,9 +2,9 @@
@import "./ossManage.scss"; @import "./ossManage.scss";
.group-row { .group-row {
padding-top: 10px; padding-top: 16px;
margin-top: 8px;
border-top: 1px solid #ededed; border-top: 1px solid #ededed;
margin-top: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -54,28 +54,60 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
.oss-group-tree {
padding: 4px 8px 0;
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.el-tree-node) {
margin-bottom: 6px;
}
:deep(.el-tree-node__content) {
padding: 0 10px !important;
height: 42px;
border-radius: 6px;
font-size: 15px;
line-height: 42px;
}
:deep(.el-tree-node__content:hover) {
background: #f5f7fa;
}
:deep(.el-tree-node.is-current > .el-tree-node__content) {
background: #ecf5ff;
color: #409eff;
font-weight: 500;
}
}
.oss-tree-node { .oss-tree-node {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
padding-right: 8px; padding-right: 4px;
font-size: 15px;
} }
.oss-tree-more { .oss-tree-more {
cursor: pointer; cursor: pointer;
color: #909399; color: #909399;
padding: 0 4px; padding: 0 4px;
font-size: 16px;
} }
.oss-manage-date-popper { .category-tabs-card {
z-index: 4000 !important; margin-bottom: 0;
} }
</style> </style>
<template> <template>
<div class="search"> <div class="search">
<el-card class="tabs-card"> <el-card v-if="!isComponent" class="tabs-card">
<el-tabs v-model="activeRoleTab" @tab-change="handleRoleTabChange"> <el-tabs v-model="activeRoleTab" @tab-change="handleRoleTabChange">
<el-tab-pane label="管理员" name="MANAGER" /> <el-tab-pane label="管理员" name="MANAGER" />
<el-tab-pane label="商家" name="STORE" /> <el-tab-pane label="商家" name="STORE" />
@@ -83,48 +115,28 @@
<el-tab-pane label="客服" name="CUSTOMER" /> <el-tab-pane label="客服" name="CUSTOMER" />
</el-tabs> </el-tabs>
</el-card> </el-card>
<el-card class="tabs-card category-tabs-card">
<el-tabs v-model="activeCategoryTab" @tab-change="handleCategoryTabChange">
<el-tab-pane label="图片" name="IMAGE" />
<el-tab-pane label="视频" name="VIDEO" />
<el-tab-pane label="文件" name="FILE" />
</el-tabs>
</el-card>
<el-row> <el-row>
<el-card> <el-card>
<div> <div>
<div class="operation">
<el-row @keyup.enter="handleSearch">
<el-form
ref="searchForm"
label-width="85px"
:model="searchForm"
class="search-form"
inline
>
<el-form-item label="上传时间">
<el-date-picker
v-model="selectDate"
clearable
teleported
popper-class="oss-manage-date-popper"
value-format="YYYY-MM-DD"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 240px"
type="daterange"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button class="search-btn" type="primary" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</el-row>
</div>
<el-row class="oss-manage-box"> <el-row class="oss-manage-box">
<el-col v-if="canShowGroups" :span="isComponent ? 5 : 4"> <el-col v-if="canShowGroups" :span="isComponent ? 5 : 4">
<div class="file-list"> <div class="file-list">
<div class="article-category mr_10"> <div class="article-category mr_10">
<el-tree <el-tree
:key="treeRenderKey"
:data="treeData" :data="treeData"
node-key="value" node-key="value"
:props="{ label: 'title', children: 'children' }" :props="{ label: 'title', children: 'children' }"
:indent="0"
highlight-current highlight-current
class="demo-tree-render" class="demo-tree-render oss-group-tree"
@node-click="handleTreeNodeClick" @node-click="handleTreeNodeClick"
> >
<template #default="{ data }"> <template #default="{ data }">
@@ -195,7 +207,16 @@
class="card-checkbox" class="card-checkbox"
@click.stop @click.stop
/> />
<img :src="item.url" alt="" /> <img v-if="isImageFile(item)" :src="item.url" alt="" />
<video
v-else-if="isVideoFile(item)"
class="card-media"
:src="item.url"
muted
/>
<div v-else class="card-file">
<el-icon :size="28"><Document /></el-icon>
</div>
<div v-if="item.isShowPreview" class="preview"> <div v-if="item.isShowPreview" class="preview">
<div @click.prevent="download(item)"> <div @click.prevent="download(item)">
<el-tooltip content="下载" placement="top"> <el-tooltip content="下载" placement="top">
@@ -207,7 +228,11 @@
<el-icon :size="18"><Delete /></el-icon> <el-icon :size="18"><Delete /></el-icon>
</el-tooltip> </el-tooltip>
</div> </div>
<div @click.prevent="showPic(item)"> <div
@click.prevent="
isVideoFile(item) ? showVideo(item) : showPic(item)
"
>
<el-tooltip content="预览" placement="top"> <el-tooltip content="预览" placement="top">
<el-icon :size="22"><View /></el-icon> <el-icon :size="22"><View /></el-icon>
</el-tooltip> </el-tooltip>
@@ -459,15 +484,6 @@
:model="groupFormValidate" :model="groupFormValidate"
:rules="groupRuleValidate" :rules="groupRuleValidate"
> >
<el-form-item label="所在分组" prop="id">
<el-cascader
v-model="defaultValue"
:options="treeData"
:props="{ value: 'value', label: 'label', children: 'children', checkStrictly: true }"
style="width: 100%"
@change="treeDataChange"
/>
</el-form-item>
<el-form-item label="分组名称" prop="directoryName"> <el-form-item label="分组名称" prop="directoryName">
<el-input v-model="groupFormValidate.directoryName" /> <el-input v-model="groupFormValidate.directoryName" />
</el-form-item> </el-form-item>
@@ -481,7 +497,7 @@
</template> </template>
<script> <script>
import { Delete, Download, View } from "@element-plus/icons-vue"; import { Delete, Download, View, Document } from "@element-plus/icons-vue";
import { import {
addFileDirectory, addFileDirectory,
deleteFile, deleteFile,
@@ -499,7 +515,7 @@ const config = require("@/config/index");
let dp; let dp;
export default { export default {
name: "oss-manage", name: "oss-manage",
components: { Delete, Download, View }, components: { Delete, Download, View, Document },
props: { props: {
isComponent: { isComponent: {
default: false, default: false,
@@ -524,11 +540,11 @@ export default {
config, // api地址 config, // api地址
fileDirectoryId: "", fileDirectoryId: "",
groupFormValidate: { groupFormValidate: {
id: [], id: "",
level: 0, level: 0,
parentId: "0",
directoryName: "", directoryName: "",
}, },
defaultValue: [], // 默认分组id
groupRuleValidate: { groupRuleValidate: {
directoryName: [ directoryName: [
{ {
@@ -537,20 +553,12 @@ export default {
trigger: "blur", trigger: "blur",
}, },
], ],
id: [
{
required: true,
message: "请选择分组",
trigger: "blur",
type: "array",
},
],
}, },
enableGroup: false, // 是否展示分组 enableGroup: false, // 是否展示分组
selectImage: false, //是否是选择 selectImage: false, //是否是选择
accessToken: {}, // 上传token鉴权 accessToken: {}, // 上传token鉴权
loading: false, // 表单加载状态 loading: false, // 表单加载状态
fileType: "all", // 文件类型 fileType: "pic", // 兼容外部调用pic / video
showType: "list", // 展示类型 showType: "list", // 展示类型
modalVisible: false, // 添加或编辑显示 modalVisible: false, // 添加或编辑显示
uploadVisible: false, // 上传展示 uploadVisible: false, // 上传展示
@@ -560,20 +568,19 @@ export default {
videoTitle: "", // 视频title videoTitle: "", // 视频title
modalTitle: "", // 添加或编辑标题 modalTitle: "", // 添加或编辑标题
activeRoleTab: "MANAGER", activeRoleTab: "MANAGER",
activeCategoryTab: "IMAGE",
searchForm: { searchForm: {
// 搜索框对应data对象 // 搜索框对应data对象
name: "", name: "",
fileKey: "", fileKey: "",
fileType: "", fileType: "",
fileCategory: "IMAGE",
userEnums: "MANAGER", userEnums: "MANAGER",
pageNumber: 1, // 当前页数 pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小 pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段 sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式 order: "desc", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
}, },
selectDate: null, // 选择日期绑定modal
oldKey: "", // 请求参数 oldKey: "", // 请求参数
form: { form: {
// 表单 // 表单
@@ -603,7 +610,7 @@ export default {
width: 300, width: 300,
align: "center", align: "center",
render: (h, params) => { render: (h, params) => {
if (params.row.fileType.includes("image") > 0) { if (this.isImageFile(params.row)) {
return h("img", { return h("img", {
attrs: { attrs: {
src: params.row.url || "", src: params.row.url || "",
@@ -622,7 +629,7 @@ export default {
}, },
}, },
}); });
} else if (params.row.fileType.includes("video") > 0) { } else if (this.isVideoFile(params.row)) {
// 如果视频文件大小超过5MB不予加载video // 如果视频文件大小超过5MB不予加载video
if (params.row.fileSize < 1024 * 1024 * 5) { if (params.row.fileSize < 1024 * 1024 * 5) {
return h( return h(
@@ -804,7 +811,7 @@ export default {
// width: 150, // width: 150,
align: "center", align: "center",
render: (h, params) => { render: (h, params) => {
if (params.row.fileType.includes("image") > 0) { if (this.isImageFile(params.row)) {
return h("img", { return h("img", {
attrs: { attrs: {
src: params.row.url || "", src: params.row.url || "",
@@ -823,7 +830,7 @@ export default {
}, },
}, },
}); });
} else if (params.row.fileType.includes("video") > 0) { } else if (this.isVideoFile(params.row)) {
// 如果视频文件大小超过5MB不予加载video // 如果视频文件大小超过5MB不予加载video
if (params.row.fileSize < 1024 * 1024 * 5) { if (params.row.fileSize < 1024 * 1024 * 5) {
return h( return h(
@@ -933,6 +940,7 @@ export default {
//树结构 //树结构
treeData: [], treeData: [],
treeDataDefault: [], treeDataDefault: [],
treeRenderKey: 0,
selectedGroupData: "", selectedGroupData: "",
insertOrUpdate: "insert", insertOrUpdate: "insert",
groupLoading: false, groupLoading: false,
@@ -977,7 +985,15 @@ export default {
}, },
watch: { watch: {
selectImage(val) { selectImage(val) {
if (val && !this.data.length) this.init(); if (val) {
if (this.isComponent) {
this.activeRoleTab = "MANAGER";
this.searchForm.userEnums = "MANAGER";
this.init();
} else if (!this.data.length) {
this.init();
}
}
}, },
choose(val) { choose(val) {
if (val) this.selectImage = val if (val) this.selectImage = val
@@ -991,11 +1007,10 @@ export default {
initialize(val) { initialize(val) {
if (val && this.isComponent) { if (val && this.isComponent) {
this.selectedOss = []; this.selectedOss = [];
} this.activeRoleTab = "MANAGER";
}, this.searchForm.userEnums = "MANAGER";
defaultValue(val) { delete this.searchForm.fileDirectoryId;
if (val) { this.selectedGroupData = "";
this.groupFormValidate.parentId = val;
} }
}, },
}, },
@@ -1054,13 +1069,24 @@ export default {
this.searchForm.userEnums = name; this.searchForm.userEnums = name;
delete this.searchForm.fileDirectoryId; delete this.searchForm.fileDirectoryId;
this.selectedGroupData = ""; this.selectedGroupData = "";
this.defaultValue = []; this.groupFormValidate.id = "";
this.groupFormValidate.id = [];
this.groupFormValidate.level = 0; this.groupFormValidate.level = 0;
this.selectedOss = []; this.selectedOss = [];
this.updateTreeDataByRole(); this.updateTreeDataByRole();
this.getDataList(); this.getDataList();
}, },
handleCategoryTabChange(name) {
this.activeCategoryTab = name;
this.searchForm.fileCategory = name;
this.searchForm.fileType = "";
this.searchForm.pageNumber = 1;
if (this.showType === "list") {
this.searchForm.pageSize = 20;
} else {
this.searchForm.pageSize = 12;
}
this.getDataList();
},
updateTreeDataByRole() { updateTreeDataByRole() {
const currentTree = this.filterTreeByRole(this.treeDataDefault, this.activeRoleTab); const currentTree = this.filterTreeByRole(this.treeDataDefault, this.activeRoleTab);
this.treeData = [ this.treeData = [
@@ -1070,24 +1096,22 @@ export default {
value: "0", value: "0",
type: this.activeRoleTab, type: this.activeRoleTab,
level: 0, level: 0,
children: currentTree, children: [],
id: "0", id: "0",
categoryId: 0, categoryId: 0,
}, },
...currentTree,
]; ];
this.treeRenderKey += 1;
}, },
filterTreeByRole(tree = [], role) { filterTreeByRole(tree = [], role) {
if (!tree || !tree.length) return []; if (!tree || !tree.length) return [];
return tree.reduce((arr, item) => { return tree
const children = this.filterTreeByRole(item.children || [], role); .filter((item) => item.type === role)
if (item.type === role || children.length) { .map((item) => ({
arr.push({
...item, ...item,
children, children: [],
}); }));
}
return arr;
}, []);
}, },
// 复选框值改变时触发 // 复选框值改变时触发
selectOssChange(e) { selectOssChange(e) {
@@ -1190,11 +1214,10 @@ export default {
handleContextMenuEdit(root, node, data) { handleContextMenuEdit(root, node, data) {
this.insertOrUpdate = "update"; this.insertOrUpdate = "update";
this.enableGroup = true; this.enableGroup = true;
this.groupFormValidate.directoryName = data.label; this.groupFormValidate.id = data.value;
this.groupFormValidate.id = [data.value]; this.groupFormValidate.directoryName = data.title || data.label;
this.groupFormValidate.level = data.level; this.groupFormValidate.level = 0;
this.groupFormValidate.parentId = data.parentId; this.groupFormValidate.parentId = "0";
this.defaultValue = [data.parentId];
}, },
// 删除分组 // 删除分组
async handleContextMenuDelete(val) { async handleContextMenuDelete(val) {
@@ -1211,40 +1234,20 @@ export default {
}, },
}); });
}, },
treeDataChange(value) {
if (value && value.length) {
this.groupFormValidate.id = value;
if (value[value.length - 1] == "0") {
this.groupFormValidate.level = 0;
} else {
const node = this.findTreeNodeByValue(this.treeData, value[value.length - 1]);
this.groupFormValidate.level = node ? Number(node.level) + 1 : 0;
}
}
},
findTreeNodeByValue(nodes, value) {
if (!nodes || !nodes.length) return null;
for (const node of nodes) {
if (String(node.value) === String(value)) return node;
const found = this.findTreeNodeByValue(node.children, value);
if (found) return found;
}
return null;
},
// 保存/修改分组
async submitAddGroup() { async submitAddGroup() {
this.$refs["formValidate"].validate(async (valid) => { this.$refs["formValidate"].validate(async (valid) => {
if (valid) { if (valid) {
let res let res;
const params = {...this.groupFormValidate}; const params = {
params.directoryType = (this.selectedGroupData && this.selectedGroupData.type) || this.activeRoleTab; directoryName: this.groupFormValidate.directoryName,
level: 0,
parentId: "0",
directoryType: this.activeRoleTab,
};
if (this.insertOrUpdate === "insert") { if (this.insertOrUpdate === "insert") {
params.parentId = params.id[params.id.length - 1];
delete params.id;
res = await addFileDirectory(params); res = await addFileDirectory(params);
} else { } else {
params.id = params.id[params.id.length - 1]; params.id = this.groupFormValidate.id;
params.parentId = params.parentId[params.parentId.length - 1];
res = await updateFileDirectory(params); res = await updateFileDirectory(params);
} }
@@ -1259,16 +1262,15 @@ export default {
} }
}); });
}, },
// 添加/修改分组
handleClickAddGroup() { handleClickAddGroup() {
this.insertOrUpdate = "insert"; this.insertOrUpdate = "insert";
if (this.selectedGroupData) { this.groupFormValidate = {
this.groupFormValidate.id = [this.selectedGroupData.value]; id: "",
} else { level: 0,
this.groupFormValidate.id = ["0"]; parentId: "0",
} directoryName: "",
};
this.enableGroup = true; this.enableGroup = true;
this.groupFormValidate.directoryName = "";
}, },
copyFileUrl(row) { copyFileUrl(row) {
const textArea = document.createElement("textarea"); const textArea = document.createElement("textarea");
@@ -1296,26 +1298,31 @@ export default {
} }
}); });
}, },
// 文件目录分类格式化方法 // 文件目录分类格式化:仅展示一级分组
getTree(tree = []) { getTree(tree = []) {
let arr = []; const flat = [];
if (!!tree && tree.length !== 0) { const walk = (nodes) => {
tree.forEach((item) => { if (!nodes || !nodes.length) return;
let obj = {}; nodes.forEach((item) => {
obj.title = item.directoryName; flat.push({
obj.value = item.id; // 拥有者id title: item.directoryName,
obj.type = item.directoryType; // 用户类型 value: item.id,
obj.label = item.directoryName; type: item.directoryType,
obj.level = item.level; label: item.directoryName,
obj.expand = false; level: 0,
obj.selected = false; expand: false,
obj.contextmenu = false; selected: false,
obj.parentId = item.parentId; contextmenu: false,
obj.children = this.getTree(item.children); // 递归调用 parentId: "0",
arr.push(obj); children: [],
}); });
if (item.children && item.children.length) {
walk(item.children);
} }
return arr; });
};
walk(tree);
return flat;
}, },
// 选择分类回调 // 选择分类回调
@@ -1327,9 +1334,6 @@ export default {
this.searchForm.fileDirectoryId = value; this.searchForm.fileDirectoryId = value;
if (value === "0" || value === 0) { if (value === "0" || value === 0) {
delete this.searchForm.fileDirectoryId; delete this.searchForm.fileDirectoryId;
this.groupFormValidate.level = 0;
} else {
this.groupFormValidate.level = Number(level) + 1;
} }
this.searchForm.userEnums = type || this.activeRoleTab; this.searchForm.userEnums = type || this.activeRoleTab;
this.getDataList(); this.getDataList();
@@ -1360,6 +1364,7 @@ export default {
accessToken: this.getStore("accessToken"), accessToken: this.getStore("accessToken"),
}; };
this.searchForm.userEnums = this.activeRoleTab; this.searchForm.userEnums = this.activeRoleTab;
this.searchForm.fileCategory = this.activeCategoryTab;
this.getDataList(); this.getDataList();
this.getAllList(); this.getAllList();
}, },
@@ -1410,16 +1415,7 @@ export default {
} }
this.getDataList(); this.getDataList();
}, },
// 起止时间从新赋值 // 起止时间筛选已移除
selectDateRange(v) {
if (v && v.length === 2) {
this.searchForm.startDate = v[0];
this.searchForm.endDate = v[1];
} else {
this.searchForm.startDate = "";
this.searchForm.endDate = "";
}
},
// 改变查看方式 // 改变查看方式
changeShowType() { changeShowType() {
this.searchForm.pageNumber = 1; this.searchForm.pageNumber = 1;
@@ -1438,35 +1434,41 @@ export default {
this.pageSizeOpts = [12, 24, 48]; this.pageSizeOpts = [12, 24, 48];
} }
this.loading = true; this.loading = true;
getFileListData(this.searchForm).then((res) => { getFileListData(this.searchForm)
.then((res) => {
if (res && res.success && res.result) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
} else {
this.data = [];
this.total = 0;
}
})
.catch(() => {
this.data = [];
this.total = 0;
})
.finally(() => {
this.loading = false; this.loading = false;
this.data = res.result.records;
this.total = res.result.total;
}); });
}, },
// 搜索 // 兼容外部按场景切换分类(如装修选图/选视频)
handleSearch() {
this.searchForm.title = this.searchForm.name;
this.searchForm.pageNumber = 1;
if (this.showType == "list") {
this.searchForm.pageSize = 20;
} else {
this.searchForm.pageSize = 12;
}
this.getDataList();
},
// 文件类型筛选
changeFileType() { changeFileType() {
let name = this.fileType; const categoryMap = {
if (name == "all") { pic: "IMAGE",
this.searchForm.fileType = ""; video: "VIDEO",
} else if (name == "pic") { file: "FILE",
this.searchForm.fileType = "image"; all: "IMAGE",
} else if (name == "video") { };
this.searchForm.fileType = "video"; const category = categoryMap[this.fileType] || "IMAGE";
} this.activeCategoryTab = category;
this.handleSearch(); this.handleCategoryTabChange(category);
},
isImageFile(row) {
return row?.fileCategory === "IMAGE" || (row?.fileType && row.fileType.includes("image"));
},
isVideoFile(row) {
return row?.fileCategory === "VIDEO" || (row?.fileType && row.fileType.includes("video"));
}, },
// 上传文件超过大小限制 // 上传文件超过大小限制
handleMaxSize() { handleMaxSize() {

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

@@ -544,6 +544,7 @@ export default {
name: "", name: "",
fileKey: "", fileKey: "",
fileType: "", fileType: "",
fileCategory: "",
userEnums: "STORE", userEnums: "STORE",
pageNumber: 1, // 当前页数 pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小 pageSize: 20, // 页面大小
@@ -582,7 +583,7 @@ export default {
width: 300, width: 300,
align: "center", align: "center",
render: (h, params) => { render: (h, params) => {
if (params.row.fileType.includes("image") > 0) { if (this.isImageFile(params.row)) {
return h("img", { return h("img", {
attrs: { attrs: {
src: params.row.url || "", src: params.row.url || "",
@@ -601,7 +602,7 @@ export default {
}, },
}, },
}); });
} else if (params.row.fileType.includes("video") > 0) { } else if (this.isVideoFile(params.row)) {
// 如果视频文件大小超过5MB不予加载video // 如果视频文件大小超过5MB不予加载video
if (params.row.fileSize < 1024 * 1024 * 5) { if (params.row.fileSize < 1024 * 1024 * 5) {
return h( return h(
@@ -783,7 +784,7 @@ export default {
// width: 150, // width: 150,
align: "center", align: "center",
render: (h, params) => { render: (h, params) => {
if (params.row.fileType.includes("image") > 0) { if (this.isImageFile(params.row)) {
return h("img", { return h("img", {
attrs: { attrs: {
src: params.row.url || "", src: params.row.url || "",
@@ -802,7 +803,7 @@ export default {
}, },
}, },
}); });
} else if (params.row.fileType.includes("video") > 0) { } else if (this.isVideoFile(params.row)) {
// 如果视频文件大小超过5MB不予加载video // 如果视频文件大小超过5MB不予加载video
if (params.row.fileSize < 1024 * 1024 * 5) { if (params.row.fileSize < 1024 * 1024 * 5) {
return h( return h(
@@ -1419,16 +1420,22 @@ export default {
}, },
// 文件类型筛选 // 文件类型筛选
changeFileType() { changeFileType() {
let name = this.fileType; const name = this.fileType;
if (name == "all") {
this.searchForm.fileType = ""; this.searchForm.fileType = "";
} else if (name == "pic") { this.searchForm.fileCategory = "";
this.searchForm.fileType = "image"; if (name === "pic") {
} else if (name == "video") { this.searchForm.fileCategory = "IMAGE";
this.searchForm.fileType = "video"; } else if (name === "video") {
this.searchForm.fileCategory = "VIDEO";
} }
this.handleSearch(); this.handleSearch();
}, },
isImageFile(row) {
return row?.fileCategory === "IMAGE" || (row?.fileType && row.fileType.includes("image"));
},
isVideoFile(row) {
return row?.fileCategory === "VIDEO" || (row?.fileType && row.fileType.includes("video"));
},
// 上传文件超过大小限制 // 上传文件超过大小限制
handleMaxSize() { handleMaxSize() {
this.$Message.warning("所选文件大小过大,不得超过 20MB"); this.$Message.warning("所选文件大小过大,不得超过 20MB");

View File

@@ -45,14 +45,14 @@
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 14px;
> .drawer-item { > .drawer-item {
cursor: pointer; cursor: pointer;
border: 1px solid #ededed; border: 1px solid #ededed;
background: #f9f0ff; background: #f9f0ff;
width: 170px; width: calc(50% - 7px);
margin-right: 14px; box-sizing: border-box;
color: #9254de; color: #9254de;
margin-bottom: 14px;
border-radius: 0.8em; border-radius: 0.8em;
height: 60px; height: 60px;
display: flex; display: flex;

View File

@@ -559,6 +559,7 @@ export default {
name: "", name: "",
fileKey: "", fileKey: "",
fileType: "", fileType: "",
fileCategory: "",
userEnums: "MANAGER", userEnums: "MANAGER",
pageNumber: 1, // 当前页数 pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小 pageSize: 20, // 页面大小
@@ -597,7 +598,7 @@ export default {
width: 300, width: 300,
align: "center", align: "center",
render: (h, params) => { render: (h, params) => {
if (params.row.fileType.includes("image") > 0) { if (this.isImageFile(params.row)) {
return h("img", { return h("img", {
attrs: { attrs: {
src: params.row.url || "", src: params.row.url || "",
@@ -616,7 +617,7 @@ export default {
}, },
}, },
}); });
} else if (params.row.fileType.includes("video") > 0) { } else if (this.isVideoFile(params.row)) {
// 如果视频文件大小超过5MB不予加载video // 如果视频文件大小超过5MB不予加载video
if (params.row.fileSize < 1024 * 1024 * 5) { if (params.row.fileSize < 1024 * 1024 * 5) {
return h( return h(
@@ -798,7 +799,7 @@ export default {
// width: 150, // width: 150,
align: "center", align: "center",
render: (h, params) => { render: (h, params) => {
if (params.row.fileType.includes("image") > 0) { if (this.isImageFile(params.row)) {
return h("img", { return h("img", {
attrs: { attrs: {
src: params.row.url || "", src: params.row.url || "",
@@ -817,7 +818,7 @@ export default {
}, },
}, },
}); });
} else if (params.row.fileType.includes("video") > 0) { } else if (this.isVideoFile(params.row)) {
// 如果视频文件大小超过5MB不予加载video // 如果视频文件大小超过5MB不予加载video
if (params.row.fileSize < 1024 * 1024 * 5) { if (params.row.fileSize < 1024 * 1024 * 5) {
return h( return h(
@@ -1452,16 +1453,22 @@ export default {
}, },
// 文件类型筛选 // 文件类型筛选
changeFileType() { changeFileType() {
let name = this.fileType; const name = this.fileType;
if (name == "all") {
this.searchForm.fileType = ""; this.searchForm.fileType = "";
} else if (name == "pic") { this.searchForm.fileCategory = "";
this.searchForm.fileType = "image"; if (name === "pic") {
} else if (name == "video") { this.searchForm.fileCategory = "IMAGE";
this.searchForm.fileType = "video"; } else if (name === "video") {
this.searchForm.fileCategory = "VIDEO";
} }
this.handleSearch(); this.handleSearch();
}, },
isImageFile(row) {
return row?.fileCategory === "IMAGE" || (row?.fileType && row.fileType.includes("image"));
},
isVideoFile(row) {
return row?.fileCategory === "VIDEO" || (row?.fileType && row.fileType.includes("video"));
},
// 上传文件超过大小限制 // 上传文件超过大小限制
handleMaxSize() { handleMaxSize() {
this.$Message.warning("所选文件大小过大,不得超过 20MB"); this.$Message.warning("所选文件大小过大,不得超过 20MB");