feat: 增强用户登录体验与组件逻辑优化

- 在多个组件中添加用户登录状态检查,确保用户在进行特定操作前已登录。
- 更新购物车、消息中心等组件的点击事件,未登录时提示用户登录。
- 优化优惠券领取逻辑,未登录时引导用户登录。
- 调整样式以提升组件一致性,确保用户界面友好。
This commit is contained in:
田香琪
2026-07-10 13:31:14 +08:00
parent c355194aba
commit d648855533
23 changed files with 302 additions and 760 deletions

View File

@@ -162,12 +162,20 @@
<el-form-item>
<div style="color: grey">主图仅支持pngjpgjpeg格式宽高至少600*600px大小2M内可拖拽调整主图顺序</div>
</el-form-item>
<el-form-item class="form-item-view-el" label="主图视频" prop="goodsVideo">
<el-form-item
class="form-item-view-el goods-video-form-item"
:class="{ 'goods-video-form-item--multi': baseInfoForm.goodsVideo }"
label="主图视频"
prop="goodsVideo"
>
<div class="goods-video">
<div v-if="baseInfoForm.goodsVideo" class="goods-video-preview">
<video :src="baseInfoForm.goodsVideo" class="video" controls style="max-width: 300px;" />
</div>
<div class="goods-video-actions">
<div
class="goods-video-actions"
:class="{ 'goods-video-actions--with-preview': baseInfoForm.goodsVideo }"
>
<el-upload
:action="uploadFileUrl"
:headers="{ ...accessToken }"
@@ -2365,16 +2373,32 @@ export default {
/* .tox-notifications-container{
display: none !important;
} */
.goods-video-label {
width: 120px;
text-align: right;
padding: 10px 12px 10px 0;
.goods-video-form-item {
align-items: center;
}
.goods-video-form-item--multi {
align-items: flex-start;
}
.goods-video-form-item--multi :deep(.el-form-item__label) {
padding-top: 8px;
}
.goods-video-form-item :deep(.el-form-item__content) {
display: flex;
align-items: center;
}
.goods-video-form-item--multi :deep(.el-form-item__content) {
align-items: flex-start;
}
.goods-video {
align-items: flex-start;
display: flex;
flex-direction: column;
width: 100%;
}
.goods-video-preview {
@@ -2386,7 +2410,17 @@ export default {
display: flex;
align-items: center;
gap: 10px;
margin-top: 6px;
min-height: 32px;
}
.goods-video-actions--with-preview {
margin-top: 8px;
}
.goods-video-actions :deep(.el-upload) {
display: inline-flex;
align-items: center;
vertical-align: middle;
}
.mb-10 {

View File

@@ -174,7 +174,7 @@ export default {
}
.active {
color: $theme_color;
color: var(--el-color-primary);
position: relative;
}
@@ -185,6 +185,6 @@ export default {
left: 0;
width: 100%;
height: 3px;
background: $theme_color;
background: var(--el-color-primary);
}
</style>

View File

@@ -1,133 +0,0 @@
<template>
<div v-if="templateShow">
<el-form :model="form" label-width="120px">
<el-form-item label="每日场次设置">
<el-row :gutter="16" class="row">
<el-col
v-for="(item, index) in times"
:key="index"
:span="3"
class="time-item"
>
<div class="time" :class="{ active: item.check }" @click="handleClickTime(item, index)">
{{ item.time }}:00
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="秒杀规则">
<el-input
v-model="form.seckillRule"
type="textarea"
:autosize="{ minRows: 4 }"
placeholder="申请规则"
clearable
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<div class="foot-btn">
<el-button @click="closeCurrentPage">返回</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { getSetting, setSetting } from "@/api/index";
export default {
data() {
return {
templateShow: false,
submitLoading: false,
times: [],
form: {
seckillRule: "",
},
};
},
mounted() {
this.init();
},
methods: {
closeCurrentPage() {
this.$store.commit("removeTag", "manager-seckill-add");
localStorage.pageOpenedList = JSON.stringify(this.$store.state.app.pageOpenedList);
this.$router.go(-1);
},
async handleSubmit() {
const hours = this.times
.filter((item) => item.check)
.map((item) => item.time)
.join(",");
const result = await setSetting("SECKILL_SETTING", {
seckillRule: this.form.seckillRule,
hours,
});
if (result.success) {
this.$Message.success("设置成功!");
this.init();
}
},
async init() {
const result = await getSetting("SECKILL_SETTING");
if (result.success) {
this.templateShow = true;
this.form.seckillRule = result.result.seckillRule;
this.times = [];
for (let i = 0; i < 24; i++) {
let matched = false;
if (result.result.hours) {
const way = result.result.hours.split(",");
way.forEach((hours) => {
if (hours == i) {
this.times.push({ time: i, check: true });
matched = true;
}
});
}
if (!matched) {
this.times.push({ time: i, check: false });
}
}
}
},
handleClickTime(val) {
val.check = !val.check;
},
},
};
</script>
<style scoped lang="scss">
.row {
width: 50%;
}
.foot-btn {
margin-left: 10px;
display: flex;
gap: 8px;
}
.active {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
color: #fff;
background: $theme_color !important;
}
.time {
width: 100%;
cursor: pointer;
transition: 0.35s;
border-radius: 0.8em;
justify-content: center;
align-items: center;
display: flex;
background: #f3f5f7;
height: 50px;
font-size: 15px;
}
.time-item {
margin: 8px 0;
}
</style>

View File

@@ -1,216 +0,0 @@
<template>
<div class="model-view">
<div class="model-view-content">
<div class="content">
<div class="wap-title">??</div>
<div class="draggable">
<!-- ?? -->
<div class="full-shadow" v-if="type == 'full'">
<img :src="advertising[0].img" alt="" />
</div>
</div>
</div>
</div>
<div class="model-config">
<div class="decorate">
<div class="decorate-title">????</div>
<div class="decorate-list">
<div
class="decorate-item"
v-for="(item, index) in advertising"
:key="index"
>
<div class="decorate-item-title">
<div>??</div>
</div>
<div class="decorate-item-box">
<!-- ???? -->
<div class="decorate-view">
<div class="decorate-view-title">????</div>
<div>
<img class="show-image" :src="item.img" alt />
<input
type="file"
class="hidden-input"
@change="changeFile(item, index)"
ref="files"
:id="'files' + index"
/>
<div class="tips">
????
<span>{{ item.size }}</span>
</div>
</div>
<div class="selectBtn">
<el-button
size="small"
@click="handleClickFile(item, index)"
plain
type="primary"
>????</el-button
>
</div>
</div>
<!-- ???? -->
<div class="decorate-view">
<div class="decorate-view-title">????</div>
<div>
<el-button
plain
type="primary"
size="small"
@click="clickLink(item)"
>????</el-button
>
</div>
</div>
<div class="decorate-view">
<div class="decorate-view-title">??????</div>
<div>
<el-tag :type="result.pageShow === 'OPEN' ? 'success' : 'danger'">
{{ result.pageShow === "OPEN" ? "??" : "??" }}
</el-tag>
</div>
</div>
</div>
</div>
</div>
<!-- <el-button type="primary" @click="addDecorate()" ghost>??</el-button> -->
<liliDialog ref="liliDialog" @selectedLink="selectedLink" :types="linkType"></liliDialog>
</div>
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage @callback="callbackSelected" :is-component="true" :initialize="picModelFlag" :max-select="1" ref="ossManage" />
</el-dialog>
</div>
</div>
</template>
<script>
import ossManage from "@/views/sys/oss-manage/ossManage";
import * as API_Other from "@/api/other.js";
export default {
components: {
ossManage,
},
data() {
return {
result:"",
picModelFlag: false, //?????
type: "full", // ????
//????
advertising: [
{
img:
"https://st-gdx.dancf.com/gaodingx/0/uxms/design/20200903-182035-5e87.png?x-oss-process=image/resize,w_932/interlace,1,image/format,webp",
size: "750*1624",
},
],
selectedLinks: {},
linkType: "", // ????
};
},
watch: {
advertising: {
handler(val) {
this.$store.state.openStyleStore = val;
console.log(
"this.$store.state.openStyleStore",
this.$store.state.openStyleStore
);
},
deep: true,
},
},
mounted() {
this.openPage();
},
methods: {
openPage() {
const alertType = this.$route.query.pagetype;
const pageType = {
INDEX: "INDEX",
ALERT: "OPEN_SCREEN_PAGE",
OPEN_SCREEN_ANIMATION: "OPEN_SCREEN_ANIMATION",
}[alertType ? alertType : "INDEX"];
API_Other.getOpenHomeData(pageType).then((res) => {
this.$store.state.openStoreId = res.result.id;
if (res.result.pageData) {
this.advertising = [JSON.parse(res.result.pageData)];
}
this.result = res.result
});
},
// ????
clickLink(item) {
this.$refs.liliDialog.open('link')
},
// ???????
selectedLink(val) {
this.selectedLinks.url = val;
this.advertising[0].config = val;
},
// ??????
handleClickFile() {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
}, // ???????
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) return;
this.advertising[0].img = val.url;
}
},
};
</script>
<style scoped lang="scss">
@import "./style.scss";
@import "./decorate.scss";
.decorate-radio {
margin: 10px 0;
}
.window-shadow,
.full-shadow {
display: flex;
position: absolute;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.2);
left: 0;
align-items: center;
justify-content: center;
}
.window-shadow {
> img {
width: 306px;
height: 418px;
}
}
.full-shadow {
> img {
width: 100%;
height: 100%;
}
}
.draggable {
position: relative;
}
.btn-item {
> img {
margin: 4px 0;
border-radius: 50%;
width: 30px;
height: 30px;
}
}
</style>

View File

@@ -1,233 +0,0 @@
<template>
<div class="model-view">
<div class="model-view-content">
<div class="content">
<div class="wap-title">首页</div>
<div class="draggable">
<!-- 弹窗广告 -->
<div class="window-shadow">
<img :src="advertising[0].img" alt="" />
</div>
</div>
</div>
</div>
<div class="model-config">
<div class="decorate">
<div class="decorate-title">弹窗广告</div>
<div class="decorate-list">
<div
class="decorate-item"
v-for="(item, index) in advertising"
:key="index"
>
<div class="decorate-item-title">
<div>设置</div>
</div>
<div class="decorate-item-box">
<!-- 选择照片 -->
<div class="decorate-view">
<div class="decorate-view-title">选择图片</div>
<div>
<img class="show-image" :src="item.img" alt />
<input
type="file"
class="hidden-input"
@change="changeFile(item, index)"
ref="files"
:id="'files' + index"
/>
<div class="tips">
建议尺寸
<span>{{ item.size }}</span>
</div>
</div>
<div class="selectBtn">
<el-button
size="small"
@click="handleClickFile(item, index)"
plain
type="primary"
>选择链接</el-button
>
</div>
</div>
<!-- 选择连接 -->
<div class="decorate-view">
<div class="decorate-view-title">选择图片</div>
<div>
<el-button
plain
type="primary"
size="small"
@click="clickLink(item)"
>选择链接</el-button
>
</div>
</div>
<div class="decorate-view">
<div class="decorate-view-title">当前页面状态</div>
<div>
<el-tag :type="result.pageShow === 'OPEN' ? 'success' : 'danger'">
{{ result.pageShow === "OPEN" ? "开启" : "关闭" }}
</el-tag>
</div>
</div>
</div>
</div>
</div>
<liliDialog
ref="liliDialog"
@selectedLink="selectedLink"
:types="linkType"
></liliDialog>
</div>
<el-dialog v-model="picModelFlag" width="1200px" append-to-body destroy-on-close>
<ossManage @callback="callbackSelected" :is-component="true" :initialize="picModelFlag" :max-select="1" ref="ossManage" />
</el-dialog>
</div>
</div>
</template>
<script>
import ossManage from "@/views/sys/oss-manage/ossManage";
import * as API_Other from "@/api/other.js";
export default {
components: {
ossManage,
},
data() {
return {
result:"",
picModelFlag: false, //图片选择器
type: "full", // 展示方式
//全屏广告
advertising: [
{
img:
"https://shopro-1253949872.file.myqcloud.com/uploads/20200704/9136ecddcddf6607184fab689207e7e3.png",
size: "612*836",
},
],
linkType: "", // 选择类型
selectedLinks: {},
};
},
watch: {
advertising: {
handler(val) {
this.$store.state.openStyleStore = val;
console.log(
"this.$store.state.openStyleStore",
this.$store.state.openStyleStore
);
},
deep: true,
},
},
mounted() {
this.openPage();
},
methods: {
// 回调选择的链接
selectedLink(val) {
this.selectedLinks.url = val;
this.advertising[0].config = val;
},
openPage() {
const alertType = this.$route.query.pagetype;
const pageType = {
INDEX: "INDEX",
ALERT: "OPEN_SCREEN_PAGE",
OPEN_SCREEN_ANIMATION: "OPEN_SCREEN_ANIMATION",
}[alertType ? alertType : "INDEX"];
API_Other.getOpenHomeData(pageType).then((res) => {
this.$store.state.openStoreId = res.result.id;
if (res.result.pageData) {
this.advertising = [JSON.parse(res.result.pageData)];
}
this.result = res.result
});
},
// 图片选择器回显
callbackSelected(val) {
this.picModelFlag = false;
if (!val?.url) return;
this.advertising[0].img = val.url;
},
// 点击选择图片
handleClickFile(item, index) {
this.picModelFlag = true;
this.$nextTick(() => {
if (this.$refs.ossManage) {
this.$refs.ossManage.selectImage = true;
}
});
},
// 点击链接
clickLink(item) {
this.$refs.liliDialog.open('link')
},
//点击图片解析成base64
changeFile(item, index) {
const file = document.getElementById("files" + index).files[0];
if (file == void 0) return false;
const reader = new FileReader();
reader.readAsDataURL(file);
this.$nextTick((res) => {
reader.onload = (e) => {
item.img = e.target.result;
};
});
},
},
};
</script>
<style scoped lang="scss">
@import "./style.scss";
@import "./decorate.scss";
.decorate-radio {
margin: 10px 0;
}
.window-shadow,
.full-shadow {
display: flex;
position: absolute;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.2);
left: 0;
align-items: center;
justify-content: center;
}
.window-shadow {
> img {
width: 306px;
height: 418px;
}
}
.full-shadow {
> img {
width: 100%;
height: 100%;
}
}
.draggable {
position: relative;
}
.btn-item {
> img {
margin: 4px 0;
border-radius: 50%;
width: 30px;
height: 30px;
}
}
</style>

View File

@@ -1,17 +1,7 @@
import index from './index.vue' //首页
import advertising from './advertising.vue' //全屏活动
import alertAdvertising from './alertAdvertising.vue' //弹窗活动
import index from "./index.vue";
const templates = {
index,
index,
};
advertising,
alertAdvertising
}
export default templates
export default templates;

View File

@@ -81,7 +81,6 @@ import Draggable from "vuedraggable";
import { modelData } from "./config";
import decorate from "./decorate";
import * as API_Other from "@/api/other";
import * as API_Promotions from "@/api/promotion";
export default {
components: {
Draggable,
@@ -192,80 +191,17 @@ export default {
},
// 右侧栏回调
handleDrawer(val) {
let newIndex = this.selected;
if (val.promotionsType) {
if (this.contentData.list[newIndex].options.list.length >= 2) {
this.$Message.error("最多只能展示两个活动");
return;
}
if (val.promotionsType === "LIVE") {
API_Promotions.getLiveList({
status: "START",
pageSize: 1,
pageNumber: 1,
}).then((listRes) => {
const records = listRes.result?.records || [];
if (!listRes.success || records.length === 0) {
this.$Message.warning("暂无进行中的直播活动");
return;
}
API_Promotions.getLiveInfo(records[0].id).then((infoRes) => {
if (infoRes.success) {
this.contentData.list[newIndex].options.list.push({
type: val.promotionsType,
title: val.name,
title1: val.subName,
color1: val.subColor,
bk_color: val.subBkColor,
data: infoRes.result.commodityList
? infoRes.result.commodityList.splice(0, 2)
: [],
});
}
});
});
} else {
API_Promotions.getAllPromotion().then((res) => {
let exist = this.contentData.list[newIndex].options.list.find(
(i) => i.type === val.promotionsType
);
if (res.success && !exist) {
this.contentData.list[newIndex].options.list.push({
data: res.result[val.promotionsType]
? res.result[val.promotionsType].splice(0, 2)
: [],
type: val.promotionsType,
title1: val.subName,
color1: val.subColor,
bk_color: val.subBkColor,
title: val.name,
});
}
});
}
this.contentData.list[newIndex] = {
...val,
options: {
...this.contentData.list[newIndex].options,
},
model: val.type,
};
} else {
this.decorateData = "";
this.contentData.list[newIndex] = {
...val,
options: {
...val.options,
},
model: val.type,
};
this.contentData.list = JSON.parse(
JSON.stringify(this.contentData.list)
);
this["decorateData"] = this.contentData.list[newIndex];
}
const newIndex = this.selected;
this.decorateData = "";
this.contentData.list[newIndex] = {
...val,
options: {
...val.options,
},
model: val.type,
};
this.contentData.list = JSON.parse(JSON.stringify(this.contentData.list));
this.decorateData = this.contentData.list[newIndex];
},
// 封装拖拽参数

View File

@@ -22,13 +22,7 @@ export default {
};
},
mounted() {
if (this.$route.query.pagetype == "ALERT") {
this.name = "alertAdvertising";
}
if (this.$route.query.pagetype == "OPEN_SCREEN_ANIMATION") {
this.name = "advertising";
}
this.pagetype = this.$route.query.pagetype;
this.pagetype = this.$route.query.pagetype || "INDEX";
},
methods: {
selected(val) {

View File

@@ -68,22 +68,11 @@ export default {
num: 20, // 提交进度
saveDialog: false, // 加载状态
way: [
// 装修tab栏切换
{
title: "首页",
name: "index",
selected: true,
},
{
title: "全屏广告",
name: "advertising",
selected: false,
},
{
title: "弹窗广告",
name: "ALERT",
selected: false,
},
],
submitWay: {
@@ -102,10 +91,6 @@ export default {
this.way.push({ title: "首页", name: "index", selected: true });
} else if (val == "SPECIAL") {
this.way.push({ title: "专题", name: "special", selected: true });
} else if (val == "ALERT") {
this.way.push({ title: "开屏广告", name: "alert", selected: true });
}else if (val == "OPEN_SCREEN_ANIMATION") {
this.way.push({ title: "app开屏页面", name: "OPEN_SCREEN_ANIMATION", selected: true });
}
},
immediate: true,
@@ -144,7 +129,7 @@ export default {
save() {
if (
this.$store.state.styleStore == void 0 &&
(this.$route.query.pagetype && this.$route.query.pagetype != 'ALERT' && this.$route.query.pagetype != 'OPEN_SCREEN_ANIMATION')
this.$route.query.pagetype != 'SPECIAL'
) {
this.$Message.error("请装修楼层");
return false;
@@ -155,17 +140,9 @@ export default {
: (this.submitWay.pageShow = "CLOSE");
this.submitWay.pageData = JSON.stringify(this.$store.state.styleStore);
this.submitWay.pageType = "INDEX";
this.submitWay.pageType = this.$route.query.pagetype || "INDEX";
// this.submitWay.pageType = this.pagetype;
this.submitWay.pageType = this.$route.query.pagetype || "INDEX";
if (this.$route.query.pagetype == 'ALERT' || this.$route.query.pagetype == 'OPEN_SCREEN_ANIMATION') {
this.update(this.submitWay);
} else {
this.$route.query.id ? this.updateHome() : this.submit(this.submitWay);
}
// this.$route.query.id ? this.update() : this.submit(this.submitWay);
this.$route.query.id ? this.updateHome() : this.submit(this.submitWay);
},
// 更新

View File

@@ -31,6 +31,17 @@ module.exports = {
},
devServer: {
port: configs.port,
client: {
overlay: {
runtimeErrors: (error) => {
const message = error?.message || "";
if (message.includes("ResizeObserver loop")) {
return false;
}
return true;
},
},
},
},
productionSourceMap: false,
configureWebpack: {