Merge branch 'vue3'

This commit is contained in:
田香琪
2026-07-09 11:03:16 +08:00
672 changed files with 77633 additions and 60878 deletions

200
MIGRATION-VUE3.md Normal file
View File

@@ -0,0 +1,200 @@
# Lilishop UI — Vue 3 迁移与上线前检查清单
四个子项目buyer / seller / manager / im已完成 **Vue 3 + Element Plus** 迁移,并完成依赖小版本对齐。本文档供上线前统一回归使用。
## 技术栈(四端一致)
| 类别 | 包 | 版本package.json |
|------|-----|---------------------|
| 框架 | vue | ^3.5.35 |
| 编译 | @vue/compiler-sfc | ^3.5.35 |
| 路由 | vue-router | ^4.6.4 |
| 状态 | vuex | ^4.1.0 |
| UI | element-plus | ^2.14.1 |
| 图标 | @element-plus/icons-vue | ^2.3.2 |
| 请求 | axios | ^1.7.9 |
| 兼容 | core-js | ^3.49.0 |
| 构建 | @vue/cli-service | ^5.0.9 |
> 使用 `^` 范围,实际安装版本以各目录 `yarn.lock` 为准。
> 要求 **Node >= 16**,包管理统一 **yarn**。
## 本地启动
```bash
# 买家端
cd buyer && yarn install && yarn dev # 端口 10000
# 商家端
cd seller && yarn install && yarn dev # 端口 10002
# 管理端
cd manager && yarn install && yarn dev # 端口 10003
# IM 客服
cd im && yarn install && yarn dev # 端口 8000
```
生产构建(上线前必跑):
```bash
cd buyer && yarn build
cd seller && yarn build
cd manager && yarn build
cd im && yarn build
```
---
## 一、通用检查(四端都要过)
### 1. 认证与请求axios 1.x 重点)
各端请求封装位置:
| 端 | 文件 |
|----|------|
| buyer | `buyer/src/plugins/request.js` |
| seller | `seller/src/libs/axios.js` |
| manager | `manager/src/libs/axios.js` |
| im | `im/src/utils/request.js` |
- [ ] **登录**账号密码登录成功token 写入正常
- [ ] **登出**:清除 token跳转登录页
- [ ] **401**token 过期后跳转登录 / 刷新 tokenbuyer/manager/seller 有刷新逻辑)
- [ ] **403**:无权限时提示文案正确、不白屏
- [ ] **400 / 5xx**:接口报错能弹出 `Message` / `ElMessage` / `Notification`
- [ ] **网络超时**:断网或超时后有错误提示,页面不卡死
- [ ] **GET 带参**:列表筛选、分页参数序列化正常
- [ ] **POST JSON**`Content-Type: application/json` 请求体正确
- [ ] **POST 表单**`application/x-www-form-urlencoded` 提交正常im 等)
### 2. Element Plus UI2.6 → 2.14 重点)
- [ ] **表格** `el-table`:列展示、排序、空数据、加载态
- [ ] **分页** `el-pagination`:翻页、每页条数切换
- [ ] **表单** `el-form`:必填校验、错误提示位置
- [ ] **弹窗** `el-dialog` / `ElMessageBox`:打开、确认、取消、关闭
- [ ] **下拉** `el-select` / `el-dropdown`:选项、点击外部关闭
- [ ] **消息** `ElMessage` / `$Message` 兼容层success / error / warning
- [ ] **加载** `ElLoading` / `v-loading`:请求期间遮罩正常消失
- [ ] **日期** `el-date-picker`:选择、清空、范围选择
- [ ] **上传** `el-upload`:选文件、进度、成功 / 失败回调
### 3. 路由与页面
- [ ] 浏览器 **刷新** 当前路由不 404history 模式 + 服务端配置)
- [ ] **动态路由** 加载正常seller / manager 菜单权限路由)
- [ ] 浏览器 **前进 / 后退** 正常
- [ ] 各端 **404 页** 可回到首页
### 4. 构建与部署
- [ ] 四端 `yarn build` 无 errorwarning 可记录,不阻断)
- [ ] 生产环境 API 地址配置正确(`config` / 环境变量)
- [ ] 静态资源 `publicPath` 与 Nginx / CDN 路径一致
- [ ] gzip 压缩包(如有)可正常访问
---
## 二、分端专项检查
### Buyer 买家端10000
详见 [buyer/MIGRATION-VUE3.md](./buyer/MIGRATION-VUE3.md)
- [ ] 首页装修模块渲染(轮播、商品列表、秒杀区等)
- [ ] 商品详情:规格选择、加购、收藏、`el-image` 预览
- [ ] 购物车 → 结算 → 支付流程
- [ ] 用户中心:订单列表、地址、优惠券、售后
- [ ] 登录 / 注册 / 忘记密码
- [ ] 店铺入驻、商户页
- [ ] 地图(高德 `@amap/amap-jsapi-loader`)定位与选点
- [ ] 侧边栏 `el-menu`、头部 `el-dropdown`、动态图标 `iconMap.js`
### Seller 商家端10002
详见 [seller/MIGRATION-VUE3.md](./seller/MIGRATION-VUE3.md)
- [ ] 商家登录、店铺信息
- [ ] 商品发布三步流程(`goodsOperation` 步骤条)
- [ ] 订单管理:发货、退款、详情
- [ ] 促销 / 分销 / 统计图表(`@antv/g2`
- [ ] 页面装修wap / modelList从 manager 同步部分)
- [ ] 图片懒加载 `vue-lazyload`
- [ ] Excel 导出 `vue-json-excel`
### Manager 管理端10003
详见 [manager/MIGRATION-VUE3.md](./manager/MIGRATION-VUE3.md)
- [ ] 管理员登录、权限菜单、动态路由
- [ ] 系统设置(`sys/setting-manage` 各 Tab
- [ ] 菜单 / 部门管理
- [ ] 页面装修PC + H5
- [ ] 文章管理
- [ ] 商品 / 订单 / 会员 / 促销等核心列表 CRUD
- [ ] 富文本 TinyMCE 编辑、图片上传
- [ ] 多语言 `vue-i18n` 切换(如有启用)
### IM 客服端8000
详见 [im/MIGRATION-VUE3.md](./im/MIGRATION-VUE3.md)
- [ ] 带 token 参数进入会话页
- [ ] 对话列表加载、切换会话、未读数
- [ ] 发送文字、表情、图片消息
- [ ] 商品 / 订单卡片链接跳转(`linkToGoods` / `linkToOrders`
- [ ] 右键菜单(复制、撤回、删除等)
- [ ] WebSocket 连接 / 断线重连 / 新消息通知
- [ ] 最近浏览、订单列表侧栏(商家场景)
- [ ] 用户名片弹层 `$user()`
---
## 三、已知可忽略项
| 项 | 说明 |
|----|------|
| `vue-awesome-swiper` peer warning | 历史依赖Vue 3 下仍有 peer 告警,功能正常可暂忽略 |
| im 内 `<i class="el-icon-*">` | Element Plus 无字体图标,部分旧图标可能不显示,不影响核心功能 |
| `vue-prism-editor@2.0.0-alpha.2` | im 代码块编辑器刻意锁定 alpha 版 |
| 打包体积 warning | chunk-vendors 偏大,属 Element Plus 全量引入,可后续做按需优化 |
---
## 四、问题记录模板
上线测试时建议按端记录:
```markdown
### [端名] 问题标题
- 页面/路由:
- 操作步骤:
- 预期:
- 实际:
- 控制台报错:
- 优先级P0 / P1 / P2
```
---
## 五、子项目文档索引
| 端 | 迁移说明 |
|----|----------|
| buyer | [buyer/MIGRATION-VUE3.md](./buyer/MIGRATION-VUE3.md) |
| seller | [seller/MIGRATION-VUE3.md](./seller/MIGRATION-VUE3.md) |
| manager | [manager/MIGRATION-VUE3.md](./manager/MIGRATION-VUE3.md) |
| im | [im/MIGRATION-VUE3.md](./im/MIGRATION-VUE3.md) |
---
## 六、建议上线顺序
1. **manager**(后台配置源头)
2. **seller**(商家经营)
3. **buyer**C 端流量最大)
4. **im**(客服,依赖前后端 WebSocket
每上一端,至少完成本文 **第一节通用检查** + 对应 **第二节分端检查** 后再切下一端。

View File

@@ -1,12 +0,0 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}

43
buyer/MIGRATION-VUE3.md Normal file
View File

@@ -0,0 +1,43 @@
# Buyer 买家端 Vue 3 迁移说明
## 技术栈
- Vue 3 + Vue Router 4 + Vuex 4
- Element Plus替代 view-design / iView
- 保留 `$Message` / `$Modal` / `$Notice` 兼容层(`src/utils/message.js`
## 启动
```bash
cd buyer
yarn install
yarn dev # 默认端口 10000
yarn build
```
## 迁移要点
1. **入口** `src/main.js` 使用 `createApp`,全局过滤器改为 `$filters`
2. **路由** `src/router/index.js` 使用 `createRouter` + 动态 `import()`
3. **请求** `src/plugins/request.js` 使用 Element Plus `ElLoading`
4. **iView 组件已全部替换**:轮播 `el-carousel`、下拉 `el-dropdown`、侧栏 `el-menu`、头像 `el-avatar`
5. **图标映射** `src/utils/iconMap.js` — 动态图标(如侧边栏菜单)通过 `resolveIcon()` 使用
6. **自定义字体图标** `icomoon` 保留为 `<i class="icomoon icon-*">`(购物车步骤条、钱包等)
7. **首页装修** `indexDecorate/modelList` 保留买家端展示逻辑(非 manager 编辑版)
8. **特殊替换**
- `vue-piczoom``el-image` 预览
- `mv-count-down` → 内置倒计时
- 秒杀轮播 → 横向滚动列表
## 批量脚本
- `scripts/migrate-iview-to-element.js` — iView → Element 模板替换
- `scripts/fix-filters.js` — Vue 2 过滤器语法修复
- `scripts/replace-page.js``<Page>``el-pagination`
- `scripts/replace-icon.js` — 静态 `<Icon type>``el-icon`
- `scripts/fix-el-icon-click.js` — 修复 `@click` 绑定
- `scripts/fix-broken-tags.js` — 闭合标签修复
## 上线前检查
四端统一清单认证、axios、Element Plus、分端专项[../MIGRATION-VUE3.md](../MIGRATION-VUE3.md)

View File

@@ -8,35 +8,35 @@
"dev": "vue-cli-service serve"
},
"engines": {
"node": ">=14"
"node": ">=16"
},
"dependencies": {
"@amap/amap-jsapi-loader": "0.0.7",
"axios": "^0.19.2",
"@element-plus/icons-vue": "^2.3.2",
"axios": "^1.7.9",
"core-js": "^3.49.0",
"dplayer": "^1.27.1",
"element-plus": "^2.14.1",
"js-cookie": "^2.2.1",
"less": "^2.7.0",
"less-loader": "^5.0.0",
"mv-count-down": "^0.1.15",
"sass": "^1.63.6",
"postcss-loader": "^4.3.0",
"nprogress": "^0.2.0",
"psl": "^1.8.0",
"qs": "^6.9.4",
"sass": "^1.63.6",
"sass-loader": "^10.4.1",
"uuid": "^8.3.2",
"view-design": "^4.3.2",
"vue": "^2.6.11",
"vue-awesome-swiper": "^3.1.3",
"vue-piczoom": "^1.0.6",
"vue-qr": "^2.3.0",
"vue-router": "^3.0.1",
"vuex": "^3.0.1"
"vue": "^3.5.35",
"vue-qr": "^5.0.3",
"vue-router": "^4.6.4",
"vuex": "^4.1.0"
},
"devDependencies": {
"@vue/cli-service": "~4.5.0",
"compression-webpack-plugin": "^5.0.0",
"sass-loader": "^10.4.1",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-template-compiler": "^2.6.11"
"@vue/cli-plugin-babel": "^5.0.9",
"@vue/cli-service": "^5.0.9",
"@vue/compiler-sfc": "^3.5.35",
"compression-webpack-plugin": "^10.0.0",
"less": "^4.2.0",
"less-loader": "^11.1.4",
"webpack": "^5.95.0"
},
"browserslist": [
"> 1%",
@@ -45,7 +45,6 @@
],
"resolutions": {
"minimatch": "^3.1.2",
"node-sass": "npm:sass@^1.63.6",
"@achrinza/node-ipc": "9.2.2"
}
}

View File

@@ -7,20 +7,12 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>logo.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%=css%>" />
<% } %>
</head>
<body>
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%=js%>"></script>
<% } %>
<script src="/config.js"></script>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function fix(content) {
let c = content;
c = c.replace(/<\/Button>/g, "</el-button>");
c = c.replace(/<\/Button\b/g, "</el-button");
c = c.replace(/<\/Input>/g, "</el-input>");
c = c.replace(/<\/Input\b/g, "</el-input");
c = c.replace(/<\/Option>/g, "</el-option>");
c = c.replace(/<\/Option\b/g, "</el-option");
c = c.replace(/<\/Checkbox>/g, "</el-checkbox>");
c = c.replace(/<\/Checkbox\b/g, "</el-checkbox");
c = c.replace(/<\/Select>/g, "</el-select>");
c = c.replace(/<\/Select\b/g, "</el-select");
c = c.replace(/<\/Form>/g, "</el-form>");
c = c.replace(/<\/Modal>/g, "</el-dialog>");
c = c.replace(/@click\.native/g, "@click");
c = c.replace(/@on-search/g, "@keyup.enter");
c = c.replace(/\bsearch\b(?=\s+enter-button)/g, "");
c = c.replace(/\benter-button\b/g, "");
c = c.replace(/<BreadcrumbItem/g, "<el-breadcrumb-item");
c = c.replace(/<\/BreadcrumbItem>/g, "</el-breadcrumb-item>");
c = c.replace(/<Breadcrumb/g, "<el-breadcrumb");
c = c.replace(/<\/Breadcrumb>/g, "</el-breadcrumb>");
c = c.replace(/type="error"/g, 'type="danger"');
c = c.replace(/<i-col/g, "<div");
c = c.replace(/<\/i-col>/g, "</div>");
c = c.replace(/\{\{([^}|]+)\s*\|\s*secrecyMobile\s*\}\}/g, "{{ $filters.secrecyMobile($1) }}");
return c;
}
let count = 0;
for (const file of walkDir(path.join(ROOT, "src"))) {
const original = fs.readFileSync(file, "utf8");
const fixed = fix(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
}
}
console.log(`Fixed tags in ${count} files`);

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function fix(content) {
let c = content;
c = c.replace(/<p #header>/g, "<template #header><p>");
c = c.replace(
/(<template #header><p>[\s\S]*?<\/p>)(\s*<div)/g,
"$1</template>$2"
);
c = c.replace(
/<template #footer><div([^>]*)>([\s\S]*?)<\/div><\/template><\/el-dialog>/g,
"<template #footer><div$1>$2</div></template>\n </el-dialog>"
);
c = c.replace(
/<template #default\s*>([\s\S]*?)<\/template\s*>/g,
(match, inner) => {
if (match.includes("el-alert")) {
return `<template #title>${inner}</template>`;
}
return match;
}
);
return c;
}
let count = 0;
for (const file of walkDir(path.join(ROOT, "src"))) {
const original = fs.readFileSync(file, "utf8");
const fixed = fix(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
}
}
console.log(`Fixed dialog slots in ${count} files`);

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "src");
function walk(dir, files = []) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const f = path.join(dir, e.name);
if (e.isDirectory()) walk(f, files);
else if (e.name.endsWith(".vue")) files.push(f);
}
return files;
}
let n = 0;
for (const file of walk(ROOT)) {
let c = fs.readFileSync(file, "utf8");
const next = c.replace(/:size="(\d+)"\s+:size="\1"/g, ':size="$1"');
if (next !== c) {
fs.writeFileSync(file, next);
n++;
console.log(path.relative(ROOT, file));
}
}
console.log(`Fixed duplicate :size in ${n} files`);

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "src");
function walk(dir, files = []) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const f = path.join(dir, e.name);
if (e.isDirectory()) walk(f, files);
else if (e.name.endsWith(".vue")) files.push(f);
}
return files;
}
let n = 0;
for (const file of walk(ROOT)) {
let c = fs.readFileSync(file, "utf8");
const next = c
.replace(/<el-icon([^>]*?):click=/g, "<el-icon$1@click=")
.replace(/:class="refresh"/g, 'class="refresh"')
.replace(/:size="20" @click="show = false" :size="20"/g, ':size="20" @click="show = false"');
if (next !== c) {
fs.writeFileSync(file, next);
n++;
}
}
console.log(`Fixed el-icon click in ${n} files`);

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function fixFilters(content) {
let c = content;
// Fix broken: {{ expr {{ $filters.unitPrice(args) }}
c = c.replace(
/\{\{\s*([^}{]+?)\s*\{\{\s*\$filters\.unitPrice\(([^)]*)\)\s*\}\}/g,
"{{ $filters.unitPrice($1, $2) }}"
);
// Fix: {{ expr | unitPrice }} or {{ expr | unitPrice('¥') }}
c = c.replace(
/\{\{\s*([^}|]+?)\s*\|\s*unitPrice(?:\(([^)]*)\))?\s*\}\}/g,
(_, expr, args) => {
const e = expr.trim();
return args !== undefined
? `{{ $filters.unitPrice(${e}, ${args}) }}`
: `{{ $filters.unitPrice(${e}) }}`;
}
);
// Fix inline without braces: expr | unitPrice("¥")
c = c.replace(
/(?<!\{\{)\s*([a-zA-Z0-9_.?()[\]'"\s]+?)\s*\|\s*unitPrice\(([^)]*)\)/g,
(match, expr, args) => {
if (match.includes("{{")) return match;
return `{{ $filters.unitPrice(${expr.trim()}, ${args}) }}`;
}
);
// Fix inline without args: expr | unitPrice
c = c.replace(
/(?<!\{\{)\s*([a-zA-Z0-9_.?()[\]'"\s]+?)\s*\|\s*unitPrice(?!\()/g,
(match, expr) => {
if (match.includes("{{") || match.includes("$filters")) return match;
return `{{ $filters.unitPrice(${expr.trim()}) }}`;
}
);
// Fix unixToDate filter
c = c.replace(
/\{\{\s*([^}|]+?)\s*\|\s*unixToDate(?:\(([^)]*)\))?\s*\}\}/g,
(_, expr, args) => {
const e = expr.trim();
return args !== undefined
? `{{ $filters.unixToDate(${e}, ${args}) }}`
: `{{ $filters.unixToDate(${e}) }}`;
}
);
return c;
}
let count = 0;
for (const file of walkDir(path.join(ROOT, "src"))) {
const original = fs.readFileSync(file, "utf8");
const fixed = fixFilters(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
}
}
console.log(`Fixed filters in ${count} files`);

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function fix(content) {
let c = content;
c = c.replace(/<el-pagination\b/g, "<Page");
c = c.replace(/<\/el-pagination>/g, "</Page>");
c = c.replace(
/<Icon([^>]*)\s+#prepend\s*>\s*<\/Icon>/g,
"<template #prepend><Icon$1></Icon></template>"
);
c = c.replace(
/<Icon([^>]*)\s+#prepend\s*>\s*<\/Icon>/g,
"<template #prepend><Icon$1></Icon></template>"
);
c = c.replace(
/<template #prepend><Icon([^>]*)\s*\/>/g,
"<template #prepend><Icon$1></Icon></template>"
);
c = c.replace(/@keyup\.enter\.native/g, "@keyup.enter");
c = c.replace(/slot="append"/g, "#append");
c = c.replace(/(<div[^>]*)\s+#append/g, "<template #append>$1");
c = c.replace(/(<el-button[^>]*)\slong/g, '$1 style="width:100%"');
c = c.replace(/@on-select/g, "@select");
c = c.replace(/@on-click/g, "@click");
return c;
}
let count = 0;
for (const file of walkDir(path.join(ROOT, "src"))) {
const original = fs.readFileSync(file, "utf8");
const fixed = fix(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
}
}
console.log(`Runtime compat fixes in ${count} files`);

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function fix(content) {
let c = content;
c = c.replace(
/<template #default="(\{[^"]+\})" #([a-zA-Z0-9_-]+)>/g,
'<template #$2="$1">'
);
c = c.replace(
/<div([^>]*) #footer([^>]*)>/g,
'<template #footer><div$1$2>'
);
c = c.replace(/<\/div>\s*(?=<\/el-dialog>)/g, "</div></template>");
c = c.replace(/<span #append>/g, "<template #append><span>");
c = c.replace(/<\/span><\/el-input/g, "</span></template></el-input");
c = c.replace(/><span #append>/g, "><template #append><span>");
c = c.replace(/<el-button #append/g, "<template #append><el-button");
c = c.replace(
/<\/el-button>(\s*<\/el-input>)/g,
"</el-button></template>$1"
);
c = c.replace(/<template #desc\b/g, "<template #default");
return c;
}
let count = 0;
for (const file of walkDir(path.join(ROOT, "src"))) {
const original = fs.readFileSync(file, "utf8");
const fixed = fix(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
}
}
console.log(`Fixed slots in ${count} files`);

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function fix(content) {
let c = content;
c = c.replace(/<\/span\s*\n\s*>/g, "</span>");
c = c.replace(/<\/span\s+>/g, "</span>");
c = c.replace(/<i-input/g, "<el-input");
c = c.replace(/<\/i-input>/g, "</el-input>");
c = c.replace(
/<Icon([^>]*)\s+#prepend\s*\/>/g,
"<template #prepend><Icon$1 /></template>"
);
c = c.replace(
/<template #append><el-button([^>]*)>([\s\S]*?)<\/el-button>\s*<\/i-input>/g,
"<template #append><el-button$1>$2</el-button></template></el-input>"
);
c = c.replace(
/<template #append><el-button([^>]*)>([\s\S]*?)<\/el-button>\s*<\/el-input>/g,
"<template #append><el-button$1>$2</el-button></template></el-input>"
);
return c;
}
let count = 0;
for (const file of walkDir(path.join(ROOT, "src"))) {
const original = fs.readFileSync(file, "utf8");
const fixed = fix(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
}
}
console.log(`Fixed span/input tags in ${count} files`);

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function fix(content) {
let c = content;
c = c.replace(
/(<div[^>]*)\s+#content>/g,
"<template #content><div$1>"
);
c = c.replace(
/(<\/el-tooltip>)/g,
(match, _m, offset, str) => {
return match;
}
);
c = c.replace(
/(<div style="white-space:[^"]*"[^>]*>[\s\S]*?<\/div>)(\s*<\/el-tooltip>)/g,
"$1</template>$2"
);
c = c.replace(/<\/div><\/template><\/el-dialog>/g, (m, offset, str) => {
const before = str.slice(Math.max(0, offset - 200), offset);
if (before.includes("#footer")) return m;
return "</div></el-dialog>";
});
c = c.replace(/<i(\s[^>]*)\/>/g, "<i$1></i>");
return c;
}
let count = 0;
for (const file of walkDir(path.join(ROOT, "src"))) {
const original = fs.readFileSync(file, "utf8");
const fixed = fix(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
}
}
console.log(`Fixed stray templates in ${count} files`);

View File

@@ -0,0 +1,227 @@
#!/usr/bin/env node
/**
* Migrate buyer Vue files from iView to Element Plus.
* Usage: node scripts/migrate-iview-to-element.js
*/
const fs = require("fs");
const path = require("path");
const BUYER_ROOT = path.join(__dirname, "..");
const MANAGER_ROOT = path.join(__dirname, "../../manager");
const MODEL_LIST = [
["Carousel.vue", "carousel.vue"],
["Carousel1.vue", "carousel1.vue"],
["Carousel2.vue", "carousel2.vue"],
["FirstPageAdvert.vue", "firstPageAdvert.vue"],
["NewGoodsSort.vue", "newGoodsSort.vue"],
["NotEnough.vue", "notEnough.vue"],
["Recommend.vue", "recommend.vue"],
["Seckill.vue", "seckill.vue"],
["forYour.vue", "forYour.vue"],
["goodsAndType.vue", "goodsAndType.vue"],
["mixModel.vue", "mixModel.vue"],
["oneRowThreeColumns.vue", "oneRowThreeColumns.vue"],
["onlyGoodsModel.vue", "onlyGoodsModel.vue"],
["mixs/mix-brand.vue", "mixs/mix-brand.vue"],
["mixs/mix-goods.vue", "mixs/mix-goods.vue"],
];
const DECORATE_COPY_MAP = [
[
"src/components/indexDecorate/ModelForm.vue",
"src/views/page-decoration/modelForm.vue",
],
[
"src/components/indexDecorate/ModelFormItem.vue",
"src/views/page-decoration/modelFormItem.vue",
],
];
MODEL_LIST.forEach(([buyerName, managerName]) => {
DECORATE_COPY_MAP.push([
`src/components/indexDecorate/modelList/${buyerName}`,
`src/views/page-decoration/modelList/${managerName}`,
]);
});
const TARGET_DIRS = ["src/pages", "src/components"];
function adaptBuyerContent(content) {
return content
.replace(/@\/views\/page-decoration\//g, "@/components/indexDecorate/")
.replace(/admin-setting/g, "buyer-setting")
.replace(/adminPCPageCache/g, "buyerPCPageCache")
.replace(/getStore\('adminPCPageCache'\)/g, "getStore('buyerPCPageCache')")
.replace(/setStore\('adminPCPageCache'/g, "setStore('buyerPCPageCache'");
}
function applyScriptTransforms(content) {
let c = content;
c = c.replace(/\$options\.filters\./g, "$filters.");
c = c.replace(/\|\s*unitPrice\(/g, "{{ $filters.unitPrice(");
c = c.replace(/\|\s*unixToDate\(/g, "{{ $filters.unixToDate(");
c = c.replace(/\bbeforeDestroy\b/g, "beforeUnmount");
c = c.replace(/this\.\$set\(([^,]+),\s*([^,]+),\s*([^)]+)\)/g, "$1[$2] = $3");
c = c.replace(/::v-deep/g, ":deep");
c = c.replace(/\/deep\//g, ":deep");
c = c.replace(/@on-change/g, "@change");
c = c.replace(/@on-click/g, "@click");
c = c.replace(/@on-ok/g, "@ok");
c = c.replace(/@on-cancel/g, "@cancel");
c = c.replace(/@on-clear/g, "@clear");
c = c.replace(/@on-open-change/g, "@visible-change");
c = c.replace(/@on-page-size-change/g, "@size-change");
c = c.replace(/@on-select/g, "@select");
c = c.replace(/@keydown\.enter\.native/g, "@keyup.enter");
c = c.replace(/slot-scope="([^"]+)"/g, '#default="$1"');
c = c.replace(/slot="([^"]+)"/g, '#$1');
c = c.replace(/from ['"]view-design['"]/g, 'from "@/utils/message"');
c = c.replace(/import\s*\{([^}]*)\}\s*from\s*['"]view-design['"]/g, 'import {$1} from "@/utils/message"');
return c;
}
function applyTemplateTransforms(content) {
let c = content;
const replacements = [
[/<\/?Card>/g, (m) => m.replace("Card", "el-card")],
[/<Form-item/g, "<el-form-item"],
[/<\/Form-item>/g, "</el-form-item>"],
[/<FormItem/g, "<el-form-item"],
[/<\/FormItem>/g, "</el-form-item>"],
[/<Form(\s|>)/g, "<el-form$1"],
[/<\/Form>/g, "</el-form>"],
[/<InputNumber/g, "<el-input-number"],
[/<\/InputNumber>/g, "</el-input-number>"],
[/<Input(\s|>)/g, "<el-input$1"],
[/<\/Input>/g, "</el-input>"],
[/<Button(\s|>)/g, "<el-button$1"],
[/<\/Button>/g, "</el-button>"],
[/<Select(\s|>)/g, "<el-select$1"],
[/<\/Select>/g, "</el-select>"],
[/<Option(\s)/g, "<el-option "],
[/<\/Option>/g, "</el-option>"],
[/<DatePicker/g, "<el-date-picker"],
[/<\/DatePicker>/g, "</el-date-picker>"],
[/<TimePicker/g, "<el-time-picker"],
[/<\/TimePicker>/g, "</el-time-picker>"],
[/<Modal/g, "<el-dialog"],
[/<\/Modal>/g, "</el-dialog>"],
[/<Page(\s)/g, "<el-pagination "],
[/<\/Page>/g, "</el-pagination>"],
[/<Tabs(\s|>)/g, "<el-tabs$1"],
[/<\/Tabs>/g, "</el-tabs>"],
[/<TabPane/g, "<el-tab-pane"],
[/<\/TabPane>/g, "</el-tab-pane>"],
[/<Row(\s|>)/g, "<div$1"],
[/<\/Row>/g, "</div>"],
[/<Col(\s|>)/g, "<div$1"],
[/<\/Col>/g, "</div>"],
[/<Tag(\s|>)/g, "<el-tag$1"],
[/<\/Tag>/g, "</el-tag>"],
[/<Switch(\s|>)/g, "<el-switch$1"],
[/<\/Switch>/g, "</el-switch>"],
[/<RadioGroup/g, "<el-radio-group"],
[/<\/RadioGroup>/g, "</el-radio-group>"],
[/<Radio(\s)/g, "<el-radio "],
[/<\/Radio>/g, "</el-radio>"],
[/<CheckboxGroup/g, "<el-checkbox-group"],
[/<\/CheckboxGroup>/g, "</el-checkbox-group>"],
[/<Checkbox/g, "<el-checkbox"],
[/<\/Checkbox>/g, "</el-checkbox>"],
[/<Poptip/g, "<el-popover"],
[/<\/Poptip>/g, "</el-popover>"],
[/<Tooltip/g, "<el-tooltip"],
[/<\/Tooltip>/g, "</el-tooltip>"],
[/<Drawer/g, "<el-drawer"],
[/<\/Drawer>/g, "</el-drawer>"],
[/<Alert/g, "<el-alert"],
[/<\/Alert>/g, "</el-alert>"],
[/<Badge/g, "<el-badge"],
[/<\/Badge>/g, "</el-badge>"],
[/<Divider/g, "<el-divider"],
[/<\/Divider>/g, "</el-divider>"],
[/<Rate/g, "<el-rate"],
[/<\/Rate>/g, "</el-rate>"],
[/<Steps/g, "<el-steps"],
[/<\/Steps>/g, "</el-steps>"],
[/<Step(\s)/g, "<el-step "],
[/<\/Step>/g, "</el-step>"],
[/<Upload/g, "<el-upload"],
[/<\/Upload>/g, "</el-upload>"],
[/<Cascader/g, "<el-cascader"],
[/<\/Cascader>/g, "</el-cascader>"],
[/<Affix/g, "<el-affix"],
[/<\/Affix>/g, "</el-affix>"],
[/<Spin/g, "<el-skeleton"],
[/<\/Spin>/g, "</el-skeleton>"],
[/:label-width="(\d+)"/g, 'label-width="$1px"'],
[/:mask-closable="false"/g, ':close-on-click-modal="false"'],
[/:width="(\d+)"/g, 'width="$1"'],
[/type="primary"/g, 'type="primary"'],
];
for (const [pattern, replacement] of replacements) {
c = c.replace(pattern, replacement);
}
return c;
}
function walkDir(dir, files = []) {
if (!fs.existsSync(dir)) return files;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkDir(full, files);
} else if (entry.name.endsWith(".vue")) {
files.push(full);
}
}
return files;
}
const copied = [];
const transformed = [];
const skipped = [];
for (const [buyerRel, managerRel] of DECORATE_COPY_MAP) {
const managerPath = path.join(MANAGER_ROOT, managerRel);
const buyerPath = path.join(BUYER_ROOT, buyerRel);
if (!fs.existsSync(managerPath)) {
skipped.push({ file: buyerRel, reason: "manager counterpart missing" });
continue;
}
let content = fs.readFileSync(managerPath, "utf8");
content = adaptBuyerContent(content);
fs.mkdirSync(path.dirname(buyerPath), { recursive: true });
fs.writeFileSync(buyerPath, content);
copied.push(buyerRel);
}
const copiedSet = new Set(copied);
for (const dir of TARGET_DIRS) {
const absDir = path.join(BUYER_ROOT, dir);
for (const filePath of walkDir(absDir)) {
const rel = path.relative(BUYER_ROOT, filePath).replace(/\\/g, "/");
if (copiedSet.has(rel)) continue;
let content = fs.readFileSync(filePath, "utf8");
const hasIView =
/<(Card|Form|Form-item|FormItem|Table|Button|Modal|Page|Tabs|TabPane|Input|Select|Option|DatePicker|Row|Poptip|Drawer|Upload|Checkbox|Radio|Switch|Tag|Alert|Rate|Steps|Step|Spin|Affix|TimePicker|Cascader|Divider|Badge|InputNumber)\b/.test(
content
);
if (!hasIView) {
skipped.push({ file: rel, reason: "no iView components" });
continue;
}
content = applyTemplateTransforms(content);
content = applyScriptTransforms(content);
fs.writeFileSync(filePath, content);
transformed.push(rel);
}
}
console.log("=== Buyer Migration Summary ===");
console.log(`Copied from manager: ${copied.length}`);
console.log(`Script-transformed: ${transformed.length}`);
console.log(`Skipped: ${skipped.length}`);

View File

@@ -0,0 +1,149 @@
#!/usr/bin/env node
/**
* 将静态 <Icon type="..." /> 替换为 <el-icon><Xxx /></el-icon>
* 动态 :type / custom 属性保留,由人工或 resolveIcon 处理。
*/
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "src");
const TYPE_TO_COMPONENT = {
"md-checkmark": "Check",
"md-close": "Close",
"md-trash": "Delete",
"md-add": "Plus",
"md-arrow-dropdown": "ArrowDown",
"md-arrow-dropup": "ArrowUp",
"md-person": "User",
"md-lock": "Lock",
"md-phone-portrait": "Iphone",
"md-key": "Key",
"md-link": "Link",
"md-refresh": "Refresh",
"md-qr-scanner": "Iphone",
"ios-arrow-down": "ArrowDown",
"ios-arrow-up": "ArrowUp",
"ios-arrow-forward": "ArrowRight",
"ios-arrow-back": "ArrowLeft",
"ios-arrow-round-down": "ArrowDown",
"ios-arrow-round-forward": "ArrowRight",
"ios-cart-outline": "ShoppingCart",
"ios-camera": "Camera",
"ios-eye-outline": "View",
"ios-trash-outline": "Delete",
"ios-alert-outline": "Warning",
"ios-help-circle-outline": "Help",
"ios-add-circle-outline": "Plus",
"ios-information-circle": "InfoFilled",
"ios-search": "Search",
"ios-text-outline": "Document",
"ios-alarm-outline": "AlarmClock",
"ios-heart": "StarFilled",
"md-close-circle": "CircleClose",
edit: "Edit",
"trash-a": "Delete",
location: "Location",
};
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory() && entry.name !== "compat") walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function parseAttrs(attrStr) {
const attrs = {};
const re = /(?::([\w-]+)|@([\w-]+)|([\w-]+))=(?:"([^"]*)"|'([^']*)')|(?::([\w-]+)|@([\w-]+)|([\w-]+))(?=\s|\/|>)/g;
let m;
while ((m = re.exec(attrStr)) !== null) {
const name = m[1] || m[2] || m[3] || m[6] || m[7] || m[8];
const value = m[4] ?? m[5] ?? true;
attrs[name] = value;
}
return attrs;
}
function attrsToString(attrs, exclude = []) {
return Object.entries(attrs)
.filter(([k]) => !exclude.includes(k))
.map(([k, v]) => {
if (v === true) return k;
if (k.startsWith("@")) return `${k}="${v}"`;
return `:${k}="${v}"`;
})
.join(" ");
}
function transform(content, filePath) {
if (filePath.includes("/compat/")) return content;
let c = content;
const used = new Set();
c = c.replace(/<Icon\b([^>]*?)\/>/g, (match, attrStr) => {
if (/\bcustom=/.test(attrStr) || /:type=/.test(attrStr)) return match;
const attrs = parseAttrs(attrStr);
const type = attrs.type;
if (!type || !TYPE_TO_COMPONENT[type]) return match;
const comp = TYPE_TO_COMPONENT[type];
used.add(comp);
const rest = attrsToString(attrs, ["type"]);
const size = attrs.size ? ` :size="${attrs.size}"` : "";
const open = rest ? `<el-icon ${rest}${size}>` : `<el-icon${size}>`;
return `${open}<${comp} /></el-icon>`;
});
c = c.replace(/<Icon\b([^>]*?)><\/Icon>/g, (match, attrStr) => {
if (/\bcustom=/.test(attrStr) || /:type=/.test(attrStr)) return match;
const attrs = parseAttrs(attrStr);
const type = attrs.type;
if (!type || !TYPE_TO_COMPONENT[type]) return match;
const comp = TYPE_TO_COMPONENT[type];
used.add(comp);
const rest = attrsToString(attrs, ["type"]);
const size = attrs.size ? ` :size="${attrs.size}"` : "";
const open = rest ? `<el-icon ${rest}${size}>` : `<el-icon${size}>`;
return `${open}<${comp} /></el-icon>`;
});
if (used.size && !c.includes("@element-plus/icons-vue")) {
const imports = [...used].sort().join(", ");
c = c.replace(
/(<script[^>]*>\s*\n)/,
`$1import { ${imports} } from '@element-plus/icons-vue';\n`
);
c = c.replace(/export default \{/, (m) => {
if (/components:\s*\{/.test(c)) {
return m;
}
return `${m}\n components: { ${imports} },`;
});
if (/components:\s*\{/.test(c) && !c.includes(`${[...used][0]},`)) {
c = c.replace(/components:\s*\{([^}]*)\}/, (m, inner) => {
const existing = inner.trim();
const add = [...used].filter((n) => !inner.includes(n)).join(", ");
if (!add) return m;
const merged = existing ? `${existing}, ${add}` : add;
return `components: { ${merged} }`;
});
}
}
return c;
}
let count = 0;
for (const file of walkDir(ROOT)) {
const original = fs.readFileSync(file, "utf8");
if (!/<Icon\b/.test(original)) continue;
const fixed = transform(original, file);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
console.log(path.relative(ROOT, file));
}
}
console.log(`Icon replacements in ${count} files`);

View File

@@ -0,0 +1,63 @@
#!/usr/bin/env node
/**
* 将全局 <Page> 封装替换为原生 el-pagination
*/
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "src");
function walkDir(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(full, files);
else if (entry.name.endsWith(".vue")) files.push(full);
}
return files;
}
function transformPage(content) {
return content.replace(
/<Page\b([^>]*?)(\/>|>(\s*)<\/Page>)/gs,
(match, attrs, close) => {
let a = attrs;
const hasTotal = /\bshow-total\b/.test(a);
const hasSizer = /\bshow-sizer\b/.test(a);
const hasElevator = /\bshow-elevator\b/.test(a);
a = a.replace(/\s+show-total\b/g, "");
a = a.replace(/\s+show-sizer\b/g, "");
a = a.replace(/\s+show-elevator\b/g, "");
a = a.replace(/:current=/g, ":current-page=");
a = a.replace(/:page-size-opts=/g, ":page-sizes=");
a = a.replace(/@change=/g, "@current-change=");
a = a.replace(/\ssize="small"/g, " small");
const layoutParts = [];
if (hasTotal) layoutParts.push("total");
if (hasSizer) layoutParts.push("sizes");
layoutParts.push("prev", "pager", "next");
if (hasElevator) layoutParts.push("jumper");
const layoutAttr = ` layout="${layoutParts.join(", ")}"`;
if (close.startsWith("/>")) {
return `<el-pagination${a}${layoutAttr} />`;
}
return `<el-pagination${a}${layoutAttr}></el-pagination>`;
}
);
}
let count = 0;
for (const file of walkDir(ROOT)) {
const original = fs.readFileSync(file, "utf8");
if (!/<Page\b/.test(original)) continue;
const fixed = transformPage(original);
if (fixed !== original) {
fs.writeFileSync(file, fixed);
count++;
console.log(path.relative(ROOT, file));
}
}
console.log(`Page → el-pagination in ${count} files`);

View File

@@ -6,57 +6,62 @@
<script>
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 {
name: "App",
mounted() {
this.init();
},
methods: {
init(){
if(!storage.getItem("siteName")||!storage.getItem("logoImg")||!storage.getItem("sitelogo_expiration_time")) {
this.getSite();
}else{
// 如果缓存过期,则获取最新的信息
if (new Date() > storage.getItem("sitelogo_expiration_time")) {
this.getSite();
return;
}else{
window.document.title = storage.getItem("siteName");
//动态获取icon
let link =document.querySelector("link[rel*='icon']") ||document.createElement("link");
link.type = "image/x-icon";
link.href = storage.getItem("siteIcon");
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
}
}
},
getSite(){
//获取基本站点信息
getBaseSite().then((res) => {
if (res.success && res.result.settingValue) {
let data = JSON.parse(res.result.settingValue);
// 过期时间
var expirationTime = new Date().setHours(new Date().getHours() + 1);
// 存放过期时间
applySiteInfo(data) {
const expirationTime = new Date().setHours(new Date().getHours() + 1);
storage.setItem("sitelogo_expiration_time", expirationTime);
// 存放信息
storage.setItem('siteName', data.siteName);
storage.setItem('logoImg', data.buyerSideLogo);
storage.setItem("siteName", data.siteName);
storage.setItem("logoImg", data.buyerSideLogo);
storage.setItem("siteIcon", data.buyerSideIcon);
this.$store.commit("SET_SITENAME", data.siteName);
this.$store.commit("SET_LOGOIMG", data.buyerSideLogo);
window.document.title = data.siteName;
//动态获取icon
let link =document.querySelector("link[rel*='icon']") ||document.createElement("link");
let link =
document.querySelector("link[rel*='icon']") ||
document.createElement("link");
link.type = "image/x-icon";
link.href = data.buyerSideIcon;
link.rel = "shortcut icon";
document.getElementsByTagName("head")[0].appendChild(link);
}
},
init() {
const expirationTime = storage.getItem("sitelogo_expiration_time");
const cacheValid =
expirationTime && new Date() <= new Date(Number(expirationTime));
if (cacheValid) {
this.applySiteInfo({
siteName: storage.getItem("siteName"),
buyerSideLogo: storage.getItem("logoImg"),
buyerSideIcon: storage.getItem("siteIcon"),
});
}
this.getSite();
this.getTheme();
},
getSite() {
getBaseSite().then((res) => {
if (res.success && res.result.settingValue) {
try {
const data = JSON.parse(res.result.settingValue);
this.applySiteInfo(data);
} catch {
// 站点配置解析失败时使用缓存
}
}
}).catch(() => {});
},
getTheme() {
fetchAndApplyTheme(getThemeSetting).catch(() => {});
},
},
};
</script>
<style lang="scss">

View File

@@ -44,7 +44,8 @@ export function editMemberInfo (params) {
url: '/buyer/passport/member/editOwn',
method: Method.PUT,
needToken: true,
data: params
data: params,
loading: false
})
}
@@ -54,7 +55,9 @@ export function editPwd (params) {
url: `/buyer/passport/member/modifyPass`,
method: Method.PUT,
needToken: true,
data: params
data: params,
loading: false,
skipMessage: true
})
}

View File

@@ -17,7 +17,8 @@ export function newMemberAddress (params) {
url: '/buyer/member/address',
needToken: true,
method: Method.POST,
data: params
data: params,
loading: false
});
}
@@ -27,7 +28,8 @@ export function editMemberAddress (params) {
url: '/buyer/member/address',
needToken: true,
method: Method.PUT,
params
params,
loading: false
});
}
@@ -36,7 +38,8 @@ export function delMemberAddress (id) {
return request({
url: `/buyer/member/address/delById/${id}`,
needToken: true,
method: Method.DELETE
method: Method.DELETE,
loading: false
});
}

View File

@@ -20,7 +20,8 @@ export function cartGoodsAll () {
return request({
url: '/buyer/trade/carts/all',
method: Method.GET,
needToken: true
needToken: true,
loading: false
});
}

View File

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

View File

@@ -8,6 +8,7 @@ export function goodsList (params) {
url: '/buyer/goods/goods/es',
method: Method.GET,
needToken: false,
optionalToken: true,
params
});
}
@@ -18,6 +19,7 @@ export function filterList (params) {
url: '/buyer/goods/goods/es/related',
method: Method.GET,
needToken: false,
optionalToken: true,
params
});
}

View File

@@ -158,7 +158,7 @@ export function addEvaluation (params) {
url: `/buyer/member/evaluation`,
method: Method.POST,
needToken: true,
params
data: params
});
}
@@ -205,6 +205,7 @@ export function couponList (params) {
url: `/buyer/promotion/coupon`,
method: Method.GET,
needToken: true,
loading: false,
params
});
}
@@ -297,7 +298,8 @@ export function cancelAfterSale (afterSaleSn) {
return request({
url: `/buyer/order/afterSale/cancel/${afterSaleSn}`,
method: Method.POST,
needToken: true
needToken: true,
loading: false
});
}
@@ -425,7 +427,9 @@ export function distCash (params) {
url: `/buyer/distribution/cash`,
method: Method.POST,
needToken: true,
params
params,
loading: false,
skipMessage: true
});
}

View File

@@ -72,7 +72,8 @@ export function applyStatus () {
return request({
url: `/buyer/store/store/apply`,
needToken: true,
method: Method.GET
method: Method.GET,
loading: false
})
}

View File

@@ -0,0 +1,29 @@
import back from "./back.png";
import carts from "./carts.png";
import collage from "./collage.png";
import feedback from "./feedback.png";
import notice from "./notice.png";
import notification from "./notification.png";
import shop from "./shop.png";
import story from "./story.png";
import support from "./support.png";
import user from "./user.png";
const iconMap = {
back,
carts,
collage,
feedback,
notice,
notification,
shop,
story,
support,
user,
};
export function getIconUrl(name) {
return iconMap[name] || "";
}
export default iconMap;

View File

@@ -0,0 +1,40 @@
:root {
--el-color-primary: var(--theme-color, #ff3c2a);
--el-color-danger: var(--theme-color, #ff3c2a);
--el-font-size-base: 14px;
}
.el-table--border {
.el-table__cell {
border-right: none !important;
}
}
.link-text {
color: var(--theme-color, #ff3c2a);
cursor: pointer;
text-decoration: none;
&:hover {
opacity: 0.85;
}
}
.op-split {
margin: 0 8px;
color: #dcdfe6;
}
/* 次要强调按钮,使用高亮主题色(如「立即购买」) */
.confirm-receipt-btn {
background-color: var(--theme-light, #ff6b35) !important;
border-color: var(--theme-light, #ff6b35) !important;
color: #fff !important;
&:hover,
&:focus {
background-color: color-mix(in srgb, var(--theme-light, #ff6b35) 88%, black) !important;
border-color: color-mix(in srgb, var(--theme-light, #ff6b35) 88%, black) !important;
color: #fff !important;
}
}

View File

@@ -0,0 +1,25 @@
// 全局布局工具类(非 scoped避免子组件继承 class 时宽度失效)
.width_1200_auto {
width: 1200px;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
box-sizing: border-box;
}
// 首页主体:分类导航 + 楼层装修共用同一 1200px 容器,保证列对齐
.index-main {
width: 1200px;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
box-sizing: border-box;
position: relative;
}
// 与 CateNav 商品分类栏、Carousel 左占位一致的列宽
$index-main-width: 1200px;
$index-side-col-width: 263.2px;
$index-col-gap: 10px;
// 1200 - 263.2*2 - 10*2 = 653.6px
$index-center-col-width: $index-main-width - ($index-side-col-width * 2) - ($index-col-gap * 2);

View File

@@ -10,7 +10,7 @@ $warning_color: #ff9900;
$error_color: #ed3f14;
$handle-btn-color: #438cde;
$theme_color: #F31947;
$theme_color: var(--theme-color, #ff3c2a);
$border_color: #dddee1;
$title_color: #8c8c8c;
@@ -30,6 +30,7 @@ $dark_content_color: #d5d5d5;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
/***** 封装一些方法可用于 黑暗主题 ,明亮主题 *****/
@@ -305,9 +306,11 @@ li {
}
.width_1200_auto {
width: 1184px;
width: 1200px;
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
}
@@ -325,18 +328,24 @@ li {
.goods-list {
display: flex;
flex-wrap: wrap;
width: 1200px;
width: 100%;
margin: 0 auto;
column-gap: 10px;
}
.goods-show-info {
width: 235px;
width: calc(20% - 8px);
padding: 6px;
margin: 10px 0px;
margin-left: 5px;
margin: 10px 0;
position: relative;
border: 1px solid #fff;
cursor: pointer;
background-color: #fff;
box-sizing: border-box;
}
.goods-show-img img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
.goods-show-info:hover {
border: 1px solid #ccc;
@@ -368,7 +377,12 @@ li {
color: $theme_color;
}
.goods-page {
margin:10px auto ;
text-align: right;
width: 1200px;
margin: 10px 0;
width: 100%;
display: flex;
justify-content: flex-end;
:deep(.el-pagination) {
justify-content: flex-end;
}
}

View File

@@ -9,18 +9,24 @@
.goods-list {
display: flex;
flex-wrap: wrap;
width: 1200px;
width: 100%;
margin: 0 auto;
column-gap: 10px;
}
.goods-show-info {
width: 235px;
width: calc(20% - 8px);
padding: 6px;
margin: 10px 0px;
margin-left: 5px;
margin: 10px 0;
position: relative;
border: 1px solid #fff;
cursor: pointer;
background-color: #fff;
box-sizing: border-box;
}
.goods-show-img img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
.goods-show-info:hover {
border: 1px solid #ccc;
@@ -37,6 +43,7 @@
text-overflow: ellipsis;
display:-webkit-box;
-webkit-line-clamp:2;
line-clamp: 2;
-webkit-box-orient:vertical;
color:#808080;
}
@@ -62,7 +69,12 @@
color: $theme_color;
}
.goods-page {
margin:10px auto ;
text-align: right;
width: 1200px;
margin: 10px 0;
width: 100%;
display: flex;
justify-content: flex-end;
:deep(.el-pagination) {
justify-content: flex-end;
}
}

View File

@@ -1,6 +1,4 @@
@import "~view-design/src/styles/index.less";
// iview 自定义样式
// Element Plus 主题变量(原 view-design less 已移除)
@primary-color: #ff5c58;
@info-color: #fa6419;
@success-color: #68cabe;
@@ -11,9 +9,7 @@
@table-td-highlight-bg: #ededed;
@font-size-base: 12px;
.ivu-drawer,
.drawer,
.ivu-drawer-wrap {
.el-drawer,
.drawer {
z-index: 2600 !important;
}

View File

@@ -1,5 +1,20 @@
<template>
<div class="navbar" :class="{'small-search-box': useClass == 'fixed-show'}">
<template v-if="useClass == 'fixed-show'">
<div class="search search-embedded">
<el-input
v-model="searchData"
size="large"
class="search-input"
placeholder="输入你想查找的商品"
@keyup.enter="search"
/>
<div class="search-btn" @click="search">
<el-icon :size="21"><SearchIcon /></el-icon>
</div>
</div>
</template>
<template v-else>
<div class="container width_1200_auto flex flex-a-c">
<img
:src="$store.state.logoImg"
@@ -8,19 +23,19 @@
alt=""
@click="$router.push('/')"
/>
<div :class="{'small-search-box':useClass == 'fixed-show'}" class="search-box">
<i-input
<div class="search-box">
<div class="search">
<el-input
v-model="searchData"
size="large"
class="search "
class="search-input"
placeholder="输入你想查找的商品"
@keyup.enter.native="search"
>
<div class="search-icon" slot="append" @click="search">
<Icon type="ios-search" size="21"/>
@keyup.enter="search"
/>
<div class="search-btn" @click="search">
<el-icon :size="21"><SearchIcon /></el-icon>
</div>
</div>
</i-input>
<template v-if="showTag">
<div class="only-store" v-if="storeId" @click="research()">
切换为{{!onlyStore ? '店铺内' : '平台'}}搜索
@@ -39,16 +54,18 @@
</template>
</div>
</div>
</template>
</div>
</template>
<script>
import storage from '@/plugins/storage.js'
import {hotWords} from '@/api/goods.js'
import { Search as SearchIcon } from '@element-plus/icons-vue'
export default {
name: 'search',
components: { SearchIcon },
props: {
showTag: { // 是否展示搜索栏下方热门搜索
type: Boolean,
@@ -111,10 +128,14 @@ export default {
},
computed: {
promotionTags() {
if (this.$store.state.hotWordsList) {
return JSON.parse(this.$store.state.hotWordsList)
} else {
return []
const raw = this.$store.state.hotWordsList;
if (!raw) {
return [];
}
try {
return typeof raw === "string" ? JSON.parse(raw) : raw;
} catch {
return [];
}
}
},
@@ -128,12 +149,12 @@ export default {
if (!reloadTime) {
hotWords({count: 5}).then(res => {
if (res.success && res.result) storage.setItem('hotWordsList', res.result)
})
}).catch(() => {})
storage.setItem('hotWordsReloadTime', new Date().getTime())
} else if (reloadTime && time > reloadTime) {
hotWords({count: 5}).then(res => {
if (res.success && res.result) storage.setItem('hotWordsList', res.result)
})
}).catch(() => {})
storage.setItem('hotWordsReloadTime', new Date().getTime())
}
}
@@ -150,14 +171,19 @@ export default {
height: 113px;
background: #fff;
}
.search-icon{
width: 100%;
height: 100%;
}
.small-search-box{
height: 60px;
margin: 0 !important;
.navbar.small-search-box {
height: 60px;
width: 100%;
margin: 0;
display: flex;
align-items: center;
background: transparent;
}
.search-embedded {
width: 100%;
margin: 0;
}
.container {
@@ -167,49 +193,47 @@ export default {
}
.search {
$btn-gap: 4px;
$btn-width: 60px;
$input-height: 37.8px;
position: relative;
width: 778.4px;
height: $input-height;
margin: 10px 0px 5px 0;
border-radius: 18.9px;
.search-input {
width: 100%;
::v-deep .ivu-input.ivu-input-large {
:deep(.el-input__wrapper) {
border: 1.4px solid $theme_color;
box-sizing: border-box;
border-radius: 19.6px;
position: relative;
padding-left: 26px;
padding-right: calc(#{$btn-gap * 2} + #{$btn-width});
font-size: 14px;
font-weight: normal;
height: 37.8px;
height: $input-height;
color: #999;
&:focus {
box-shadow: none;
}
}
::v-deep .ivu-input-group-append {
border-radius: 19.6px !important;
.search-btn {
position: absolute;
right: $btn-gap;
top: $btn-gap;
bottom: $btn-gap;
width: $btn-width;
border-radius: calc((#{$input-height} - #{$btn-gap * 2}) / 2);
cursor: pointer;
box-sizing: border-box;
border: 1.4px solid $theme_color;
width: 67.2px;
height: 37.8px;
position: absolute;
right: 0;
display: flex;
align-items: center;
justify-content: center;
top: 0;
z-index: 99;
z-index: 1;
background-color: $theme_color;
color: #ffffff;
button {
font-size: 14px;
font-weight: 600;
line-height: 1;
}
}
}
.search-box{

View File

@@ -1,49 +1,50 @@
<template>
<div>
<Modal v-model="showAddr" width="800" title="收件人地址">
<Form
<el-dialog v-model="showAddr" width="800" title="收件人地址">
<el-form
:model="formData"
ref="form"
label-position="left"
:label-width="100"
label-width="100px"
:rules="ruleInline"
>
<FormItem label="收件人" prop="name">
<i-input v-model="formData.name" style="width: 600px"></i-input>
</FormItem>
<FormItem label="收件地区" prop="address">
<el-form-item label="收件人" prop="name">
<el-input v-model="formData.name" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="收件地区" prop="address">
{{ formData.address || '暂无地址' }}
<Button type="primary" style="margin-left: 10px;" size="small" @click="$refs.map.open()">选择</Button>
</FormItem>
<FormItem label="详细地址" prop="detail">
<i-input v-model="formData.detail" style="width: 600px"></i-input>
</FormItem>
<FormItem label="手机号码" prop="mobile">
<i-input v-model="formData.mobile" style="width: 600px"></i-input>
</FormItem>
<FormItem label="地址别名">
<i-input
<el-button type="primary" style="margin-left: 10px;" size="small" @click="$refs.map.open()">选择</el-button>
</el-form-item>
<el-form-item label="详细地址" prop="detail">
<el-input v-model="formData.detail" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="formData.mobile" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="地址别名">
<el-input
v-model="formData.alias"
length
:maxlength="4"
placeholder="请输入地址别名,例如公司"
style="width: 600px"
></i-input>
</FormItem>
<FormItem label="默认地址">
<i-switch v-model="formData.isDefault" />
</FormItem>
</Form>
<div class="mt_20" slot="footer">
<Button @click="hide">取消</Button>
<Button type="primary" class="mr_10" :loading="loading" @click="save">保存收货地址</Button>
</div>
</Modal>
></el-input>
</el-form-item>
<el-form-item label="默认地址">
<el-switch v-model="formData.isDefault" />
</el-form-item>
</el-form>
<template #footer><div class="mt_20">
<el-button @click="hide">取消</el-button>
<el-button type="primary" class="mr_10" :loading="loading" @click="save">保存收货地址</el-button>
</div></template>
</el-dialog>
<multipleMap ref="map" @callback="getAddress"></multipleMap>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import multipleMap from "@/components/map/multiple-map";
import {
newMemberAddress,
@@ -96,7 +97,7 @@ export default {
editMemberAddress(params).then((res) => {
this.loading = false;
if (res.success) {
this.$Message.success('编辑地址成功');
Message.success('编辑地址成功');
this.$emit('change', true);
this.hide();
}
@@ -105,7 +106,7 @@ export default {
newMemberAddress(params).then((res) => {
this.loading = false;
if (res.success) {
this.$Message.success('新增地址成功');
Message.success('新增地址成功');
this.$emit('change', true);
this.hide();
}
@@ -130,15 +131,15 @@ export default {
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
this.$set(this.formData,'address',paths)
this.$set(this.formData,'consigneeAddressIdPath',ids)
this.formData['address'] = paths
this.formData['consigneeAddressIdPath'] = ids
const coord = val.data[val.data.length - 1].center.split(',')
this.formData.lat = coord[1]
this.formData.lon = coord[0]
}else{
this.$set(this.formData, "address", val.data.addr);
this.$set(this.formData, "consigneeAddressIdPath", val.data.addrId);
this.$set(this.formData, "detail", val.data.address);
this.formData["address"] = val.data.addr;
this.formData["consigneeAddressIdPath"] = val.data.addrId;
this.formData["detail"] = val.data.address;
this.formData.lat = val.data.position.lat;
this.formData.lon = val.data.position.lng;
}

View File

@@ -1,12 +1,14 @@
<template>
<!-- 头部广告 -->
<div class="advertising" v-if="show" :style="{'background-color': data.bgColor}">
<div class="advertising" v-if="show && data" :style="{'background-color': data.bgColor}">
<img :src="data.img" class="hover-pointer" @click="linkTo(data.url)"/>
<Icon type="md-close-circle" size="20" @click="show = false" />
<el-icon :size="20" @click="show = false"><CircleClose /></el-icon>
</div>
</template>
<script>
import { CircleClose } from '@element-plus/icons-vue';
export default {
components: { CircleClose },
props: {
data: { // 传入的广告信息
type: Object,

View File

@@ -1,85 +1,90 @@
<template>
<Card class="_Card" :bordered="false" :dis-hover="true">
<div slot="title" class="cardTitle">
<el-card class="_Card" shadow="never">
<template #header>
<div class="card-header">
<div class="cardTitle">
<span :style="{ fontSize: `${_Size}px` }">{{ _Title }}</span>
<div v-if="_Tabs" class="cardTabs">
<div @click="tabsChange(index)" :class="{active:(isActive==index)}" class="cardTabsItem" :style="{fontSize:`${_Size-2}px`}" v-for="(item,index) in _Tabs"
:key="index">
<div
@click="tabsChange(index)"
:class="{ active: isActive == index }"
class="cardTabsItem"
:style="{ fontSize: `${_Size - 2}px` }"
v-for="(item, index) in _Tabs"
:key="index"
>
{{ item }}
</div>
</div>
</div>
<div slot="extra" class="cardExtra" v-if="_More" @click="callBack()">
<div class="cardExtra" v-if="_More" @click="callBack()">
{{ _More }}
</div>
<div>
</div>
</Card>
</template>
<slot />
</el-card>
</template>
<script>
export default {
name: 'index',
props:
{
_Tabs: { // 可点击的tab栏
name: "index",
props: {
_Tabs: {
type: null,
default: ''
default: "",
},
// 头部
_Title: { // 标题
_Title: {
type: null,
default: '卡片头部'
default: "卡片头部",
},
// 右侧更多
_More: {
type: null,
default: false
default: false,
},
_Size: { // 文字大小
_Size: {
type: Number,
default: 16
default: 16,
},
// 点击更多触发跳转
_Src: {
type: null,
default: function (val) {
if (this._More) {
return val;
} else {
return false;
}
}
}
default: null,
},
_ActiveTab: {
type: Number,
default: 0,
},
},
data() {
return {
isActive: 0 // 已激活tab栏下标
isActive: this._ActiveTab,
};
},
watch: {
_ActiveTab(val) {
this.isActive = val;
},
},
methods: {
// 点击右侧的回调
callBack() {
let _this = this;
if (this._Src !== '' || this._Src != null) {
this.$router.push({
path: _this._Src
});
if (this._Src) {
this.$router.push({ path: this._Src });
}
},
// 点击tab的回调
tabsChange(index) {
// 处理并返回index
this.isActive = index;
this.$emit('_Change', index);
}
}
this.$emit("_Change", index);
},
},
};
</script>
<style scoped lang="scss">
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.cardTitle {
display: flex;
cursor: pointer;
@@ -88,7 +93,7 @@ export default {
color: $theme_color;
position: relative;
&::before {
content: '';
content: "";
position: absolute;
width: 100%;
height: 3px;
@@ -104,28 +109,26 @@ export default {
> .cardTabsItem {
padding: 0 12px;
}
> .cardTabsItem:hover {
color: $theme_color;
}
}
::v-deep .ivu-card, .ivu-card-head, ._Card {
:deep(._Card) {
margin-bottom: 20px;
@include white_background_color();
}
::v-deep .ivu-card-head {
:deep(._Card .el-card__header) {
position: relative;
padding: 0 14px;
height: 50px;
line-height: 50px;
&::before {
content: '';
content: "";
width: 3px;
height: 50%;
top: 25%;
@@ -140,9 +143,12 @@ export default {
cursor: pointer;
}
::v-deep .ivu-card-body {
:deep(._Card .el-card__body) {
padding: 0 !important;
display: none;
min-height: 0;
}
:deep(._Card .el-card__body:empty) {
display: none;
}
</style>

View File

@@ -8,12 +8,12 @@
</div>
<div>
<p class="hover-color" @click="linkTo(`/goodsDetail?skuId=${goods.goodsSku.id}&goodsId=${goods.goodsSku.goodsId}`)">{{goods.goodsSku.goodsName}}</p>
<p class="price">{{goods.goodsSku.price | unitPrice('¥')}}<span>&nbsp; x{{goods.num}}</span></p>
<p class="price">{{ $filters.unitPrice(goods.goodsSku.price, '¥') }}<span>&nbsp; x{{goods.num}}</span></p>
</div>
<span class="del hover-color" @click="delGoods(goods.goodsSku.id)">删除</span>
</li>
</ul>
<Button size="large" class="mt_10" type="primary" @click="linkTo('/cart')" long>去购物车结算</Button>
<el-button size="large" class="mt_10" type="primary" @click="linkTo('/cart')" style="width:100%">去购物车结算</el-button>
</div>
<div v-else-if="title === '我的订单'" class="order-con">
@@ -26,10 +26,10 @@
v-for="(img,imgIndex) in order.orderItems"
:key="imgIndex" width="40" height="40" alt="">
</div>
<div class="order-handle"><span>{{ order.flowPrice | unitPrice("¥") }}</span><span class="hover-color" @click="linkTo(`home/OrderDetail?sn=${order.sn}`)">查看订单</span></div>
<div class="order-handle"><span>{{ $filters.unitPrice(order.flowPrice, "¥") }}</span><span class="hover-color" @click="linkTo(`home/OrderDetail?sn=${order.sn}`)">查看订单</span></div>
</li>
</ul>
<Button type="primary" @click="linkTo('/home/MyOrder')" long>查看全部订单</Button>
<el-button type="primary" @click="linkTo('/home/MyOrder')" style="width:100%">查看全部订单</el-button>
</div>
<div v-else-if="title === '优惠券'" class="coupon-con">
@@ -37,7 +37,7 @@
<li v-for="(coupon, index) in couponList" class="coupon-item" :key="index">
<div class="c-left">
<div>
<span v-if="coupon.couponType === 'PRICE'" class="fontsize_12 global_color">¥<span class="price">{{coupon.price | unitPrice}}</span></span>
<span v-if="coupon.couponType === 'PRICE'" class="fontsize_12 global_color">¥<span class="price">{{ $filters.unitPrice(coupon.price) }}</span></span>
<span v-if="coupon.couponType === 'DISCOUNT'" class="fontsize_12 global_color"><span class="price">{{coupon.couponDiscount}}</span></span>
<span class="describe">{{coupon.consumeThreshold}}元可用</span>
</div>
@@ -56,7 +56,7 @@
<li v-for="(track,trackIndex) in tracksList" :key="trackIndex">
<img :src="track.thumbnail" :alt="track.thumbnail" @click="linkTo(`/goodsDetail?skuId=${track.id}&goodsId=${track.goodsId}`)" width="100" height="100">
<div @click="addToCart(track.id)">加入购物车</div>
<p class="global_color">{{track.price | unitPrice('¥')}}</p>
<p class="global_color">{{ $filters.unitPrice(track.price, '¥') }}</p>
</li>
</ul>
<div class="hover-color" style="text-align:center;" @click="linkTo('/home/MyTracks')">查看更多>></div>
@@ -67,22 +67,25 @@
<img :src="collect.image" :alt="collect.image" @click="linkTo(`/goodsDetail?skuId=${collect.skuId}&goodsId=${collect.goodsId}`)" width="100" height="100">
<div @click="addToCart(collect.skuId)">加入购物车</div>
<span class="del-icon" @click.stop="cancelCollect(collect.skuId)">
<Icon type="md-trash" />
<el-icon><Delete /></el-icon>
</span>
<p class="global_color">{{collect.price | unitPrice('¥')}}</p>
<p class="global_color">{{ $filters.unitPrice(collect.price, '¥') }}</p>
</li>
</ul>
<div class="hover-color" style="text-align:center;" @click="linkTo('/home/Favorites')">查看更多>></div>
</div>
<Spin v-if="loading" fix></Spin>
<el-skeleton v-if="loading" fix></el-skeleton>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import { Delete } from '@element-plus/icons-vue';
import {cartGoodsAll, delCartGoods, addCartGoods, cartCount} from '@/api/cart.js'
import { getOrderList } from '@/api/order';
import {couponList, receiveCoupon, tracksList, collectList, cancelCollect} from '@/api/member.js'
export default {
components: { Delete },
name: 'Drawer',
props: {
title: {
@@ -163,14 +166,14 @@ export default {
delGoods (id) {
delCartGoods({ skuIds: id }).then((res) => {
if (res.success) {
this.$Message.success('删除成功');
Message.success('删除成功');
this.getCartList();
cartCount().then(res => {
this.$store.commit('SET_CARTNUM', res.result)
this.Cookies.setItem('cartNum', res.result)
})
} else {
this.$Message.error(res.message);
Message.error(res.message);
}
});
},
@@ -181,7 +184,7 @@ export default {
receive (item) { // 领取优惠券
receiveCoupon(item.id).then(res => {
if (res.success) {
this.$Modal.confirm({
Modal.confirm({
title: '领取优惠券',
content: '<p>优惠券领取成功,可到我的优惠券页面查看</p>',
okText: '我的优惠券',
@@ -230,9 +233,9 @@ export default {
addCartGoods(params).then(res => {
this.loading = false;
if (res.success) {
this.$Message.success('商品已成功添加到购物车')
Message.success('商品已成功添加到购物车')
} else {
this.$Message.warning(res.message);
Message.warning(res.message);
}
}).catch(() => { this.loading = false });
},
@@ -278,7 +281,7 @@ export default {
cancelCollect (id) { // 取消商品收藏
cancelCollect('GOODS', id).then(res => {
if (res.success) {
this.$Message.success('取消收藏成功')
Message.success('取消收藏成功')
this.getCollectList();
}
})

View File

@@ -3,28 +3,30 @@
<div class="wrapper" :style="{right:handleDrawer ? '300px' : '0px'}">
<div class="barItem" @mouseenter="showCartNum(item)" @click="clickBar(item)" v-for="(item,index) in resetConfig.menuList" :key="index">
<Tooltip placement="left" :content="item.title">
<Icon size="20" :type="item.icon"/>
<el-tooltip placement="left" :content="item.title">
<el-icon :size="20"><component :is="resolveIcon(item.icon)" /></el-icon>
<p class="barTitle" v-if="item.titleShow"> {{item.title}}</p>
<div class="circle" v-if="item.title === '购物车'">
{{cartNum < 100 ? cartNum : 99}}
</div>
</Tooltip>
</el-tooltip>
</div>
</div>
<Drawer width="300" class="popup" :title="drawerData.title" :mask="resetConfig.mask" :closable="resetConfig.closable"
<el-drawer width="300" class="popup" :title="drawerData.title" :modal="resetConfig.mask" :show-close="resetConfig.closable"
v-model="handleDrawer">
<drawerPage :title="drawerData.title" />
</Drawer>
</el-drawer>
</div>
</template>
<script>
import { Modal } from "@/utils/message";
import Storage from '@/plugins/storage.js';
import Configuration from './config';
import drawerPage from './Drawer'
import {cartCount} from '@/api/cart.js'
import { resolveIcon } from '@/utils/iconMap'
export default {
name: 'Main',
data () {
@@ -46,6 +48,7 @@ export default {
}
},
methods: {
resolveIcon,
showCartNum (item) { // 获取购物车数量
if (this.userInfo && item.title === '购物车') {
this.getCartList()
@@ -53,9 +56,9 @@ export default {
},
clickBar (val) { // tabbar点击操作
if (!this.userInfo) {
this.$Modal.confirm({
Modal.confirm({
title: '请登录',
content: '<p>请登录后执行此操作</p>',
content: '请登录后执行此操作',
okText: '立即登录',
cancelText: '继续浏览',
onOk: () => {
@@ -147,11 +150,11 @@ export default {
justify-content: center;
flex-direction: column;
}
::v-deep.popup .ivu-drawer-body{
:deep(.popup .el-drawer__body) {
padding: 0 !important;
background-color: #eee;
}
::v-deep.popup .ivu-drawer-wrap{
:deep(.popup) {
z-index: 3001;
}
</style>

View File

@@ -1,42 +1,30 @@
# 右侧侧边栏组件
> 本组件依赖于iview的组件基础上进行封装
>项目结构
>* Main -- 组件用于挂在右侧的横栏
>* drawer -- 右侧横栏的内容
>* config -- 用于设置大小
基于 Element Plus `el-drawer` 封装的右侧悬浮栏。
#### config设置
```
## 项目结构
- `Main.vue` — 挂载右侧横栏的容器
- `Drawer.vue` — 横栏展开后的内容面板
- `config.js` — 尺寸与菜单配置
## config 设置
```js
/**
menuList // 组件的menu
display //是否显示此menu
badge //显示徽标数
titleShow //是否显示title
* menuList 菜单项列表
* display 是否显示该项
* badge 徽标数
* titleShow 是否显示标题
*/
//实例代码
width : 50, //bar的大小
menuList:[
{
icon
}
]
width: 50,
menuList: [{ icon: '...' }]
```
## 功能模块
账户信息、购物车、我的订单、优惠券、我的资产、我的足迹、我的收藏、邮箱订阅
> 账户信息
> 购物车
> 我的订单
> 优惠券
> 我的资产
> 我的足迹
> 我的收藏
> 邮箱订阅
## 使用
## 如何使用
1.
在页面中注册全局组件 `<drawer />` 即可(已在 `global.js` 中注册)。

View File

@@ -1,7 +1,7 @@
<template>
<div class="shadow-box">
<div class="shadow-item flex flex-a-c flex-j-c" v-for="(item,index) in iconList" @click="handleClickIcon(item)" :key="index">
<img class="icon" :src="require(`@/assets/iconfont/${item.icon}.png`)">
<img class="icon" :src="getIconUrl(item.icon)">
<div class="shadow-label">{{item.label}}</div>
<div class="line" v-if="iconList.length-1 !=index"></div>
</div>
@@ -9,6 +9,8 @@
</template>
<script>
import { getIconUrl } from "@/assets/iconfont/iconMap";
export default {
name: "fixed-index",
data(){
@@ -58,6 +60,7 @@ export default {
},
methods:{
getIconUrl,
scrollToTop() {
const c = document.documentElement.scrollTop || document.body.scrollTop;
if (c > 0) {

View File

@@ -69,12 +69,13 @@
</template>
<script>
import config from '@/config';
export default {
name: "Footer",
data() {
return {
config:require('@/config'),
config,
guideArr: [
// 导航链接
["购物指南", "购物流程", "会员介绍", "生活旅行", "常见问题"],

View File

@@ -1,54 +1,21 @@
// 全局组件配置
import empty from "./empty/Main.vue";
import drawer from "./drawer/Main.vue";
import Header from "@/components/header/Header.vue";
import FixedTopPage from "@/components/advertising/FixedTop.vue";
import Footer from "@/components/footer/Footer.vue";
import Search from "@/components/Search.vue";
import card from "@/components/card/index.vue";
import cateNav from "@/components/nav/CateNav.vue";
import UserCenterLayout from "@/components/userCenter/Layout.vue";
import empty from './empty/Main' // 空状态组件
import drawer from './drawer/Main' // 右侧bar
import Header from '@/components/header/Header'; // 头部组件
import FixedTopPage from '@/components/advertising/FixedTop'; // 顶部广告
import Footer from '@/components/footer/Footer'; // 底部栏
import Search from '@/components/Search' // 搜索框
import card from '@/components/card' // 个人中心 卡片
import cateNav from '@/components/nav/CateNav' // 个人中心 卡片
empty.install = function (Vue) {
Vue.component('empty', empty);
};
drawer.install = function (Vue) {
Vue.component('drawer', drawer);
};
Header.install = function (Vue) {
Vue.component('BaseHeader', Header);
};
FixedTopPage.install = function (Vue) {
Vue.component('FixedTopPage', FixedTopPage);
};
Footer.install = function (Vue) {
Vue.component('BaseFooter', Footer);
};
Search.install = function (Vue) {
Vue.component('Search', Search);
};
card.install = function (Vue) {
Vue.component('card', card)
}
cateNav.install = function (Vue) {
Vue.component('cateNav', cateNav)
}
// 引用本js中所有的组件
export function InstallAll (Vue) {
Vue.use(empty)
Vue.use(drawer)
Vue.use(Header)
Vue.use(FixedTopPage)
Vue.use(Footer)
Vue.use(Search)
Vue.use(card)
Vue.use(cateNav)
export function registerGlobalComponents(app) {
app.component("empty", empty);
app.component("drawer", drawer);
app.component("BaseHeader", Header);
app.component("FixedTopPage", FixedTopPage);
app.component("BaseFooter", Footer);
app.component("Search", Search);
app.component("card", card);
app.component("UserCenterLayout", UserCenterLayout);
app.component("cateNav", cateNav);
}

View File

@@ -1,9 +1,14 @@
<template>
<div>
<div class="wrapper" v-if="type === 'goodsDetail'">
<div class="wr-l"><Icon size="23" type="ios-alarm-outline" /> 秒杀活动</div>
<div class="wr-l"><el-icon :size="23"><AlarmClock /></el-icon> 秒杀活动</div>
<div class="count-down" v-if="end === ''">
<p>倒计时</p><span>{{ hours }}</span><span>{{ minutes }}</span><span>{{ seconds }}</span>
<span class="count-down-label">倒计时</span>
<span class="count-down-num">{{ hours }}</span>
<span class="count-down-colon">:</span>
<span class="count-down-num">{{ minutes }}</span>
<span class="count-down-colon">:</span>
<span class="count-down-num">{{ seconds }}</span>
</div>
<div v-else>{{end}}</div>
</div>
@@ -14,7 +19,9 @@
</div>
</template>
<script>
import { AlarmClock } from '@element-plus/icons-vue';
export default {
components: { AlarmClock },
props: {
time: { // 传入的初始时间
default: 1718977559428
@@ -87,36 +94,35 @@ export default {
font-size: 13px;
}
.count-down {
margin-right: -20px;
p{
float: left;
line-height: 20px;
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
line-height: 1;
.count-down-label {
flex-shrink: 0;
white-space: nowrap;
}
> span {
position: relative;
float: left;
width: 20px;
.count-down-num {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
text-align: center;
padding: 0 2px;
background-color: #2f3430;
margin-right: 20px;
color: white;
color: #fff;
font-size: 14px;
line-height: 20px;
&::after {
content: ":";
display: block;
position: absolute;
right: -20px;
font-weight: bolder;
line-height: 1;
border-radius: 2px;
}
.count-down-colon {
font-weight: bold;
font-size: 14px;
width: 20px;
height: 100%;
top: 0;
}
}
> span:last-child::after {
content: "";
line-height: 1;
}
}

View File

@@ -6,7 +6,12 @@
<!-- 大图放大镜 -->
<!-- <div id="dplayer"></div> -->
<div class="item-detail-big-img" v-if="imgList[imgIndex]">
<pic-zoom :url="imgList[imgIndex].url || imgList[imgIndex]" :scale="2"></pic-zoom>
<el-image
:src="currentImg"
:preview-src-list="previewList"
fit="contain"
style="width: 100%; height: 350px"
/>
</div>
<!-- <div id="dplayer"></div> -->
@@ -23,32 +28,36 @@
</div>
<div class="goodsConfig mt_10">
<span @click="collect"
><Icon
type="ios-heart"
:color="isCollected ? '#ed3f14' : '#666'"
/>{{ isCollected ? "已收藏" : "收藏" }}</span
<span
class="collect-btn"
:class="{ 'is-collected': isCollected }"
@click="collect"
>
<el-icon class="collect-icon"><StarFilled /></el-icon>
<span class="collect-text">{{ isCollected ? '已收藏' : '收藏' }}</span>
</span>
</div>
</div>
<!-- 右侧商品信息活动信息操作展示 -->
<div class="item-detail-right">
<div class="item-detail-title">
<p>
{{ skuDetail.goodsName }}
<Tag
<div class="item-detail-title-row">
<span class="item-detail-name">{{ skuDetail.goodsName }}</span>
<el-tag
v-if="skuDetail.goodsType !== 'VIRTUAL_GOODS'"
style="margin-top:10px;rgb(153, 149, 149);"
class="goods-type-tag"
size="small"
>
实物商品
</Tag>
<Tag
</el-tag>
<el-tag
v-else-if="skuDetail.goodsType == 'VIRTUAL_GOODS'"
style="margin-top:10px;rgb(153, 149, 149);"
class="goods-type-tag"
size="small"
>
虚拟商品
</Tag>
</p>
</el-tag>
</div>
</div>
<div class="sell-point">
{{ skuDetail.sellingPoint }}
@@ -68,14 +77,9 @@
>
<p>
<span class="item-price-title" v-if="promotionMap['SECKILL']"
> &nbsp;&nbsp;</span
>
<span class="item-price">{{
skuDetail.promotionPrice | unitPrice("¥")
}}</span>
<span class="item-price-old">{{
skuDetail.price | unitPrice("¥")
}}</span>
> &nbsp;&nbsp;</span>
<span class="item-price">{{ $filters.unitPrice(skuDetail.promotionPrice, "¥") }}</span>
<span class="item-price-old">{{ $filters.unitPrice(skuDetail.price, "¥") }}</span>
</p>
</div>
<!-- 商品原价 -->
@@ -92,7 +96,7 @@
:key="index"
class="item-price item-num"
>
{{ item | unitPrice("¥") }}
{{ $filters.unitPrice(item, "¥") }}
</div>
</div>
<div class="flex">
@@ -110,16 +114,13 @@
<!-- 普通价格 -->
<div v-else>
<span class="item-price-title"
> &nbsp;&nbsp;&nbsp;&nbsp;</span
>
<span class="item-price">{{
skuDetail.price | unitPrice("¥")
}}</span>
> &nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="item-price">{{ $filters.unitPrice(skuDetail.price, "¥") }}</span>
</div>
</div>
<!-- 优惠券展示 -->
<div class="item-price-coupon-row" v-if="promotionMap['COUPON'].length">
<p class="Ellipsis">
<div class="Ellipsis">
<span class="item-price-title"> </span>
<span>
<span
@@ -129,13 +130,11 @@
@click="receiveCoupon(item.id)"
>
<span v-if="item.couponType == 'PRICE'"
>{{ item.consumeThreshold }}{{ item.price }}</span
>
>{{ item.consumeThreshold }}{{ item.price }}</span>
<span v-if="item.couponType == 'DISCOUNT'"
>{{ item.consumeThreshold }}{{
item.couponDiscount
}}</span
>
}}</span>
</span>
</span>
@@ -149,32 +148,28 @@
@click="receiveCoupon(item.id)"
>
<span v-if="item.couponType == 'PRICE'"
>{{ item.consumeThreshold }}{{ item.price }}</span
>
>{{ item.consumeThreshold }}{{ item.price }}</span>
<span v-if="item.couponType == 'DISCOUNT'"
>{{ item.consumeThreshold }}{{
item.couponDiscount
}}</span
>
}}</span>
</span>
</div>
</div>
</p>
</div>
</div>
<!-- 满减展示 -->
<div class="item-price-row" v-if="promotionMap['FULL_DISCOUNT']">
<p>
<span class="item-price-title"
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span
>
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span class="item-promotion">满减</span>
<span
class="item-desc-pintuan"
v-if="promotionMap['FULL_DISCOUNT'].fullMinus"
>{{ promotionMap["FULL_DISCOUNT"].fullMoney }}立减现金{{
promotionMap["FULL_DISCOUNT"].fullMinus
}}</span
>
}}</span>
<span
class="item-desc-pintuan"
v-if="
@@ -183,8 +178,7 @@
"
>满{{ promotionMap["FULL_DISCOUNT"].fullMoney }}元,立享{{
promotionMap["FULL_DISCOUNT"].fullRate
}}折</span
>
}}折</span>
</p>
</div>
</div>
@@ -193,8 +187,7 @@
<p>累计评价</p>
<p>
<span class="item-remarks-num"
>{{ skuDetail.commentNum || 0 }} 条</span
>
>{{ skuDetail.commentNum || 0 }} 条</span>
</p>
</div>
</div>
@@ -235,16 +228,18 @@
<div class="item-select-title">
<p>数量</p>
</div>
<div class="item-select-column">
<div class="item-select-row">
<InputNumber
<el-input-number
:min="1"
:max="skuDetail.quantity"
:disabled="skuDetail.quantity === 0"
:max="quantityMax"
:disabled="isOutOfStock"
v-model="count"
:precision="0.1"
@on-blur="changeCount"
></InputNumber>
<span class="inventory"> 库存{{ skuDetail.quantity }}</span>
:precision="0"
@blur="changeCount"
></el-input-number>
<span class="inventory">&nbsp;&nbsp;库存{{ skuDetail.quantity }}</span>
</div>
</div>
</div>
<div
@@ -256,58 +251,66 @@
<div class="item-select-title">
<p>重量</p>
</div>
<div class="item-select-row">
<div class="item-select-column">
<span class="inventory">{{ skuDetail.weight }}kg</span>
</div>
</div>
<div
class="add-buy-car"
class="item-select add-buy-car-row"
v-if="$route.query.way === 'POINT' && skuDetail.authFlag === 'PASS'"
>
<Button
type="error"
<div class="item-select-column">
<div class="add-buy-car">
<el-button
type="danger"
:loading="loading"
:disabled="skuDetail.quantity === 0"
@click="pointPay"
>积分购买</Button
>积分购买</el-button
>
</div>
</div>
</div>
<div
class="add-buy-car"
class="item-select add-buy-car-row"
v-if="$route.query.way !== 'POINT' && skuDetail.authFlag === 'PASS'"
>
<Button
type="error"
<div class="item-select-column">
<div class="add-buy-car">
<el-button
type="danger"
v-if="skuDetail.goodsType !== 'VIRTUAL_GOODS'"
:loading="loading"
:disabled="skuDetail.quantity === 0"
@click="addShoppingCartBtn"
>加入购物车</Button
>加入购物车</el-button
>
<Button
type="warning"
<el-button
class="confirm-receipt-btn"
:loading="loading1"
:disabled="skuDetail.quantity === 0"
@click="buyNow"
>立即购买</Button
>立即购买</el-button
>
<Tooltip content="观看视频" v-if="skuDetail.goodsVideo">
<img class="view-video" @click="showGoodsVideo = true" :src="require('@/assets/iconfont/play.svg')" alt="">
</Tooltip>
<el-tooltip content="观看视频" v-if="skuDetail.goodsVideo">
<img class="view-video" @click="showGoodsVideo = true" :src="playIcon" alt="">
</el-tooltip>
</div>
</div>
</div>
<Modal title="浏览视频" v-model="showGoodsVideo">
</div>
</div>
<el-dialog title="浏览视频" v-model="showGoodsVideo">
<div id="dplayer"></div>
</Modal>
</el-dialog>
</div>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { StarFilled } from '@element-plus/icons-vue';
import Promotion from "./Promotion.vue";
import PicZoom from "vue-piczoom"; // 图片放大
import DPlayer from "dplayer";
import {
collectGoods,
@@ -316,6 +319,7 @@ import {
cancelCollect,
} from "@/api/member.js";
import { addCartGoods } from "@/api/cart.js";
import playIcon from "@/assets/iconfont/play.svg";
export default {
name: "ShowGoods",
@@ -331,9 +335,7 @@ export default {
handler(val) {
this.skuDetail = val.data;
this.wholesaleList = val.wholesaleList;
if (this.wholesaleList && this.wholesaleList.length > 0) {
this.count = this.wholesaleList[0].num;
}
this.syncCountWithQuantity();
this.promotion();
this.swiperGoodsImg();
},
@@ -348,6 +350,7 @@ export default {
},
data() {
return {
playIcon,
showGoodsVideo:false,
goodsVideo:"",
wholesaleList: [],
@@ -371,8 +374,15 @@ export default {
isCollected: false, // 是否收藏
};
},
components: { PicZoom, Promotion },
components: { Promotion, StarFilled },
computed: {
currentImg() {
const item = this.imgList[this.imgIndex];
return item ? item.url || item : "";
},
previewList() {
return this.imgList.map((item) => item.url || item);
},
wholesalePrice(key) {
return this.wholesaleList.length
? this.wholesaleList.map((item) => {
@@ -387,6 +397,13 @@ export default {
})
: [];
},
quantityMax() {
const qty = Number(this.skuDetail.quantity) || 0;
return qty > 0 ? qty : 1;
},
isOutOfStock() {
return (Number(this.skuDetail.quantity) || 0) <= 0;
},
},
methods: {
// 初始化video
@@ -407,17 +424,33 @@ export default {
},
syncCountWithQuantity() {
const qty = Number(this.skuDetail.quantity) || 0;
if (qty <= 0) {
this.count = 1;
return;
}
if (this.wholesaleList && this.wholesaleList.length > 0) {
this.count = Math.min(Math.max(this.wholesaleList[0].num, 1), qty);
return;
}
if (this.count > qty) {
this.count = qty;
} else if (this.count < 1) {
this.count = 1;
}
},
changeCount(val) {
if (this.wholesaleList && this.wholesaleList.length > 0) {
if (this.count <= this.wholesaleList[0].num) {
this.$Message.warning("批发商品购买数量不能小于起批数量");
Message.warning("批发商品购买数量不能小于起批数量");
this.count = this.wholesaleList[0].num;
}
}
},
select(index, value) {
// 选择规格
this.$set(this.currentSelceted, index, value);
this.currentSelceted[index] = value;
let selectedSkuId = this.goodsSpecList.find((i) => {
let matched = true;
let specValues = i.specValues.filter((j) => j.specName !== "images");
@@ -450,10 +483,16 @@ export default {
if (res.success) {
this.$router.push({
path: "/shoppingCart",
query: { detail: this.skuDetail, count: this.count },
query: {
skuId: this.skuDetail.id,
goodsId: this.skuDetail.goodsId,
thumbnail: this.skuDetail.thumbnail,
goodsName: this.skuDetail.goodsName,
count: this.count,
},
});
} else {
this.$Message.warning(res.message);
Message.warning(res.message);
}
})
.catch(() => {
@@ -481,7 +520,7 @@ export default {
query: { way: params.cartType },
});
} else {
this.$Message.warning(res.message);
Message.warning(res.message);
}
})
.catch(() => {
@@ -493,14 +532,14 @@ export default {
if (this.isCollected) {
let cancel = await cancelCollect("GOODS", this.skuDetail.id);
if (cancel.success) {
this.$Message.success("取消收藏成功");
Message.success("取消收藏成功");
this.isCollected = false;
}
} else {
let collect = await collectGoods("GOODS", this.skuDetail.id);
if (collect.code === 200) {
this.isCollected = true;
this.$Message.success("收藏商品成功,可以前往个人中心我的收藏查看");
Message.success("收藏商品成功,可以前往个人中心我的收藏查看");
}
}
},
@@ -556,9 +595,9 @@ export default {
// 领取优惠券
receiveCoupon(id).then((res) => {
if (res.success) {
this.$Message.success("优惠券领取成功");
Message.success("优惠券领取成功");
} else {
this.$Message.warning(res.message);
Message.warning(res.message);
}
});
},
@@ -652,9 +691,7 @@ export default {
height: 350px;
box-shadow: 0px 0px 8px $border_color;
cursor: pointer;
margin-bottom: 24px;
}
#dplayer{
width: 100%;
@@ -665,7 +702,7 @@ export default {
}
.item-detail-img-row {
margin-top: 15px;
margin-top: 0;
display: flex;
}
@@ -689,11 +726,23 @@ export default {
flex-direction: column;
}
.item-detail-title p {
.item-detail-title-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
padding: 8px 0;
}
.item-detail-name {
@include content_color($light_content_color);
font-weight: bold;
font-size: 20px;
padding: 8px 0;
line-height: 1.4;
}
.goods-type-tag {
flex-shrink: 0;
}
.item-detail-express {
@@ -774,7 +823,7 @@ export default {
color: $theme_color;
margin: 5px 0;
font-size: 12px;
background-color: #ffdedf;
background-color: var(--theme-tint-12, #ffdedf);
border: 1px dotted $theme_color;
cursor: pointer;
@@ -799,7 +848,7 @@ export default {
padding: 0 5px;
color: $theme_color;
font-size: 12px;
background-color: #ffdedf;
background-color: var(--theme-tint-12, #ffdedf);
border: 1px dotted $theme_color;
cursor: pointer;
@@ -843,6 +892,7 @@ export default {
.item-select {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 15px;
}
@@ -851,15 +901,25 @@ export default {
font-size: 14px;
margin-right: 15px;
width: 60px;
flex-shrink: 0;
display: flex;
align-items: center;
p {
margin: 0;
}
}
.item-select-column {
display: flex;
flex-wrap: wrap;
align-items: center;
flex: 1;
}
.item-select-row {
display: flex;
align-items: center;
margin-bottom: 8px;
}
@@ -898,26 +958,66 @@ export default {
border-top: 1px dotted $border_color;
}
.add-buy-car-row {
margin-top: 25px;
&.item-select {
margin-top: 25px;
}
.item-select-column {
flex: none;
width: 100%;
}
}
.add-buy-car {
display: flex;
align-items: center;
margin-top: 15px;
flex-wrap: wrap;
margin-top: 0;
padding-left: 0;
margin-left: 0;
> * {
margin: 0 4px;
margin: 0 8px 0 0;
}
> *:last-child {
margin-right: 0;
}
}
.goodsConfig {
display: flex;
justify-content: space-between;
> span {
.collect-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding-right: 10px;
&:hover {
color: #666;
cursor: pointer;
color: $theme_color;
line-height: 1;
&.is-collected {
.collect-icon {
color: #ffc107;
}
}
}
.collect-icon {
display: inline-flex;
align-items: center;
font-size: 16px;
}
.collect-text {
line-height: 1;
}
}
.sell-point {
font-size: 12px;
color: red;

View File

@@ -3,21 +3,23 @@
<div class="item-intro-show">
<div class="item-intro-detail" ref="itemIntroDetail">
<div class="item-intro-nav item-tabs">
<Tabs :animated="false" @on-click="tabClick">
<TabPane label="商品介绍">
<el-tabs v-model="activeDetailTab" @tab-click="(tab) => tabClick(tab.paneName)">
<el-tab-pane label="商品介绍" name="intro">
<div class="item-intro-img" ref="itemIntroGoods">
<div class="item-intro" v-html="skuDetail.intro" v-if="skuDetail.intro"></div>
<div v-else style="margin:20px;">暂无商品介绍</div>
</div>
</TabPane>
<TabPane label="商品评价">
</el-tab-pane>
<el-tab-pane label="商品评价" name="comment">
<div class="remarks-container" ref="itemGoodsComment">
<div class="remarks-analyse-box">
<div class="remarks-analyse-goods">
<i-circle :percent="skuDetail.grade" stroke-color="#5cb85c">
<el-progress type="circle" :percentage="Number(skuDetail.grade) || 0" color="#5cb85c" :width="120">
<template #default>
<span class="remarks-analyse-num">{{ skuDetail.grade }}%</span>
<p class="remarks-analyse-title">好评率</p>
</i-circle>
</template>
</el-progress>
</div>
</div>
<div class="remarks-bar">
@@ -31,12 +33,12 @@
</div>
<div class="remarks-box" v-for="(item,index) in commentList" :key="index" v-else>
<div class="remarks-user">
<Avatar :src="item.memberProfile" />
<span class="remarks-user-name">{{item.memberName | secrecyMobile}}</span>
<el-avatar :src="item.memberProfile" :size="40" />
<span class="remarks-user-name">{{ $filters.secrecyMobile(item.memberName ) }}</span>
</div>
<div class="remarks-content-box">
<p>
<Rate disabled :value="Number(item.descriptionScore)" allow-half class="remarks-star"></Rate>
<el-rate disabled :value="Number(item.descriptionScore)" allow-half class="remarks-star"></el-rate>
</p>
<p class="remarks-content">{{item.content}}</p>
<div class="comment-img" v-if="item.images">
@@ -50,8 +52,8 @@
<div class="preview-img" v-if="item.previewImg" @click.prevent="hidePreviewImg(item)">
<div>
<span @click.stop="rotatePreviewImg(0, item)"><Icon type="md-refresh" />左转</span>
<span @click.stop="rotatePreviewImg(1, item)"><Icon type="md-refresh" />右转</span>
<span @click.stop="rotatePreviewImg(0, item)"><el-icon><Refresh /></el-icon>左转</span>
<span @click.stop="rotatePreviewImg(1, item)"><el-icon><Refresh /></el-icon>右转</span>
</div>
<img :src="item.previewImg" :style="{transform:`rotate(${item.deg}deg)`}" width="198" alt="">
</div>
@@ -67,7 +69,12 @@
<p class="remarks-content">{{ item.reply }}</p>
<div>
<div class="comment-img" v-if="item.replyImage">
<div v-for="(img, imgIndex) in item.replyImage.split(',')" @click="$previewImage(img)" :key="imgIndex">
<div
v-for="(img, imgIndex) in item.replyImage.split(',')"
:key="imgIndex"
:class="{ borderColor: img === item.previewImg }"
@click="previewImg(img, item)"
>
<img :src="img" alt="">
</div>
</div>
@@ -78,15 +85,15 @@
</div>
</div>
<div class="remarks-page">
<Page :total="commentTotal" size="small"
@on-change="changePageNum"
@on-page-size-change="changePageSize"
:page-size="commentParams.pageSize"
></Page>
<el-pagination v-model:current-page="commentParams.pageNumber" v-model:page-size="commentParams.pageSize"
:total="commentTotal" small
@current-change="changePageNum"
@size-change="changePageSize"
layout="prev, pager, next"></el-pagination>
</div>
</div>
</TabPane>
<TabPane label="商品参数">
</el-tab-pane>
<el-tab-pane label="商品参数">
<template v-if="goodsParamsList.length">
<div class="item-param-container">
<div class="item-param-box" v-for="param in goodsParamsList" :key="param.paramId || param.paramName">
@@ -96,8 +103,8 @@
</div>
</template>
<div v-else>暂无商品参数</div>
</TabPane>
</Tabs>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
@@ -105,8 +112,10 @@
</template>
<script>
import { Refresh } from '@element-plus/icons-vue';
import { goodsComment, goodsCommentNum } from '@/api/member.js';
export default {
components: { Refresh },
name: 'ShowGoodsDetail',
props: {
detail: { // 商品详情
@@ -116,6 +125,7 @@ export default {
},
data () {
return {
activeDetailTab: "intro",
commentList: [], // 评论列表
commentParams: { // 评论传参
pageNumber: 1,
@@ -159,9 +169,13 @@ export default {
},
methods: {
changeHeight (name) { // 设置商品详情高度
let heightCss = window.getComputedStyle(this.$refs[name]).height;
heightCss = parseInt(heightCss.substr(0, heightCss.length - 2)) + 89;
this.$refs.itemIntroDetail.style.height = heightCss + 'px';
const el = this.$refs[name];
const container = this.$refs.itemIntroDetail;
if (!el || !container) return;
let heightCss = window.getComputedStyle(el).height;
heightCss = parseInt(heightCss, 10) + 89;
if (Number.isNaN(heightCss)) return;
container.style.height = heightCss + 'px';
},
changePageNum (val) { // 修改评论页码
this.commentParams.pageNumber = val;
@@ -187,12 +201,12 @@ export default {
});
},
viewByGrade (grade) { // 好中差评切换
this.$set(this.commentParams, 'grade', grade);
this.commentParams['grade'] = grade;
this.commentParams.pageNumber = 1;
this.getList();
},
tabClick (name) { // 商品详情和评价之间的tab切换
if (name === 0) {
if (name === "intro") {
this.$nextTick(() => {
this.changeHeight('itemIntroGoods')
});
@@ -203,13 +217,13 @@ export default {
}
},
previewImg (img, item) { // 预览图片
this.$set(item, 'previewImg', img);
item['previewImg'] = img;
this.$nextTick(() => {
this.changeHeight('itemGoodsComment')
});
},
hidePreviewImg (item) { // 隐藏预览图片
this.$set(item, 'previewImg', '');
item['previewImg'] = '';
this.$nextTick(() => {
this.changeHeight('itemGoodsComment')
});
@@ -217,15 +231,15 @@ export default {
rotatePreviewImg (type, item) { // 图片旋转
if (type) {
if (item.deg) {
this.$set(item, 'deg', item.deg + 90);
item['deg'] = item.deg + 90;
} else {
this.$set(item, 'deg', 90);
item['deg'] = 90;
}
} else {
if (item.deg) {
this.$set(item, 'deg', item.deg - 90);
item['deg'] = item.deg - 90;
} else {
this.$set(item, 'deg', -90);
item['deg'] = -90;
}
}
},
@@ -240,13 +254,18 @@ export default {
},
mounted () {
this.$nextTick(() => { // 手动设置详情高度,解决无法撑开问题
setTimeout(this.changeHeight('itemIntroGoods'), 2000);
setTimeout(() => {
this.changeHeight('itemIntroGoods');
}, 2000);
});
window.addEventListener('scroll', this.handleScroll)
this.getList();
if (this.skuDetail.grade === null || this.skuDetail.grade === undefined) {
this.skuDetail.grade = 100
}
},
beforeUnmount () {
window.removeEventListener('scroll', this.handleScroll);
}
};
</script>
@@ -319,7 +338,7 @@ export default {
font-weight: bolder;
}
.item-intro-detail{
margin: 0 30px;
margin: 0;
width: 100%;
}
.item-intro-nav{
@@ -346,8 +365,10 @@ export default {
}
.item-intro-img {
width: 100%;
box-sizing: border-box;
padding: 0 30px;
min-height: 300px;
::v-deep img{
:deep(img){
margin:0 auto;
}
}
@@ -358,6 +379,8 @@ export default {
.item-param-container {
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 0 30px;
}
.item-param-box {
padding: 5px;
@@ -381,6 +404,10 @@ export default {
color: #666666;
background-color: #F7F7F7;
}
.remarks-container {
box-sizing: border-box;
padding: 0 30px;
}
.remarks-analyse-box {
padding: 15px;
display: flex;
@@ -461,7 +488,7 @@ export default {
border-radius: 4px;
cursor: pointer;
}
span:nth-child(1) .ivu-icon {
span:nth-child(1) .el-icon {
transform: rotateY(180deg);
}
}
@@ -487,35 +514,39 @@ export default {
justify-content:flex-end;
}
/***************商品详情介绍和推荐侧边栏结束***************/
/* 改变便签页样式 */
.ivu-tabs-ink-bar {
/* 改变签页样式 */
.item-tabs {
:deep(.el-tabs__header) {
border: none;
margin: 0;
background-color: #f7f7f7;
}
:deep(.el-tabs__active-bar) {
background-color: $theme_color !important;
}
::v-deep.ivu-tabs-bar{
border: none;
}
.item-tabs > .ivu-tabs > .ivu-tabs-bar .ivu-tabs-tab{
border-radius: 0px;
:deep(.el-tabs__item) {
border-radius: 0;
color: #999;
height: 38px;
// background: #F7F7F7;
padding: 0 20px;
&:hover {
color: #999;
}
.item-tabs > .ivu-tabs > .ivu-tabs-bar .ivu-tabs-tab-active{
color: #fff;
background-color: $theme_color;
}
.item-tabs > .ivu-tabs > .ivu-tabs-bar .ivu-tabs-tab-active:before{
content: '';
display: block;
width: 100%;
height: 1px;
color: #fff;
background: #F7F7F7;
position: absolute;
top: 0;
left: 0;
:deep(.el-tabs__nav) {
padding-left: 10px;
}
.ivu-rate-star-full:before, .ivu-rate-star-half .ivu-rate-star-content:before {
:deep(.el-tabs__item.is-active) {
color: $theme_color !important;
background-color: transparent;
}
}
:deep(.el-rate__icon) {
color: $theme_color;
}
table{

View File

@@ -23,14 +23,13 @@
<li v-show="!!userInfo.username">
<div class="username-p">
<div>
<Avatar class="person-icon" :src="userInfo.face" icon="person" size="small"/>
<span class="username">{{
userInfo.nickName ? userInfo.nickName : userInfo.username | secrecyMobile
}}</span>
<el-avatar class="person-icon" :src="userInfo.face" :size="24" />
<span class="username">{{ $filters.secrecyMobile(
userInfo.nickName ? userInfo.nickName : userInfo.username ) }}</span>
</div>
<transition name='fade'>
<ul class="drop-items">
<li @click="goUserCenter('/home')">我的主页</li>
<li @click="goUserCenter('/home/MyOrder')">我的主页</li>
<li @click="goUserCenter('/home/Coupons')">优惠券</li>
<li @click="goUserCenter('/home/Favorites')">我的收藏</li>
<li @click="signOutFun">退出登录</li>
@@ -41,17 +40,20 @@
<li @click="goUserCenter('/home/MyOrder')"><span class="nav-item">我的订单</span></li>
<li @click="goUserCenter('/home/MyTracks')"><span class="nav-item">我的足迹</span></li>
<li @click="goUserCenter('/home/MsgList')"><span class="nav-item">我的消息</span></li>
<li v-if="$route.name !== 'Cart'" style="position:relative;">
<Dropdown placement="bottom-start">
<router-link to="/cart" target="_blank">
<span class="nav-item" @mouseenter="getCartList">
<Icon size="18" type="ios-cart-outline"></Icon>
<li v-if="$route.name !== 'Cart'" class="cart-nav-item">
<el-dropdown
class="cart-nav-dropdown"
placement="bottom-start"
trigger="hover"
@visible-change="handleCartDropdownVisible"
>
<span class="nav-item" @click="goToPay">
<el-icon :size="18"><ShoppingCart /></el-icon>
购物车{{ cartNum < 100 ? cartNum : '99' }}
</span>
</router-link>
<DropdownMenu slot="list">
<template #dropdown>
<div class="shopping-cart-null" style="width:200px" v-show="shoppingCart.length <= 0">
<Icon type="ios-cart-outline" class="cart-null-icon"></Icon>
<el-icon class="cart-null-icon"><ShoppingCart /></el-icon>
<span>你的购物车没有宝贝哦</span>
<span>赶快去添加商品吧~</span>
</div>
@@ -68,18 +70,16 @@
<div>
数量:
<span class="shopping-cart-text">{{ item.num }}</span>
</div>
<div class="shopping-price">
{{ item.purchasePrice | unitPrice('¥') }}
{{ $filters.unitPrice(item.purchasePrice, '¥') }}
</div>
</div>
</div>
</div>
</div>
</DropdownMenu>
</Dropdown>
</template>
</el-dropdown>
</li>
<li>
<span class="nav-item" @click="shopEntry">店铺入驻</span>
@@ -91,12 +91,15 @@
</template>
<script>
import { Modal } from "@/utils/message";
import { ShoppingCart } from '@element-plus/icons-vue';
import storage from '@/plugins/storage.js';
import {cartGoodsAll} from '@/api/cart.js';
import {logout} from '@/api/account.js';
import config from '@/config';
export default {
components: { ShoppingCart },
name: 'M-Header',
created() {
if (storage.getItem('userInfo')) {
@@ -106,9 +109,12 @@ export default {
data() {
return {
config: require('@/config'),
config,
userInfo: {}, // 用户信息
shoppingCart: [] // 购物车
shoppingCart: [], // 购物车
cartLoading: false,
cartLoadedAt: 0,
cartCacheTime: 5000
};
},
computed: {
@@ -126,7 +132,7 @@ export default {
},
myInfo() { // 跳转会员中心
let url = this.$router.resolve({
path: '/home'
path: '/home/MyOrder'
});
window.open(url.href, '_blank');
},
@@ -145,9 +151,9 @@ export default {
if (this.userInfo.username) {
this.$router.push({path: path});
} else {
this.$Modal.confirm({
Modal.confirm({
title: '请登录',
content: '<p>请登录后执行此操作</p>',
content: '请登录后执行此操作',
okText: '立即登录',
cancelText: '继续浏览',
onOk: () => {
@@ -174,15 +180,31 @@ export default {
this.$router.push('login');
}
},
handleCartDropdownVisible(visible) {
if (visible) {
this.getCartList();
}
},
getCartList() {
// 获取购物车列表
if (this.userInfo.username) {
if (!this.userInfo.username || this.cartLoading) {
return;
}
const now = Date.now();
if (this.cartLoadedAt && now - this.cartLoadedAt < this.cartCacheTime) {
return;
}
this.cartLoadedAt = now;
this.cartLoading = true;
cartGoodsAll().then((res) => {
this.shoppingCart = res.result.skuList;
if (res.success) {
this.shoppingCart = (res.result && res.result.skuList) || [];
this.$store.commit('SET_CARTNUM', this.shoppingCart.length);
this.Cookies.setItem('cartNum', this.shoppingCart.length);
});
}
}).finally(() => {
this.cartLoading = false;
});
}
}
};
@@ -196,25 +218,34 @@ export default {
height: 36px;
background: #333;
color: #fff;
display: flex;
align-items: center;
}
.nav {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 100%;
}
.nav ul {
.nav > ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
align-items: center;
}
.nav li {
.nav > ul > li,
.location > ul.flex > li {
cursor: pointer;
float: left;
float: none;
line-height: 36px;
margin-right: 15px;
display: flex;
align-items: center;
}
.nav a,
@@ -222,13 +253,36 @@ export default {
font-size: 13px;
font-weight: normal;
text-decoration: none;
cursor: pointer;
color: #fff;
display: inline-flex;
align-items: center;
line-height: 1;
}
.nav-item {
padding-left: 20px;
gap: 4px;
}
.cart-nav-item {
:deep(.cart-nav-dropdown),
:deep(.el-dropdown) {
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
vertical-align: middle;
line-height: 36px;
}
:deep(.el-tooltip__trigger),
:deep(.el-dropdown-selfdefine),
:deep([aria-expanded="true"]) {
outline: none !important;
border: none !important;
box-shadow: none !important;
}
}
.location a {
@@ -303,9 +357,12 @@ export default {
.username-p {
position: relative;
z-index: 100;
div {
cursor: pointer;
display: flex;
align-items: center;
> span {
margin-left: 5px;
@@ -315,59 +372,71 @@ export default {
.drop-items {
position: absolute;
display: none;
top: 45px;
left: 0;
right: 0;
margin: 0 auto;
padding: 5px 10px;
z-index: 20;
height: 150px;
flex-direction: column;
top: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
margin: 0;
padding: 4px 0;
z-index: 1000;
min-width: 100px;
width: max-content;
background-color: #fff;
width: 80px;
border: 1px solid #eee;
box-shadow: 2px 2px 7px #999;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
list-style: none;
li {
color: rgb(107, 106, 106);
display: block;
float: none;
width: 100%;
border-bottom: 1px solid rgb(207, 206, 206);
margin: 0;
padding: 8px 16px;
line-height: 1.4;
color: #666;
font-size: 13px;
font-weight: normal;
text-align: center;
white-space: nowrap;
border-bottom: 1px solid #f0f0f0;
box-sizing: border-box;
&:last-child {
border: none;
border-bottom: none;
}
&:hover {
cursor: pointer;
color: $theme_color;
background-color: #fff7f7;
}
}
&::before {
position: absolute;
top: -20px;
left: 30px;
top: -6px;
left: 50%;
transform: translateX(-50%);
content: '';
width: 0;
height: 0;
border: 10px solid #999;
border-color: transparent transparent #fff transparent;
border: 6px solid transparent;
border-bottom-color: #fff;
}
&::after {
content: '';
position: absolute;
width: 80px;
height: 20px;
top: -20px;
width: 100%;
height: 10px;
top: -10px;
left: 0;
}
}
&:hover {
.drop-items {
display: block;
display: flex;
}
}
}

View File

@@ -1,8 +1,12 @@
<template>
<div class="scroll-show">
<div class="content clearfix">
<cateNav class="cate" :hover="true" :showNavBar="false"
useClass="fixed-show"></cateNav>
<div class="content">
<cateNav
class="cate"
:hover="true"
:showNavBar="false"
useClass="fixed-show"
/>
<Search
class="search-con"
:hover="true"
@@ -10,98 +14,113 @@
:showLogo="false"
:showTag="false"
useClass="fixed-show"
></Search>
<div class="flex flex-a-c cart">
<Icon
type="ios-cart-outline"
@click="goCartList"
class="cart-icon"
@mouseenter.native="getCartList"
/>
<div class="cart">
<span @mouseenter="getCartList">
<el-icon class="cart-icon" :size="20" @click="goCartList"><ShoppingCart /></el-icon>
</span>
<i class="cart-badge">{{ cartNum < 100 ? cartNum : "99" }}</i>
</div>
</div>
</div>
</template>
<script>
import { cartCount } from "@/api/cart.js";
import storage from "@/plugins/storage.js";
import Search from '@/components/Search.vue'
import cateNav from '@/components/nav/CateNav.vue'
import { cartCount } from '@/api/cart.js'
import storage from '@/plugins/storage.js'
import { ShoppingCart } from '@element-plus/icons-vue'
export default {
components: { ShoppingCart, Search, cateNav },
data() {
return {
userInfo: {}, // 用户信息
};
userInfo: {}
}
},
computed: {
cartNum() {
// 购物车商品数量
return this.$store.state.cartNum;
},
return this.$store.state.cartNum
}
},
methods: {
goCartList() {
// 跳转购物车页面
let routerUrl = this.$router.resolve({
path: "/cart",
});
window.open(routerUrl.href, "_blank");
const routerUrl = this.$router.resolve({ path: '/cart' })
window.open(routerUrl.href, '_blank')
},
getCartList() {
// 获取购物车列表
if (storage.getItem("userInfo")) {
if (storage.getItem('userInfo')) {
cartCount().then((res) => {
this.$store.commit("SET_CARTNUM", res.result);
this.Cookies.setItem("cartNum", res.result);
});
this.$store.commit('SET_CARTNUM', res.result)
this.Cookies.setItem('cartNum', res.result)
})
}
}
},
},
mounted() {
if (storage.getItem("userInfo")) {
this.userInfo = JSON.parse(storage.getItem("userInfo"));
if (storage.getItem('userInfo')) {
this.userInfo = JSON.parse(storage.getItem('userInfo'))
}
}
}
},
};
</script>
<style lang="scss" scoped>
$side-col-width: 263.2px;
$col-gap: 10px;
$cart-col-width: 40px;
.scroll-show {
height: 60px;
overflow: hidden;
}
.content {
width: 1200px;
height: 40px;
height: 60px;
margin: 0 auto;
position: relative;
display: grid;
grid-template-columns: $side-col-width 1fr $cart-col-width;
column-gap: $col-gap;
align-items: center;
}
.cate {
float: left;
width: 200px !important;
min-width: 0;
:deep(.cate-nav) {
width: 100%;
margin: 0;
}
.search-con {
float: left;
width: 800px;
overflow: hidden;
margin-top: -27px;
}
.cart{
:deep(.nav-con) {
height: 60px;
}
.cart-icon {
width: 30px;
float: left;
font-size: 25px;
}
color: $theme_color;
z-index: 1;
.search-con {
min-width: 0;
width: 100%;
}
.cart {
display: flex;
align-items: center;
justify-content: center;
height: 60px;
position: relative;
&:hover {
}
.cart-icon {
font-size: 25px;
color: $theme_color;
cursor: pointer;
}
}
.cart-badge {
position: absolute;
font-style: normal;
right: 165px;
top: 12px;
right: -2px;
display: block;
background-color: $theme_color;
color: #fff;
@@ -112,6 +131,5 @@ export default {
line-height: 17px;
text-align: center;
z-index: 5;
top: 10px;
}
</style>

View File

@@ -1,15 +1,13 @@
<template>
<div class="model-form">
<div class="model-content">
<template v-for="(element, index) in data.list">
<model-form-item
v-if="element && element.key"
v-for="(element, index) in renderList"
:key="element.key"
:element="element"
:index="index"
:data="data"
></model-form-item>
</template>
</div>
</div>
</template>
@@ -20,12 +18,34 @@ export default {
components: {
ModelFormItem
},
props: ['data']
props: ['data'],
computed: {
renderList() {
const list = this.data?.list || [];
return list.filter(
(el) => el && el.key && !this.isBuiltinModule(el.type)
);
}
},
methods: {
isBuiltinModule(type) {
// 顶部广告、快捷导航由 Index 页单独渲染
return type === 'topAdvert' || type === 'navBar';
}
}
};
</script>
<style lang="scss" scoped>
.model-form {
position: relative;
z-index: 0;
width: 100%;
}
.model-content {
width: 100%;
min-height: 1200px;
box-sizing: border-box;
/* 勿设 overflow-x: hidden会裁切 discountAdvert 的 margin-left: -100px 左溢背景 */
overflow-x: visible;
}
</style>

View File

@@ -2,10 +2,9 @@
<div class="model-item" v-if="element && element.key">
<!-- 轮播图模块包括个人信息快捷导航模块 -->
<template v-if="element.type == 'carousel'">
<model-carousel
:data="element"
class="mb_20 width_1200_auto"
></model-carousel>
<div class="carousel-wrap mb_20">
<model-carousel :key="element.key" :data="element"></model-carousel>
</div>
</template>
<template v-if="element.type == 'carousel1'">
<model-carousel1 :data="element" class="mb_20"></model-carousel1>
@@ -18,42 +17,42 @@
</template>
<!-- 热门广告 -->
<template v-if="element.type == 'hotAdvert'">
<div class="mb_20 width_1200_auto">
<div class="hot-advert mb_20 width_1200_auto">
<div class="hot-advert-banner">
<img
style="display: block"
class="hover-pointer"
:src="element.options.list[0].img"
@click="linkTo(element.options.list[0].url)"
width="1200"
alt=""
/>
</div>
<ul class="advert-list width_1200_auto">
<template v-for="(item, index) in element.options.list">
<ul class="advert-list">
<template v-for="(item, index) in element.options.list" :key="index">
<li
v-if="index !== 0"
@click="linkTo(item.url)"
class="hover-pointer"
:key="index"
>
<img :src="item.img" width="230" height="190" alt="" />
<img :src="item.img" alt="" />
</li>
</template>
</ul>
</div>
</template>
<!-- 限时秒杀 待完善 -->
<template v-if="element.type == 'seckill' && element.options.list.length">
<template v-if="element.type == 'seckill' && element.options?.list?.length">
<seckill :data="element" class="mb_20 width_1200_auto"></seckill>
</template>
<!-- 折扣广告 -->
<template v-if="element.type == 'discountAdvert'">
<div class="discountAdvert-wrap width_1200_auto mb_20">
<div
class="discountAdvert"
:style="{
backgroundImage:
'url(' + require('@/assets/images/decorate.png') + ')',
backgroundImage: 'url(' + decorateImg + ')',
}"
>
<div>
<img
@click="linkTo(item.url)"
class="hover-pointer"
@@ -64,6 +63,8 @@
height="210"
alt=""
/>
</div>
<div>
<img
@click="linkTo(item.url)"
class="hover-pointer"
@@ -75,6 +76,8 @@
alt=""
/>
</div>
</div>
</div>
</template>
<!-- 好货推荐 -->
@@ -117,15 +120,12 @@
></first-page-advert>
</template>
<!-- 横幅广告 -->
<template v-if="element.type == 'bannerAdvert'">
<div style="width: 100%; text-align: center">
<img
width="1200"
class="hover-pointer mb_20 bannerAd"
<template v-if="element.type == 'bannerAdvert' && element.options.img">
<div
class="horizontal-advert width_1200_auto mb_20 hover-pointer"
@click="linkTo(element.options.url)"
:src="element.options.img"
alt=""
/>
>
<img class="bannerAd" :src="element.options.img" alt="" />
</div>
</template>
<template v-if="element.type == 'notEnough'"
@@ -152,7 +152,7 @@ import goodsAndType from "./modelList/goodsAndType.vue";
import onlyGoodsModel from "./modelList/onlyGoodsModel.vue";
import mixModel from "./modelList/mixModel.vue";
import forYour from "./modelList/forYour.vue";
import decorateImg from "@/assets/images/decorate.png";
export default {
name: "modelFormItem",
@@ -174,6 +174,7 @@ export default {
},
data() {
return {
decorateImg,
showModal: false, // 控制模态框显隐
selected: {}, // 已选数据
};
@@ -184,21 +185,65 @@ export default {
.model-item {
position: relative;
margin-bottom: 10px;
width: 100%;
box-sizing: border-box;
}
.carousel-wrap {
width: 100%;
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
overflow: hidden;
}
/** 横幅广告 */
.horizontal-advert {
width: 100%;
max-width: 1200px;
overflow: hidden;
border-radius: 10px;
line-height: 0;
}
.bannerAd {
width: 1183px;
height: 166.6px;
border-radius: 10px;
display: block;
width: 100%;
height: auto;
max-height: 166px;
object-fit: cover;
}
/** 热门广告 */
.advert-list {
background: $theme_color;
height: 200px;
display: flex;
justify-content: space-around;
padding: 3px 10px;
> li {
.hot-advert {
width: 1200px;
max-width: 100%;
overflow: hidden;
.hot-advert-banner {
line-height: 0;
img {
display: block;
width: 100%;
height: auto;
}
}
}
.advert-list {
width: 100%;
margin: 0;
padding: 10px;
list-style: none;
box-sizing: border-box;
background: $theme_color;
height: 210px;
display: flex;
gap: 10px;
align-items: center;
> li {
flex: 1 1 0;
min-width: 0;
img {
display: block;
width: 100%;
height: 190px;
cursor: pointer;
border-radius: 10px;
transition: all 150ms ease-in-out;
@@ -211,21 +256,37 @@ export default {
}
/** 折扣广告 */
.discountAdvert-wrap {
width: 1200px;
position: relative;
overflow: visible;
}
.discountAdvert {
width: 1300px;
height: 566px;
margin: 0 auto;
margin-bottom: 20px;
height: 585px;
margin-left: -100px;
box-sizing: border-box;
background-repeat: no-repeat;
background-size: 1300px 585px;
background-position: left top;
position: relative;
left: -47px;
> div {
padding-left: 295px;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
img {
box-sizing: border-box;
&:nth-child(1) img {
margin: 10px 10px 0 0;
transition: all 150ms ease-in-out;
&:hover {
box-shadow: 0 5px 12px 0 rgba(0, 0, 0, 0.4);
transform: translateY(-2px);
}
}
&:nth-child(2) {
margin-top: 10px;
margin-right: 10px;
img {
margin: 0 10px 0 0;
transition: all 150ms ease-in-out;
&:hover {
box-shadow: 0 5px 12px 0 rgba(0, 0, 0, 0.4);
@@ -233,5 +294,7 @@ export default {
}
}
}
}
}
</style>

View File

@@ -1,13 +1,20 @@
<template>
<div class="model-carousel">
<div class="nav-body clearfix">
<!-- 侧边导航占位 -->
<div class="nav-side"></div>
<div class="nav-body">
<!-- 侧边导航占位 CateNav 分类栏同宽勿与 CateNav .nav-side 混用类名 -->
<div class="carousel-side-spacer"></div>
<div class="nav-content">
<!-- 轮播图 -->
<!-- autoplay autoplay-speed="10000"-->
<Carousel :height="333.9" autoplay :autoplay-speed="5000">
<CarouselItem v-for="(item, index) in data.options.list" :key="index">
<!-- 轮播图无有效图片时不挂载 el-carousel勿对 el-carousel 使用随图片变化的 key否则 Vue3 patch 会报 emitsOptions 错误 -->
<el-carousel
v-if="displaySlides.length"
height="334px"
:interval="5000"
:loop="displaySlides.length > 1"
>
<el-carousel-item
v-for="(item, index) in displaySlides"
:key="carouselItemKey(item, index)"
>
<div class="swiper-img">
<img
:src="item.img"
@@ -15,8 +22,9 @@
class="hover-pointer"
/>
</div>
</CarouselItem>
</Carousel>
</el-carousel-item>
</el-carousel>
<div v-else class="swiper-img carousel-placeholder"></div>
</div>
<div class="nav-right">
<div class="person-msg">
@@ -24,7 +32,7 @@
<img class="user-face" :src="userInfo.face || defaultAvatar" alt />
<div class="welcome">
Hi, {{
userInfo.nickName || `欢迎来到${config.title}` | secrecyMobile
$filters.secrecyMobile(userInfo.nickName || `欢迎来到${config.title}`)
}}
</div>
</div>
@@ -43,18 +51,14 @@
</div> -->
</div>
<div v-else class="flex flex-a-c ">
<div class="btns" @click="$router.push('login')" shape="circle"
>登录</div
>
<div class="btns sign-up" @click="$router.push('signUp')" shape="circle"
>注册</div
>
<div class="btns" @click="$router.push('login')">登录</div>
<div class="btns sign-up" @click="$router.push('signUp')">注册</div>
</div>
<div class="gray-line"></div>
<div class="icon-list flex flex-j-sb" >
<div class="icon-item" @click="entryControl(item)" :key="index" v-for="(item, index) in entranceList">
<img class="icon" :src="require(`@/assets/iconfont/${item.icon}.png`)">
<img class="icon" :src="getIconUrl(item.icon)">
<div>
{{ item.label }}
</div>
@@ -62,7 +66,7 @@
</div>
<div class="icon-list flex flex-j-sb" >
<div class="icon-item" :key="index" @click="entryControl(item)" v-for="(item, index) in appendList">
<img class="icon" :src="require(`@/assets/iconfont/${item.icon}.png`)">
<img class="icon" :src="getIconUrl(item.icon)">
<div>
{{ item.label }}
</div>
@@ -81,13 +85,24 @@
<script>
import storage from "@/plugins/storage";
import config from "@/config";
import defaultAvatar from "@/assets/images/default.png";
import { getIconUrl } from "@/assets/iconfont/iconMap";
export default {
name: "modelCarousel",
props: ["data"],
computed: {
carouselList() {
return this.data?.options?.list || [];
},
displaySlides() {
return this.carouselList.filter((item) => item && item.img);
},
},
data() {
return {
config: require("@/config"),
defaultAvatar: require("@/assets/images/default.png"),
config,
defaultAvatar,
userInfo: {}, // 用户信息
entranceList: [
{
@@ -154,11 +169,34 @@ export default {
};
},
methods: {
getIconUrl,
carouselItemKey(item, index) {
return item?.img || item?.url || `slide-${index}`;
},
resolveRoute(pathStr) {
if (!pathStr) return { path: "/" };
const [path, search] = pathStr.split("?");
if (!search) return { path };
return {
path,
query: Object.fromEntries(new URLSearchParams(search)),
};
},
// 快捷跳转中心
entryControl(val) {
console.log("val",val)
let url = this.$router.resolve(val.path);
window.open(url.href, '_blank');
const location = this.resolveRoute(val.path);
if (location.path.startsWith("/home/") && !storage.getItem("userInfo")) {
this.$router.push({
path: "/login",
query: {
rePath: location.path,
query: JSON.stringify(location.query || {}),
},
});
return;
}
const url = this.$router.resolve(location);
window.open(url.href, "_blank");
},
},
mounted() {
@@ -182,10 +220,15 @@ export default {
.swiper-img {
overflow: hidden;
width: 637px;
width: 100%;
height: 329.9px;
}
.carousel-placeholder {
border-radius: 10px;
background: #ececec;
}
.icon-list {
width: 216px
}
@@ -265,21 +308,21 @@ export default {
}
.model-carousel {
width: 1200px;
width: 100%;
max-width: 1200px;
height: 340px;
margin: 0 auto;
overflow: hidden;
box-sizing: border-box;
position: relative;
z-index: 0;
}
.hover-pointer {
//display: block;
//width: 100%;
//height: 100%;
width: 637px;
width: 100%;
height: 329.9px;
object-fit: cover;
border-radius: 10px;
}
.welcome {
@@ -334,44 +377,62 @@ export default {
}
/* 导航主体 */
/* 导航主体263.2 + 10 + 1fr + 10 + 263.2 = 1200px与下方楼层右缘对齐 */
.nav-body {
width: 1200px;
width: 100%;
max-width: 1200px;
height: 340px;
margin: 0px auto;
margin: 0 auto;
display: grid;
grid-template-columns: 263.2px minmax(0, 1fr) 263.2px;
column-gap: 10px;
align-items: start;
box-sizing: border-box;
}
.nav-side {
height: 334px;
.carousel-side-spacer {
grid-column: 1;
width: 263.2px;
height: 334px;
flex-shrink: 0;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
float: left;
padding: 0px;
color: #fff;
// background-color: #6e6568;
pointer-events: none;
}
/*导航内容*/
.nav-content {
width: 637px;
margin-left: 10.8px;
grid-column: 2;
width: 100%;
min-width: 0;
margin-top: 10px;
height: 333.9px;
float: left;
position: relative;
overflow: hidden;
:deep(.el-carousel) {
width: 100%;
height: 100%;
border-radius: 10px;
overflow: hidden;
}
:deep(.el-carousel__container) {
height: 334px;
}
:deep(.el-carousel__item) {
width: 100%;
}
}
.nav-right {
float: left;
margin-top: 10px;
grid-column: 3;
width: 263.2px;
margin-left: 10px;
min-width: 263.2px;
max-width: 263.2px;
margin-top: 10px;
border-radius: 10px;
background: #FFFFFF;

View File

@@ -5,13 +5,13 @@
<div class="nav-side"></div>
<div class="nav-content">
<!-- 轮播图 -->
<Carousel autoplay @on-change="autoChange">
<CarouselItem v-for="(item, index) in data.options.list" :key="index" >
<el-carousel height="470px" :interval="5000" @change="autoChange">
<el-carousel-item v-for="(item, index) in data.options.list" :key="index">
<div style="overflow: hidden">
<img :src="item.img" width="1200" height="470" />
</div>
</CarouselItem>
</Carousel>
</el-carousel-item>
</el-carousel>
</div>
</div>
</div>
@@ -34,8 +34,8 @@ export default {
},
methods: {
// 自动切换时改变背景色
autoChange (oVal, val) {
this.bgColor = this.data.options.list[val].bgColor
autoChange (index) {
this.bgColor = this.data.options.list[index].bgColor
}
}
};

View File

@@ -5,18 +5,17 @@
<div class="nav-side"></div>
<div class="nav-content">
<!-- 轮播图 -->
<Carousel autoplay>
<CarouselItem v-for="(item, index) in data.options.list" :key="index">
<el-carousel height="470px" :interval="5000">
<el-carousel-item v-for="(item, index) in data.options.list" :key="index">
<div style="overflow: hidden">
<img :src="item.img" width="590" height="470" />
</div>
</CarouselItem>
</Carousel>
</el-carousel-item>
</el-carousel>
</div>
<div class="nav-content1">
<!-- 轮播图 -->
<Carousel autoplay :autoplay-speed="5000">
<CarouselItem v-for="(item, index) in data.options.listRight" :key="index">
<el-carousel height="470px" :interval="5000">
<el-carousel-item v-for="(item, index) in data.options.listRight" :key="index">
<div class="mb_10">
<img :src="item[0].img" width="190" height="150" />
</div>
@@ -26,19 +25,19 @@
<div>
<img :src="item[2].img" width="190" height="150" />
</div>
</CarouselItem>
</Carousel>
</el-carousel-item>
</el-carousel>
</div>
<div class="nav-right">
<div class="person-msg">
<img :src="userInfo.face" v-if="userInfo.face" alt />
<Avatar icon="ios-person" class="mb_10" v-else size="80" />
<div>Hi{{ userInfo.nickName || `欢迎来到${config.title}` | secrecyMobile }}</div>
<el-avatar v-else :size="80" class="mb_10"><el-icon><User /></el-icon></el-avatar>
<div>Hi{{ $filters.secrecyMobile(userInfo.nickName || `欢迎来到${config.title}`) }}</div>
<div v-if="userInfo.id">
<Button type="error" shape="circle">会员中心</Button>
<el-button type="danger">会员中心</el-button>
</div>
<div v-else>
<Button type="error" shape="circle">请登录</Button>
<el-button type="danger">请登录</el-button>
</div>
</div>
<div class="shop-msg">
@@ -60,12 +59,15 @@
import {articleList} from '@/api/common.js'
import storage from '@/plugins/storage';
import { User } from '@element-plus/icons-vue';
import config from '@/config';
export default {
components: { User },
name: 'modelCarousel2',
props: ['data'],
data () {
return {
config:require('@/config'),
config,
userInfo: {}, // 用户信息
articleList: [], // 常见问题
params: { // 请求常见问题参数
@@ -163,7 +165,7 @@ export default {
margin-top: 10px;
}
.ivu-btn-default {
.el-button--default {
color: $theme_color;
border-color: $theme_color;
}

View File

@@ -4,8 +4,7 @@
<div class="top-header" :style="{ background: options.left.bgColor }">
<span>{{ options.left.title }}</span>
<span @click="linkTo(options.left.url)" class="hover-pointer"
>{{ options.left.secondTitle }} &gt;</span
>
>{{ options.left.secondTitle }} &gt;</span>
</div>
<div class="content">
<div
@@ -27,8 +26,7 @@
<div class="top-header" :style="{ background: options.middle.bgColor }">
<span>{{ options.middle.title }}</span>
<span class="hover-pointer" @click="linkTo(options.middle.url)"
>{{ options.middle.secondTitle }} &gt;</span
>
>{{ options.middle.secondTitle }} &gt;</span>
</div>
<div class="content">
<div
@@ -50,8 +48,7 @@
<div class="top-header" :style="{ background: options.right.bgColor }">
<span>{{ options.right.title }}</span>
<span @click="linkTo(options.right.url)" class="hover-pointer"
>{{ options.right.secondTitle }} &gt;</span
>
>{{ options.right.secondTitle }} &gt;</span>
</div>
<div class="content">
<div
@@ -60,13 +57,15 @@
class="hover-pointer"
@click="linkTo(item.url)"
>
<div class="img-wrap">
<img :src="item.img" alt="" />
<p>{{ item.name }}</p>
<p>{{ item.price | unitPrice("¥") }}</p>
<div class="jiaobiao" :class="'jiaobiao' + (index + 1)">
{{ index + 1 }}
</div>
</div>
<p>{{ item.name }}</p>
<p>{{ $filters.unitPrice(item.price, "¥") }}</p>
</div>
</div>
</div>
</div>
@@ -91,85 +90,169 @@ export default {
.new-goods {
display: flex;
justify-content: space-between;
align-items: stretch;
> div {
width: 393px;
height: 440px;
overflow: hidden;
}
.left > .content {
> div:nth-child(1) {
height: 240px;
flex-direction: column;
border: 1px solid #eee;
border-top: none;
border-left: none;
justify-content: space-between;
img {
width: 160px;
height: 160px;
}
.describe {
margin-top: 10px;
}
}
> div:nth-child(2) {
border-right: 1px solid #eee;
}
> div:nth-child(3),
> div:nth-child(4) {
border-bottom: 1px solid #eee;
}
}
.middle > .content {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3, 1fr);
flex-direction: unset;
overflow: hidden;
> div {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
box-sizing: border-box;
display: flex;
padding: 6px 4px 4px 6px;
border-style: solid;
border-color: #eee;
border-width: 0;
border-bottom-width: 1px;
border-right-width: 1px;
img {
width: 64px;
height: 64px;
margin-top: 2px;
flex-shrink: 0;
object-fit: cover;
}
> div {
flex: 1;
min-width: 0;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 1.3;
}
.describe {
margin-top: 4px;
font-size: 11px;
}
}
}
> div:nth-child(1) {
grid-row: span 2;
flex-direction: column;
justify-content: space-between;
align-items: center;
text-align: center;
padding: 8px 6px;
img {
width: 120px;
height: 120px;
margin-top: 6px;
}
> div {
flex: unset;
width: 100%;
p {
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.describe {
margin-top: 6px;
white-space: normal;
}
}
}
> div:nth-child(1),
> div:nth-child(2),
> div:nth-child(3) {
> div:nth-child(3),
> div:nth-child(5) {
border-right-width: 0;
}
> div:nth-child(4),
> div:nth-child(5) {
border-bottom-width: 0;
}
}
.middle > .content {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3, 1fr);
flex-direction: unset;
> div {
width: 100%;
height: 100%;
min-width: 0;
overflow: hidden;
box-sizing: border-box;
border-style: solid;
border-color: #eee;
border-width: 0;
border-bottom-width: 1px;
border-right-width: 1px;
}
> div:nth-child(6),
> div:nth-child(3) {
> div:nth-child(2n) {
border-right-width: 0;
}
> div:nth-child(n + 5) {
border-bottom-width: 0;
}
}
.right > .content {
display: flex;
flex-wrap: wrap;
flex-direction: row;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
flex-direction: unset;
flex-wrap: unset;
font-size: 12px;
overflow: hidden;
> div {
position: relative;
width: 120px;
padding: 5px 10px 0 10px;
img {
width: 100px;
height: 100px;
}
border-bottom: 1px solid #eee;
:nth-child(2) {
height: 38px;
width: 100%;
height: 100%;
min-height: 0;
box-sizing: border-box;
padding: 4px 4px 2px;
display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
}
:nth-child(3) {
color: $theme_color;
margin-top: 5px;
border-bottom: 1px solid #eee;
border-right: 1px solid #eee;
.img-wrap {
position: relative;
width: 80px;
height: 80px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.jiaobiao {
position: absolute;
width: 23px;
height: 23px;
top: 10px;
right: 16px;
background: url(../../../assets/images/festival_icon.png);
top: 0;
right: 0;
margin: 0;
padding: 0;
max-height: none;
overflow: visible;
display: block;
background: url(../../../assets/images/festival_icon.png) no-repeat;
color: #fff;
text-align: center;
line-height: 23px;
font-size: 12px;
-webkit-line-clamp: unset;
-webkit-box-orient: unset;
}
.jiaobiao1,
.jiaobiao4 {
@@ -184,10 +267,30 @@ export default {
background-position: -60px -30px;
}
}
> div:nth-child(4),
> div:nth-child(5),
> div:nth-child(6) {
border: none;
> p:nth-child(2) {
width: 100%;
height: auto;
max-height: 32px;
line-height: 16px;
margin-top: 4px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-align: center;
}
> p:nth-child(3) {
color: $theme_color;
margin-top: 2px;
line-height: 1.2;
flex-shrink: 0;
}
}
> div:nth-child(3n) {
border-right: none;
}
> div:nth-child(n + 4) {
border-bottom: none;
}
}
@@ -212,6 +315,8 @@ export default {
flex-wrap: wrap;
flex-direction: column;
height: 370px;
box-sizing: border-box;
overflow: hidden;
}
.con-item {
width: 185px;
@@ -225,6 +330,42 @@ export default {
margin-top: 10px;
}
}
.middle > .content .con-item {
width: 100%;
max-width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
box-sizing: border-box;
padding: 6px 4px 4px 6px;
img {
width: 64px;
height: 64px;
margin-top: 2px;
flex-shrink: 0;
}
> div {
flex: 1;
min-width: 0;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 1.3;
}
.describe {
margin-top: 4px;
font-size: 11px;
}
}
}
.left > .content .con-item {
width: 100%;
max-width: 100%;
height: 100%;
}
.describe {
color: #999;
font-size: 12px;

View File

@@ -1,7 +1,8 @@
<template>
<div class="not-enough" ref="obtain" id="demo">
<Affix :offset-top="62" @on-change="change">
<ul class="nav-bar" v-show="topSearchShow">
<el-affix :offset-top="62" @change="change" class="nav-affix">
<div class="nav-bar" v-show="topSearchShow">
<ul class="nav-list">
<li
v-for="(item, index) in conData.options.navList"
:class="currentIndex === index ? 'curr' : ''"
@@ -12,7 +13,8 @@
<p @click="gotoDemo">{{ item.desc }}</p>
</li>
</ul>
</Affix>
</div>
</el-affix>
<div class="content" v-if="showContent">
<div
v-for="(item, index) in conData.options.list[currentIndex]"
@@ -23,7 +25,7 @@
<img :src="item.img" width="210" height="210" :alt="item.name" />
<p>{{ item.name }}</p>
<p>
<span>{{ Number(item.price) | unitPrice("¥") }}</span>
<span>{{ $filters.unitPrice(Number(item.price), "¥") }}</span>
</p>
</div>
</div>
@@ -95,18 +97,34 @@ export default {
};
</script>
<style lang="scss" scoped>
.not-enough {
width: 100%;
box-sizing: border-box;
}
:deep(.nav-affix.el-affix) {
width: 100%;
}
.nav-bar {
display: flex;
justify-content: center;
width: 100%;
margin-bottom: 10px;
background-color: #f8f8f8;
height: 60px;
box-sizing: border-box;
}
.nav-list {
display: flex;
justify-content: center;
align-items: center;
position: relative;
position: sticky;
position: -webkit-sticky;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
list-style: none;
box-sizing: border-box;
li {
padding: 0 30px;
text-align: center;
@@ -114,11 +132,15 @@ export default {
font-size: 16px;
border-radius: 50px;
padding: 0 7px;
margin: 0;
line-height: 1.2;
}
p:nth-child(2) {
font-size: 14px;
color: #999;
margin: 2px 0 0;
line-height: 1.2;
}
&:hover {
@@ -129,6 +151,7 @@ export default {
}
border-right: 1px solid #eee;
}
li:last-of-type {
border: none;
}
@@ -136,7 +159,6 @@ export default {
.curr {
p:nth-child(1) {
background-color: $theme_color;
color: #fff;
}
p:nth-child(2) {
@@ -146,6 +168,8 @@ export default {
}
.content {
width: 100%;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
justify-content: space-between;

View File

@@ -10,12 +10,12 @@
<img class="hover-pointer" @click="linkTo(msgLeft.list[0].url)" :src="msgLeft.list[0].img" width="160" height="160" alt="" />
<div class="margin-left">{{ msgLeft.list[0].name }}</div>
<div class="margin-left">{{ msgLeft.list[0].describe }}</div>
<Button
<el-button
size="small"
:style="{ background: msgLeft.bgColor }"
@click="linkTo(msgLeft.list[0].url)"
class="fz_12 view-btn"
>点击查看</Button
>点击查看</el-button
>
</div>
<div>
@@ -36,8 +36,7 @@
<div class="head-recommend" :style="{ background: msgRight.bgColor }">
<span>{{ msgRight.title }}</span>
<span @click="linkTo(msgRight.url)" class="hover-pointer"
>{{ msgRight.secondTitle }}&gt;</span
>
>{{ msgRight.secondTitle }}&gt;</span>
</div>
<div class="content-right">
<div
@@ -45,7 +44,7 @@
:key="index"
@click="linkTo(item.url)" class="hover-pointer"
>
<div class="right-item" :style="{'border': index===2 || index===3 ?'none': ''}">
<div class="right-item">
<div>
<span :style="{ background: msgRight.bgColor }">{{item.name}}</span>
<span>{{ item.describe }}</span>
@@ -84,12 +83,17 @@ export default {
width: 595px;
.content-left {
display: flex;
align-items: stretch;
padding-top: 10px;
font-size: 12px;
box-sizing: content-box;
> div:nth-child(1) {
width: 189px;
border-right: 1px solid #eee;
height: 360px;
display: flex;
flex-direction: column;
box-sizing: border-box;
img {
margin: 40px 0 0 15px;
}
@@ -107,15 +111,20 @@ export default {
color: #999;
}
.view-btn {
margin-left: 15px;
margin-top: 10px;
align-self: center;
width: 145px;
margin-top: auto;
margin-bottom: 15px;
color: #fff;
}
}
> div:nth-child(2) {
width: 405px;
height: 360px;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
box-sizing: border-box;
> div {
display: flex;
align-items: center;
@@ -136,55 +145,78 @@ export default {
.recommend-right {
width: 595px;
height: 360px;
.head-recommend {
background: #a25684;
}
.content-right {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
height: 360px;
padding-top: 10px;
> div {
width: 50%;
text-align: center;
height: 180px;
padding-top: 10px;
.right-item {
border-bottom: 1px solid #eee;
display: flex;
margin-top: 30px;
margin-left: 5px;
margin-right: 5px;
height: 150px;
padding: 0 10px;
font-size: 12px;
>div:nth-child(1) {
box-sizing: content-box;
> div {
width: 100%;
height: 100%;
text-align: center;
padding: 0;
border-right: 1px solid #eee;
border-bottom: 1px solid #eee;
box-sizing: border-box;
&:nth-child(2n) {
border-right: none;
}
&:nth-child(n + 3) {
border-bottom: none;
}
.right-item {
border-bottom: none;
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
margin: 0;
padding: 10px 15px;
font-size: 12px;
box-sizing: border-box;
> div:nth-child(1) {
flex: 1;
min-width: 0;
margin-top: 0;
text-align: left;
margin-top: 30px;
span:nth-child(1) {
color: #fff;
border-radius: 10px;
padding: 0 5px;
background-color: #a25684;
display: block;
max-width: 120px;
overflow: hidden;
white-space: nowrap;
margin: 0 10px 10px 0;
margin: 0 0 10px;
}
span:nth-child(2) {
font-size: 12px;
color: #666;
display: block;
}
}
.right-img {
width: 100;
width: 100px;
height: 100px;
flex-shrink: 0;
text-align: center;
margin: 0 auto;
margin: 0;
img {
max-height: 100px;
max-width: 100px;
@@ -192,9 +224,6 @@ export default {
}
}
}
> div:nth-child(n + 1) {
border-right: 1px solid #eee;
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="seckill" v-if="goodsList.length">
<div class="seckill" v-if="goodsList && goodsList.length">
<div class="aside hover-pointer" @click="goToSeckill">
<div class="title">{{ actName }}</div>
<div class="hour">
@@ -12,155 +12,111 @@
</div>
<div class="act-status" v-else>未开始</div>
</div>
<swiper :options="swiperOption" ref="mySwiper">
<swiper-slide v-for="(item,index) in goodsList" :key="index">
<div class="content hover-pointer" @click.stop="goToSeckill">
<img :src="item.goodsImage" width="140" height="140" :alt="item.goodsName">
<div class="goods-scroll">
<div
v-for="(item, index) in goodsList"
:key="index"
class="content hover-pointer"
@click.stop="goToSeckill"
>
<img :src="item.goodsImage" width="140" height="140" :alt="item.goodsName" />
<div class="ellipsis">{{ item.goodsName }}</div>
<div>
<span>{{ item.price | unitPrice('¥') }}</span>
<span>{{ item.originalPrice | unitPrice('¥') }}</span>
<span>{{ $filters.unitPrice(item.price, "¥") }}</span>
<span>{{ $filters.unitPrice(item.originalPrice, "¥") }}</span>
</div>
</div>
</swiper-slide>
<div class="swiper-button-prev" slot="button-prev">
<Icon type="ios-arrow-back" />
</div>
<div class="swiper-button-next" slot="button-next">
<Icon type="ios-arrow-forward" />
</div>
</swiper>
</div>
</template>
<script>
// 引入插件
import { swiper, swiperSlide } from 'vue-awesome-swiper';
import 'swiper/dist/css/swiper.css';
export default {
components: {
swiper,
swiperSlide
},
props: {
data: Object
data: Object,
},
data() {
return {
list: [], // 秒杀时段列表
goodsList: [], // 商品列表
actStatus: 0, // 0 未开始 1 进行中
actName: '限时秒杀', // 活动名称
currIndex: 0, // 当前时间段的下标
currHour: '00', // 当前秒杀场
diffSeconds: 0, // 倒计时秒数
hours: 0, // 小时
minutes: 0, // 分钟
seconds: 0, // 秒
interval: null, // 定时器
swiperOption: { // 轮播图参数
slidesPerView: 5,
// 设置点击箭头
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
}
}
list: [],
goodsList: [],
actStatus: 0,
actName: "限时秒杀",
currIndex: 0,
currHour: "00",
diffSeconds: 0,
hours: 0,
minutes: 0,
seconds: 0,
interval: null,
};
},
watch: {
currIndex(val) {
clearInterval(this.interval)
this.interval = null
this.countDown(val)
this.goodsList = this.list[val].seckillGoodsList
clearInterval(this.interval);
this.interval = null;
this.countDown(val);
this.goodsList = this.list[val]?.seckillGoodsList || [];
},
diffSeconds(val) {
const hours = Math.floor(val / 3600);
// 当前秒数 / 60向下取整
// 获取到所有分钟数 3600 / 60 = 60分钟
// 对60取模超过小时数的分钟数
const minutes = Math.floor(val / 60) % 60;
// 当前的秒数 % 60获取到 超过小时数、分钟数的秒数(秒数)
const seconds = val % 60;
this.hours = filteTime(hours)
this.minutes = filteTime(minutes)
this.seconds = filteTime(seconds)
this.hours = filteTime(hours);
this.minutes = filteTime(minutes);
this.seconds = filteTime(seconds);
if (val <= 0) {
clearInterval(this.interval)
this.interval = null
clearInterval(this.interval);
this.interval = null;
}
function filteTime(time) {
if (time < 10) {
return '0' + time
} else {
return time
}
}
return time < 10 ? "0" + time : time;
}
},
computed: {
swiper () { // 轮播组件
return this.$refs.mySwiper.swiper;
}
},
beforeDestroy () {
// 销毁前清除定时器
beforeUnmount() {
clearInterval(this.interval);
},
mounted() {
this.getListByDay()
this.getListByDay();
},
methods: {
goToSeckill () { // 跳转秒杀页面
let routeUrl = this.$router.resolve({
path: '/seckill'
});
window.open(routeUrl.href, '_blank');
goToSeckill() {
const routeUrl = this.$router.resolve({ path: "/seckill" });
window.open(routeUrl.href, "_blank");
},
countDown (currIndex) { // 倒计时
// 0点时间戳
let zeroTime = new Date(new Date().toLocaleDateString()).getTime();
let currTime = new Date().getTime()
countDown(currIndex) {
const zeroTime = new Date(new Date().toLocaleDateString()).getTime();
const currTime = new Date().getTime();
let actTime = 0;
let nowHour = new Date().getHours(); // 当前小时数
if (this.list[currIndex].timeLine > nowHour) { // 活动未开始
const nowHour = new Date().getHours();
if (this.list[currIndex].timeLine > nowHour) {
this.actStatus = 0;
actTime = zeroTime + this.list[currIndex].timeLine * 3600 * 1000
} else if (this.list[currIndex].timeLine <= nowHour) { // 活动进行中
actTime = zeroTime + this.list[currIndex].timeLine * 3600 * 1000;
} else if (this.list[currIndex].timeLine <= nowHour) {
this.actStatus = 1;
if (currIndex === this.list.length - 1) { // 如果是最后一个活动直到24点结束
actTime = zeroTime + 24 * 3600 * 1000
if (currIndex === this.list.length - 1) {
actTime = zeroTime + 24 * 3600 * 1000;
} else {
actTime = zeroTime + this.list[currIndex + 1].timeLine * 3600 * 1000
actTime = zeroTime + this.list[currIndex + 1].timeLine * 3600 * 1000;
}
}
this.currHour = this.list[this.currIndex].timeLine
this.diffSeconds = Math.floor((actTime - currTime) / 1000)
this.currHour = this.list[this.currIndex].timeLine;
this.diffSeconds = Math.floor((actTime - currTime) / 1000);
this.interval = setInterval(() => {
this.diffSeconds--
}, 1000)
this.diffSeconds--;
}, 1000);
},
getListByDay () { // 当天秒杀活动
// const list = [
// {
// timeLine: 18,
// seckillGoodsList: [
// {goodsImage: 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/a9593607de404546953055f279fd5d54.png', goodsName: 'dfsdgsdf', originalPrice: 39, price: 12},
// {goodsImage: 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/a9593607de404546953055f279fd5d54.png', goodsName: 'dfsdgsdf', originalPrice: 39, price: 12},
// {goodsImage: 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/a9593607de404546953055f279fd5d54.png', goodsName: 'dfsdgsdf', originalPrice: 39, price: 12},
// {goodsImage: 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/a9593607de404546953055f279fd5d54.png', goodsName: 'dfsdgsdf', originalPrice: 39, price: 12},
// {goodsImage: 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/a9593607de404546953055f279fd5d54.png', goodsName: 'dfsdgsdf', originalPrice: 39, price: 12},
// {goodsImage: 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/a9593607de404546953055f279fd5d54.png', goodsName: 'dfsdgsdf', originalPrice: 39, price: 12}
// ]
// }
// ]
this.list = this.data.options.list
this.goodsList = this.list[0].seckillGoodsList
console.log( this.goodsList)
this.countDown(this.currIndex)
getListByDay() {
this.list = this.data?.options?.list || [];
if (!this.list.length) {
this.goodsList = [];
return;
}
this.goodsList = this.list[0]?.seckillGoodsList || [];
if (this.goodsList.length) {
this.countDown(this.currIndex);
}
},
},
};
</script>
<style lang="scss" scoped>
@@ -174,15 +130,13 @@ export default {
width: 190px;
height: 100%;
color: #fff;
background-image: url("../../../assets/images/seckillBg.png");
background: linear-gradient(180deg, var(--theme-color, #F31947) 0%, var(--theme-light, #ff6b35) 100%);
.title {
width: 100%;
text-align: center;
font-size: 28px;
margin-top: 31px;
}
.hour {
margin-top: 90px;
text-align: center;
@@ -190,7 +144,6 @@ export default {
font-size: 18px;
}
}
.count-down {
margin: 10px 0 0 30px;
> span {
@@ -219,15 +172,21 @@ export default {
content: "";
}
}
.act-status {
margin: 10px 0 0 65px;
font-size: 20px;
}
}
.goods-scroll {
height: 260px;
flex: 1;
margin-left: 10px;
background-color: #fff;
display: flex;
overflow-x: auto;
}
.content {
width: 200px;
min-width: 200px;
display: flex;
justify-content: center;
align-items: center;
@@ -262,26 +221,12 @@ export default {
line-height: 24px;
display: flex;
text-align: center;
span:nth-child(1) {
color: #fff;
font-size: 16px;
width: 92px;
background-color: $theme_color;
position: relative;
&::before {
content: " ";
width: 0;
height: 0;
border-color: transparent white transparent transparent;
border-style: solid;
border-width: 24px 8px 0 0;
position: absolute;
top: 0;
left: 84px;
}
}
span:nth-child(2) {
color: #999;
width: 66px;
@@ -290,35 +235,4 @@ export default {
}
}
}
.swiper-container {
height: 260px;
width: 1000px;
margin-left: 10px;
background-color: #fff;
}
.swiper-button-prev, .swiper-button-next {
background: #ccc;
width: 25px;
height: 35px;
font-size: 16px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.swiper-button-prev:hover, .swiper-button-next:hover {
background: #aaa;
}
.swiper-button-prev {
left: 0;
border-bottom-right-radius: 18px;
border-top-right-radius: 18px;
padding-right: 5px;
}
.swiper-button-next {
right: 0;
border-top-left-radius: 18px;
border-bottom-left-radius: 18px;
padding-left: 5px;
}
</style>

View File

@@ -2,14 +2,14 @@
<div class="model" v-if="data">
<div class="for-your">{{ data.options.title }}</div>
<div class="flex card">
<div class="left">
<div class="left hover-pointer" @click="linkTo(data.options.data.image.url)">
<img :src="data.options.data.image.src" alt="" />
</div>
<div class="right flex">
<!-- 商品区 -->
<div class="goods-list">
<div class="for-your-goods-list">
<div
class="goods-item"
class="for-your-goods-item"
:key="index"
v-for="(item, index) in data.options.data.list"
@click="linkTo(item.url)"
@@ -34,7 +34,7 @@
<div class="hot-goods">
<div class="hot-goods-title">{{ item.title }}</div>
<div>
<div class="hot-price">{{ item.price | unitPrice }}</div>
<div class="hot-price">{{ $filters.unitPrice(item.price) }}</div>
</div>
</div>
</div>
@@ -65,14 +65,18 @@ export default {
</script>
<style scoped lang="scss">
.goods-list {
.for-your-goods-list {
display: flex;
flex: 0 0 558px;
width: 558px;
flex-wrap: wrap;
column-gap: 0;
}
.goods-item {
.for-your-goods-item {
flex: 0 0 278px;
width: 278px;
height: 277px;
min-width: 0;
text-align: center;
box-sizing: border-box;
cursor: pointer;
@@ -84,10 +88,8 @@ export default {
border-top: 1.4px solid #e2e2e2;
}
&:hover {
::v-deep .goods-name {
color: $theme_color;
}
::v-deep .goods-desc {
.goods-name,
.goods-desc {
color: $theme_color;
}
}
@@ -105,10 +107,12 @@ export default {
color: #333333;
}
.goods-img {
display: block;
width: 190px;
height: 156px;
margin-top: 19px;
margin: 19px auto 0;
border-radius: 10px;
object-fit: contain;
}
.goods-desc {
font-size: 14px;
@@ -137,6 +141,8 @@ export default {
background: #ffffff;
border-radius: 10px;
position: relative;
overflow: hidden;
flex-shrink: 0;
box-shadow: 0px 1px 13px 0px #E5E5E5;
&:hover {
@@ -159,20 +165,27 @@ export default {
}
}
.left {
flex: 0 0 346px;
width: 346px;
height: 554px;
border-radius: 9.8px 0px 0px 9.8px;
overflow: hidden;
> img {
max-width: 100%;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
}
.right {
flex: 0 0 839px;
width: 839px;
height: 554px;
border-radius: 0px 9.8px 9.8px 0px;
overflow: hidden;
}
.hot-list {
flex: 0 0 279px;
width: 279px;
}
.hot-title {
@@ -201,8 +214,11 @@ export default {
}
> img {
flex-shrink: 0;
display: block;
width: 76.3px;
height: 77.7px;
object-fit: contain;
}
}
.hot-goods {

View File

@@ -33,7 +33,7 @@
<div class="goods-desc">{{ item.desc }}</div>
</div>
<div class="goods-price">
{{ item.price | unitPrice("¥") }}
{{ $filters.unitPrice(item.price, "¥") }}
</div>
<!-- 查看 -->
<div class="white-shadow">
@@ -173,7 +173,7 @@ export default {
text-align: center;
letter-spacing: 0px;
color: #f31947;
color: $theme_color;
-webkit-text-stroke: #979797 0.7px; /* 浏览器可能不支持 */
}
.goods-type-line {
@@ -190,7 +190,7 @@ export default {
color: #333333;
}
.active {
color: #f31947;
color: $theme_color;
}
.goods-type-labels {
cursor: pointer;

View File

@@ -49,5 +49,6 @@ export default {
background: #FFFFFF;
box-shadow: 0px 1px 13px 0px #E5E5E5;
position: relative;
overflow: hidden;
}
</style>

View File

@@ -1,20 +1,23 @@
<template>
<div class="flex" v-if="data.options.right.model == 'brand'">
<div class="left-side" @click="linkTo(data.options.right.data.image.url)">
<img :src="data.options.right.data.image.src" alt="">
<div class="mix-panel flex" v-if="data.options.right.model == 'brand'">
<div class="left-side hover-pointer" @click="linkTo(data.options.right.data.image.url)">
<img :src="data.options.right.data.image.src" alt="" />
</div>
<div class="right-side">
<div class="badge-box flex">
<div class="round">
<Icon type="ios-arrow-forward" />
<el-icon><ArrowRight /></el-icon>
</div>
</div>
<div class="flex goods-list">
<div class="goods-item flex" @click="linkTo(item.url)" :key="index" v-for="(item,index) in data.options.right.data.list">
<div
class="goods-item flex hover-pointer"
@click="linkTo(item.url)"
:key="index"
v-for="(item, index) in data.options.right.data.list"
>
<div class="goods-thumbnail">
<img :src="item.img" alt="">
<img :src="item.img" alt="" />
</div>
</div>
</div>
@@ -23,62 +26,60 @@
</template>
<script>
import { ArrowRight } from "@element-plus/icons-vue";
export default {
name: "mix-goods",
data() {
return {}
},
name: "mix-brand",
components: { ArrowRight },
props: {
data: {
type: Object,
default: {}
}
default: () => ({}),
},
components: {},
mounted() {
},
methods: {}
}
};
</script>
<style scoped lang="scss">
.right-side{
width: 330px;
.mix-panel {
height: 100%;
align-items: stretch;
overflow: hidden;
}
.goods-detail-title{
font-size: 13px;
font-weight: normal;
line-height: 16px;
text-align: center;
letter-spacing: 0px;
color: #333333;
}
.goods-detail-price{
font-size: 14px;
font-weight: normal;
line-height: 17px;
letter-spacing: 0px;
color: $theme_color;
margin-top: 8px;
margin-bottom:10px;
}
.goods-thumbnail{
margin-left: 7px;
display: flex;
align-items: center;
.left-side {
flex-shrink: 0;
width: 254px;
height: 344px;
> img {
width:90px;
height: 90px;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 9.8px 0 0 9.8px;
}
}
.right-side {
flex: 1;
min-width: 0;
height: 344px;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
}
.badge-box {
margin-top: 33px;
justify-content:right;
margin-top: 24px;
margin-bottom: 8px;
justify-content: flex-end;
flex-shrink: 0;
> .round {
width: 17.5px;
height: 17.5px;
opacity: 1;
border-radius: 50%;
text-align: center;
line-height: 17.5px;
@@ -86,47 +87,48 @@ export default {
margin-right: 17.5px;
}
}
.goods-list {
width: 330px;
width: 100%;
box-sizing: border-box;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 16px;
align-content: flex-start;
padding: 0 16px 12px;
gap: 8px 0;
flex: 1;
min-height: 0;
overflow: hidden;
}
.goods-item {
display: flex;
align-items: center;
justify-content: center;
width:50%;
height: 85px;
flex: 0 0 calc(50% - 4px);
width: calc(50% - 4px);
height: 82px;
border-radius: 9.8px;
opacity: 1;
transition: .35s;
background: #FFFFFF;
margin-bottom:9px;
box-sizing: border-box;
overflow: hidden;
background: #ffffff;
transition: 0.35s;
cursor: pointer;
}
.left-side {
.goods-thumbnail {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
> img {
width: 254px;
height: 344px;
border-radius: 9.8px 0px 0px 9.8px;
opacity: 1;
max-width: 80px;
max-height: 76px;
width: auto;
height: auto;
object-fit: contain;
}
}
.badge {
width: 50px;
height: 27px;
line-height:27px;
border-radius: 13.3px 0px 0px 13.3px;
opacity: 1;
background: #F31947;
font-size: 12.6px;
font-weight: normal;
text-align: center;
letter-spacing: 0px;
color: #FFFFFF;
margin-top: 26px;
margin-bottom:17px;
}
</style>

View File

@@ -1,23 +1,27 @@
<template>
<div class="flex" v-if="data.options.left.model == 'goods'">
<div class="left-side" @click="linkTo(data.options.right.data.image.url)">
<img :src="data.options.left.data.image.src" alt="">
<div class="mix-panel flex" v-if="data.options.left.model == 'goods'">
<div class="left-side hover-pointer" @click="linkTo(data.options.left.data.image.url)">
<img :src="data.options.left.data.image.src" alt="" />
</div>
<div class="right-side">
<div class="badge-box flex">
<div class="badge" @click="linkTo(data.options.right.data.image.url)">
<div class="badge hover-pointer" @click="linkTo(data.options.left.data.badge.url)">
{{ data.options.left.data.badge.label }}
</div>
</div>
<div class="flex goods-list">
<div class="goods-item flex" @click="linkTo(item.url)" :key="index" v-for="(item,index) in data.options.left.data.list">
<div
class="goods-item flex hover-pointer"
@click="linkTo(item.url)"
:key="index"
v-for="(item, index) in data.options.left.data.list"
>
<div class="goods-thumbnail">
<img :src="item.img" alt="">
<img :src="item.img" alt="" />
</div>
<div class="goods-detail">
<div class="goods-detail-title">{{ item.title }}</div>
<div class="goods-detail-price">{{item.price | unitPrice('¥')}}</div>
<div class="goods-detail-price">{{ $filters.unitPrice(item.price, '¥') }}</div>
</div>
</div>
</div>
@@ -28,99 +32,117 @@
<script>
export default {
name: "mix-goods",
data() {
return {}
},
props: {
data: {
type: Object,
default: {}
}
default: () => ({}),
},
components: {},
mounted() {
},
methods: {}
}
};
</script>
<style scoped lang="scss">
.right-side{
width: 387px;
.mix-panel {
height: 100%;
align-items: stretch;
overflow: hidden;
}
.left-side {
flex-shrink: 0;
width: 196.7px;
height: 344px;
> img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px 0 0 10px;
}
}
.right-side {
flex: 1;
min-width: 0;
height: 344px;
display: flex;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
overflow: hidden;
}
.goods-detail-title {
font-size: 13px;
font-weight: normal;
line-height: 16px;
text-align: center;
letter-spacing: 0px;
color: #333333;
}
.goods-detail-price {
font-size: 14px;
font-weight: normal;
line-height: 17px;
letter-spacing: 0px;
color: #F31947;
color: $theme_color;
margin-top: 8px;
margin-bottom:10px;
}
.goods-thumbnail {
margin-left: 7px;
display: flex;
align-items: center;
flex-shrink: 0;
> img {
width: 90px;
height: 90px;
object-fit: contain;
}
}
.badge-box{
justify-content:right;
.badge-box {
justify-content: flex-end;
flex-shrink: 0;
}
.goods-list {
width: 387px;
width: 100%;
box-sizing: border-box;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 16px;
gap: 9px 0;
}
.goods-item {
cursor: pointer;
display: flex;
align-items: center;
width: 173.6px;
flex: 0 0 calc(50% - 4px);
width: calc(50% - 4px);
height: 119px;
border-radius: 9.8px;
opacity: 1;
transition: .35s;
background: #FFFFFF;
margin-bottom:9px;
box-sizing: border-box;
overflow: hidden;
background: #ffffff;
transition: 0.35s;
&:hover {
box-shadow: 0px 1px 6px 0px #E5E5E5;
box-shadow: 0 1px 6px 0 #e5e5e5;
}
}
}
.left-side {
>img{
border-radius: 10px 0px 0px 10px;
display: block;
width: 196.7px;
height: 343.7px;
}
}
.badge {
width: 50px;
height: 27px;
line-height: 27px;
border-radius: 13.3px 0px 0px 13.3px;
opacity: 1;
background: #F31947;
border-radius: 13.3px 0 0 13.3px;
background: $theme_color;
font-size: 12.6px;
font-weight: normal;
text-align: center;
letter-spacing: 0px;
color: #FFFFFF;
color: #ffffff;
margin-top: 26px;
margin-bottom: 17px;
}

View File

@@ -1,7 +1,12 @@
<template>
<div class="line flex flex-a-c flex-j-sb">
<div class="column" v-for="(item,index) in data.options.list" :key="index">
<img :src="item.img" class="three-column-img">
<div
class="column hover-pointer"
v-for="(item, index) in data.options.list"
:key="index"
@click="linkTo(item.url)"
>
<img :src="item.img" class="three-column-img" alt="" />
</div>
</div>
</template>
@@ -25,12 +30,18 @@ export default {
</script>
<style scoped lang="scss">
.line {
width: 100%;
}
.column {
width: 385px;
height: 165px;
cursor: pointer;
> img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>

View File

@@ -0,0 +1,62 @@
.setup-content{
position: relative;
&:hover{
.setup-box{
display: block;
}
}
}
.setup-box{
display: none;
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, .2);
z-index: 99;
width: 100%;
height: 100%;
.el-button{
float: right;
margin-right: 5px;
margin-top: 5px;
}
}
.modal-tab-bar{
margin-left: 20px;
table{
margin-top: 10px;
display:inline-block;
border: 1px solid #ddd;
border-radius: 5px;
max-height: 400px !important;
overflow: hidden auto;
}
thead{
background-color: #eee;
th{
padding: 5px 0;
}
}
td{
padding: 10px;
text-align: center;
}
tbody>tr:hover{
background-color: aliceblue;
}
}
.modal-top-advert{
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
>*{
margin-bottom: 10px;
}
.el-input{
width: 200px;
}
}

View File

@@ -1,90 +1,89 @@
<template>
<div class="invoice-modal">
<Modal v-model="invoiceAvailable" width="640" footer-hide>
<p slot="header">
<el-dialog v-model="invoiceAvailable" width="640">
<template #header><p>
<span>发票信息</span>
</p>
</p></template>
<div class="nav-content">
<Form :model="invoiceForm" ref="form" label-position="left" :rules="formRules" :label-width="118">
<FormItem label="发票类型">
<RadioGroup v-model="invoice" type="button" button-style="solid" @on-change="onInvoiceKindChange">
<Radio :label="1">电子普通发票</Radio>
<Radio :label="2">增值税专用发票</Radio>
</RadioGroup>
</FormItem>
<el-form :model="invoiceForm" ref="form" label-position="left" :rules="formRules" label-width="118px">
<el-form-item label="发票类型">
<el-radio-group v-model="invoice" type="button" button-style="solid" @change="onInvoiceKindChange">
<el-radio :label="1">电子普通发票</el-radio>
<el-radio :label="2">增值税专用发票</el-radio>
</el-radio-group>
</el-form-item>
<!-- 电子普通发票 -->
<div v-if="invoice === 1" key="invoice-normal">
<FormItem label="发票抬头">
<RadioGroup v-model="type" type="button" button-style="solid" @on-change="onHeaderTypeChange">
<Radio :label="1">个人</Radio>
<Radio :label="2">单位</Radio>
</RadioGroup>
</FormItem>
<FormItem :label="type === 1 ? '个人名称' : '单位名称'" :prop="type === 1 ? 'personalName' : 'companyName'">
<i-input v-if="type === 1" v-model="invoiceForm.personalName"></i-input>
<i-input v-else v-model="invoiceForm.companyName"></i-input>
</FormItem>
<el-form-item label="发票抬头">
<el-radio-group v-model="type" type="button" button-style="solid" @change="onHeaderTypeChange">
<el-radio :label="1">个人</el-radio>
<el-radio :label="2">单位</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="type === 1 ? '个人名称' : '单位名称'" :prop="type === 1 ? 'personalName' : 'companyName'">
<el-input v-if="type === 1" v-model="invoiceForm.personalName"></el-input>
<el-input v-else v-model="invoiceForm.companyName"></el-input>
</el-form-item>
<FormItem label="纳税人识别号" v-if="type === 2" prop="taxpayerId">
<i-input v-model="invoiceForm.taxpayerId"></i-input>
</FormItem>
<FormItem label="发票内容">
<RadioGroup v-model="invoiceForm.receiptContent" type="button" button-style="solid">
<Radio label="商品明细">商品明细</Radio>
<Radio label="商品类别">商品类别</Radio>
</RadioGroup>
</FormItem>
<FormItem label="收票人手机" prop="receiptPhone">
<i-input v-model="invoiceForm.receiptPhone"></i-input>
</FormItem>
<FormItem label="收票人邮箱" prop="receiptEmail">
<i-input v-model="invoiceForm.receiptEmail" placeholder="可选"></i-input>
</FormItem>
<el-form-item label="纳税人识别号" v-if="type === 2" prop="taxpayerId">
<el-input v-model="invoiceForm.taxpayerId"></el-input>
</el-form-item>
<el-form-item label="发票内容">
<el-radio-group v-model="invoiceForm.receiptContent" type="button" button-style="solid">
<el-radio label="商品明细">商品明细</el-radio>
<el-radio label="商品类别">商品类别</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="收票人手机" prop="receiptPhone">
<el-input v-model="invoiceForm.receiptPhone"></el-input>
</el-form-item>
<el-form-item label="收票人邮箱" prop="receiptEmail">
<el-input v-model="invoiceForm.receiptEmail" placeholder="可选"></el-input>
</el-form-item>
</div>
<!-- 增值税专用发票固定为单位 -->
<div v-else key="invoice-vat">
<FormItem label="发票抬头">
<el-form-item label="发票抬头">
<span class="inv-title-fixed">单位</span>
</FormItem>
<FormItem label="单位名称" prop="companyName">
<i-input v-model="invoiceForm.companyName" placeholder="与营业执照一致"></i-input>
</FormItem>
<FormItem label="纳税人识别号" prop="taxpayerId">
<i-input v-model="invoiceForm.taxpayerId"></i-input>
</FormItem>
<FormItem label="单位地址" prop="companyAddress">
<i-input v-model="invoiceForm.companyAddress" placeholder="注册地址"></i-input>
</FormItem>
<FormItem label="单位电话" prop="companyPhone">
<i-input v-model="invoiceForm.companyPhone" placeholder="固话或手机"></i-input>
</FormItem>
<FormItem label="开户银行" prop="bankName">
<i-input v-model="invoiceForm.bankName"></i-input>
</FormItem>
<FormItem label="银行账号" prop="bankAccount">
<i-input v-model="invoiceForm.bankAccount"></i-input>
</FormItem>
<FormItem label="发票内容">
<RadioGroup v-model="invoiceForm.receiptContent" type="button" button-style="solid">
<Radio label="商品明细">商品明细</Radio>
</RadioGroup>
</FormItem>
<FormItem label="收票人手机" prop="receiptPhone">
<i-input v-model="invoiceForm.receiptPhone"></i-input>
</FormItem>
<FormItem label="收票人邮箱" prop="receiptEmail">
<i-input v-model="invoiceForm.receiptEmail" placeholder="可选"></i-input>
</FormItem>
</el-form-item>
<el-form-item label="单位名称" prop="companyName">
<el-input v-model="invoiceForm.companyName" placeholder="与营业执照一致"></el-input>
</el-form-item>
<el-form-item label="纳税人识别号" prop="taxpayerId">
<el-input v-model="invoiceForm.taxpayerId"></el-input>
</el-form-item>
<el-form-item label="单位地址" prop="companyAddress">
<el-input v-model="invoiceForm.companyAddress" placeholder="注册地址"></el-input>
</el-form-item>
<el-form-item label="单位电话" prop="companyPhone">
<el-input v-model="invoiceForm.companyPhone" placeholder="固话或手机"></el-input>
</el-form-item>
<el-form-item label="开户银行" prop="bankName">
<el-input v-model="invoiceForm.bankName"></el-input>
</el-form-item>
<el-form-item label="银行账号" prop="bankAccount">
<el-input v-model="invoiceForm.bankAccount"></el-input>
</el-form-item>
<el-form-item label="发票内容">
<el-radio-group v-model="invoiceForm.receiptContent" type="button" button-style="solid">
<el-radio label="商品明细">商品明细</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="收票人手机" prop="receiptPhone">
<el-input v-model="invoiceForm.receiptPhone"></el-input>
</el-form-item>
<el-form-item label="收票人邮箱" prop="receiptEmail">
<el-input v-model="invoiceForm.receiptEmail" placeholder="可选"></el-input>
</el-form-item>
</div>
</Form>
</el-form>
<div style="text-align: center">
<Button type="primary" :loading="loading" @click="submit">保存发票信息</Button>
<Button type="default" @click="invoiceAvailable = false">取消</Button>
<el-button type="primary" :loading="loading" @click="submit">保存发票信息</el-button>
<el-button type="default" @click="invoiceAvailable = false">取消</el-button>
</div>
</div>
</Modal>
</div></el-dialog>
</div>
</template>
<script>

View File

@@ -5,7 +5,7 @@
<div id="map-container"></div>
<div class="search-con">
<Input placeholder="输入关键字搜索" id="input-map" v-model="mapSearch" />
<el-input placeholder="输入关键字搜索" id="input-map" v-model="mapSearch" />
<ul>
<li v-for="(tip, index) in tips" :key="index" @click="selectAddr(tip.location)">
<p>{{ tip.name }}</p>
@@ -13,18 +13,17 @@
</li>
</ul>
</div>
<div slot="footer" class="footer">
<Button type="primary" :loading="loading" @click="ok">确定</Button>
<div class="footer">
<el-button type="primary" :loading="loading" @click="ok">确定</el-button>
</div>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import AMapLoader from "@amap/amap-jsapi-loader";
import { handleRegion } from "@/api/address.js";
import config from "@/config/index";
const config = require('@/config/index')
export default {
name: "map",
data() {
@@ -64,7 +63,7 @@ export default {
}
});
} else {
this.$Message.error('未获取到坐标信息请查看高德API配置是否正确')
Message.error('未获取到坐标信息请查看高德API配置是否正确')
}
},
@@ -135,7 +134,7 @@ export default {
selectAddr(location) {
// 选择坐标
if (!location) {
this.$Message.warning("请选择正确点位");
Message.warning("请选择正确点位");
return false;
}
const lnglat = [location.lng, location.lat];

View File

@@ -1,9 +1,9 @@
<template>
<Modal width="800" footer-hide v-model="enableMap">
<RadioGroup @on-change="changeMap" v-model="mapDefault" type="button">
<Radio label="select">级联选择</Radio>
<Radio label="map" v-if="aMapSwitch">高德地图</Radio>
</RadioGroup>
<el-dialog width="800" v-model="enableMap">
<el-radio-group @change="changeMap" v-model="mapDefault" type="button">
<el-radio label="select">级联选择</el-radio>
<el-radio label="map" v-if="aMapSwitch">高德地图</el-radio>
</el-radio-group>
<div>
<div v-if="mapDefault === 'select'">
<div class="selector">
@@ -18,18 +18,16 @@
</div>
<div class="footer">
<Button type="primary" @click="finished">确定</Button>
<el-button type="primary" @click="finished">确定</el-button>
</div>
</div>
<mapping v-if="mapDefault === 'map'" ref="map" @getAddress="getAddress" />
</div>
</Modal>
</div></el-dialog>
</template>
<script>
import { Message } from "@/utils/message";
import { aMapSwitch } from '@/config/index'
import mapping from "@/components/map/index.vue";
import * as API_Setup from "@/api/common.js";
@@ -104,7 +102,7 @@ export default {
// 选择完成
finished() {
if(!this.chiosend[0]){
this.$Message.error("请选择地址")
Message.error("请选择地址")
return
}
const params = this.chiosend.filter((item) => item!=="" && item.value !== "");

View File

@@ -1,12 +1,12 @@
<template>
<div>
<Cascader
<el-cascader
:data="data"
:load-data="loadData"
v-model="addr"
@on-change="change"
@change="change"
style="width: 300px"
></Cascader>
></el-cascader>
</div>
</template>
<script>

View File

@@ -3,6 +3,7 @@
</template>
<script>
import { Message } from "@/utils/message";
import { getIMDetail } from "@/api/common";
import Storage from "@/plugins/storage";
import { getMemberMsg } from "@/api/login";
@@ -34,7 +35,7 @@ export default {
}
} else {
this.$Message.error("请登录后再联系客服");
Message.error("请登录后再联系客服");
return;
}
},

View File

@@ -1,5 +1,5 @@
<template>
<div class="cate-nav width_1200_auto" :class="{'fixed-show':useClass == 'fixed-show'}">
<div class="cate-nav" :class="{'fixed-show':useClass == 'fixed-show'}">
<div class="nav-con" :class="{'background-white':useClass == 'background-white'}">
<div
class="all-categories hover-pointer"
@@ -49,8 +49,7 @@
@click="goGoodsList(second.id, second.parentId)"
class="nav-side-item"
v-if="secIndex < 2"
>{{ second.name }}</span
>
>{{ second.name }}</span>
</span>
</li>
</ul>
@@ -69,8 +68,7 @@
@click="goGoodsList(item.id,item.parentId)"
v-if="index < 8"
:key="index"
>{{ item.name }}<Icon type="ios-arrow-forward"
/></span>
>{{ item.name }}<el-icon><ArrowRight /></el-icon></span>
</template>
</div>
<ul>
@@ -83,7 +81,7 @@
class="detail-item-title"
@click="goGoodsList(items.id, items.parentId)"
>
{{ items.name }} <Icon type="ios-arrow-forward" />
{{ items.name }} <el-icon><ArrowRight /></el-icon>
<span class="glyphicon glyphicon-menu-right"></span>
</span>
<div>
@@ -92,8 +90,7 @@
@click="goGoodsList(item.id, items.id, items.parentId)"
:key="subIndex"
class="detail-item"
>{{ item.name }}</span
>
>{{ item.name }}</span>
</div>
</li>
</ul>
@@ -105,7 +102,9 @@
<script>
import { getCategory } from "@/api/goods";
import storage from "@/plugins/storage.js";
import { ArrowRight } from "@element-plus/icons-vue";
export default {
components: { ArrowRight },
name: "GoodsListNav",
props: {
showAlways: {
@@ -150,9 +149,8 @@ export default {
// 导航列表
if (storage.getItem("navList")) {
return JSON.parse(storage.getItem("navList"));
} else {
return [];
}
return { list: [] };
},
},
methods: {
@@ -169,7 +167,7 @@ export default {
return;
}
this.cateList = JSON.parse(localStorage.getItem("category"));
// this.$Message.info(cateList)
// Message.info(cateList)
}
},
getCate() {
@@ -186,7 +184,7 @@ export default {
// 存放分类信息
localStorage.setItem("category", JSON.stringify(res.result));
}
});
}).catch(() => {});
},
showDetail(index) {
// 展示全部分类
@@ -243,8 +241,10 @@ export default {
}
.fixed-show{
margin-top: 0 !important;
width: 100%;
>.nav-con{
>.all-categories{
width: 100%;
align-items: center !important;
height: 60px;
display: inherit;
@@ -262,37 +262,45 @@ export default {
}
.cate-nav {
position: relative;
margin: 14px auto 0 auto;
z-index: 5;
width: 100%;
margin: 14px 0 0;
box-sizing: border-box;
}
/** 商品分类 */
.nav-con {
height: 46px;
margin: 0 auto;
display: flex;
align-items: stretch;
.all-categories {
flex-shrink: 0;
display: flex;
align-items: center;
height: 46px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding-left: 37.4px;
background: #FFFFFF;
width: 263.2px;
padding-top: 15.4px;
line-height: 20px;
border-bottom: none;
font-size: 16.8px;
font-weight: normal;
color: #333333;
letter-spacing: 0px;
box-sizing: border-box;
}
.nav-item {
width: 914px;
flex: 1;
min-width: 0;
height: 46px;
border-radius: 10px;
background: #FFFFFF;
margin-left: 10px;
margin: 0 0 0 10px;
padding: 0;
line-height: 46px;
overflow: hidden;
list-style: none;
// background-color: #eee;
display: flex;
li {
float: left;
@@ -307,11 +315,11 @@ export default {
}
}
}
// 分类列表
// 分类列表(浮层叠在轮播左列之上)
.cate-list {
margin: 0 auto;
position: absolute;
left: 0;
top: 46px;
z-index: 1000;
}
@@ -351,11 +359,14 @@ export default {
.nav-side ul {
width: 100%;
list-style: none;
margin: 0;
padding: 0;
}
.nav-side li {
padding: 0 0 16.2px 37.4px;
font-size: 13px;
line-height: 18px;
box-sizing: border-box;
}
.nav-side-item:hover {
cursor: pointer;

View File

@@ -5,32 +5,32 @@
<template v-if="$route.query.categoryId">
<!-- 头部展示筛选信息 -->
<div @click="cateClick(tabBar,1)">{{ tabBar.name }}</div>
<Icon type="ios-arrow-forward" />
<el-icon><ArrowRight /></el-icon>
<div class="bar" v-if="tabBar.first">
{{ tabBar.first.name }} <Icon type="ios-arrow-down" />
{{ tabBar.first.name }} <el-icon><ArrowDown /></el-icon>
<ul>
<li v-for="item in tabBar.children" :key="item.id" @click="cateClick(item,2)">
{{ item.name }}
</li>
</ul>
</div>
<Icon type="ios-arrow-forward" v-if="tabBar.first" />
<el-icon :v-if="tabBar.first"><ArrowRight /></el-icon>
<div class="bar" v-if="tabBar.second">
{{ tabBar.second.name }} <Icon type="ios-arrow-down" />
{{ tabBar.second.name }} <el-icon><ArrowDown /></el-icon>
<ul>
<li v-for="item in tabBar.first.children" :key="item.id" @click="cateClick(item,3)">
{{ item.name }}
</li>
</ul>
</div>
<Icon type="ios-arrow-forward" v-if="tabBar.second" />
<el-icon :v-if="tabBar.second"><ArrowRight /></el-icon>
</template>
<!-- 无商品分类展示搜索结果 -->
<template v-else>
<div style="font-size:14px">全部结果</div>
<Icon type="ios-arrow-forward" />
<el-icon><ArrowRight /></el-icon>
<div style="font-weight:bold;" class="mr_10">{{params.keyword}}</div>
</template>
<!-- 所选分类 -->
@@ -41,7 +41,7 @@
:key="index"
:title="item.name"
>
<span>{{ item.type }}</span><span>{{ item.name }}</span><Icon type="md-close" />
<span>{{ item.type }}</span><span>{{ item.name }}</span><el-icon><Close /></el-icon>
</a>
</div>
@@ -66,7 +66,7 @@
v-show="multSelected.includes(item.name)"
>
<div></div>
<Icon type="md-checkmark" />
<el-icon><Check /></el-icon>
</div>
</li>
</ul>
@@ -74,21 +74,19 @@
<div class="btn" v-show="multiple !== 0">
<span @click="moreBrand(0)"
>{{ tagsContent[0].more ? "收起" : "更多"
}}<Icon
:type="tagsContent[0].more ? 'ios-arrow-up' : 'ios-arrow-down'"
/></span>
<span @click="multSelectBrand(0)"><Icon type="md-add" />多选</span>
}}<el-icon><ArrowUp v-if="tagsContent[0].more" /><ArrowDown v-else /></el-icon></span>
<span @click="multSelectBrand(0)"><el-icon><Plus /></el-icon>多选</span>
</div>
<div class="multBtn" v-show="multiple === 0">
<Button
<el-button
type="primary"
size="small"
:disabled="!multSelected.length"
@click="sure(0)"
>确定</Button
>确定</el-button
>
<Button size="small" @click="cancel">取消</Button>
<el-button size="small" @click="cancel">取消</el-button>
</div>
</div>
</div>
@@ -115,40 +113,38 @@
</li>
</ul>
<CheckboxGroup
<el-checkbox-group
:class="{ 'show-more': tag.more }"
class="list"
v-model="multSelected"
v-show="multiple === tagIndex"
>
<Checkbox
<el-checkbox
class="item"
:label="item"
v-for="(item, index) in tag.values"
:key="index"
>{{ item }}</Checkbox
>{{ item }}</el-checkbox
>
</CheckboxGroup>
</el-checkbox-group>
<div class="btn" v-show="multiple !== tagIndex">
<span @click="moreBrand(tagIndex)" v-show="tag.values.length > 9"
>{{ tag.more ? "收起" : "更多"
}}<Icon :type="tag.more ? 'ios-arrow-up' : 'ios-arrow-down'"
/></span>
}}<el-icon><ArrowUp v-if="tag.more" /><ArrowDown v-else /></el-icon></span>
<span @click="multSelectBrand(tagIndex)"
><Icon type="md-add" />多选</span
>
><el-icon><Plus /></el-icon>多选</span>
</div>
<div class="multBtn" v-show="multiple === tagIndex">
<Button
<el-button
type="primary"
size="small"
:disabled="!multSelected.length"
@click="sure(tagIndex)"
>确定</Button
>确定</el-button
>
<Button size="small" @click="cancel">取消</Button>
<el-button size="small" @click="cancel">取消</el-button>
</div>
</div>
</div>
@@ -161,8 +157,10 @@
</template>
<script>
import { ArrowDown, ArrowRight, ArrowUp, Check, Close, Plus } from '@element-plus/icons-vue';
import * as APIGoods from '@/api/goods';
export default {
components: { ArrowDown, ArrowRight, ArrowUp, Check, Close, Plus },
name: 'GoodsClassNav',
data () {
return {
@@ -250,13 +248,13 @@ export default {
const first = this.tabBar.children.filter(e => {
return e.id === arr[1]
})[0]
this.$set(this.tabBar, 'first', first)
this.tabBar['first'] = first
}
if (arr.length > 2) {
const second = this.tabBar.first.children.filter(e => {
return e.id === arr[2]
})[0]
this.$set(this.tabBar, 'second', second)
this.tabBar['second'] = second
}
},
cateClick (item, index) { // 点选分类
@@ -329,11 +327,11 @@ export default {
moreBrand (index) {
// 更多按钮
const flag = !this.tagsContent[index].more
this.$set(this.tagsContent[index], 'more', flag)
this.tagsContent[index]['more'] = flag
},
multSelectBrand (index) {
// 多选按钮
this.$set(this.tagsContent[index], 'more', true)
this.tagsContent[index]['more'] = true
this.multiple = index;
},
sure (index) {
@@ -376,8 +374,8 @@ export default {
this.tagsContent[0].values = data.brands;
this.tagsContent = this.tagsContent.concat(data.paramOptions);
this.tagsContent.forEach((item) => {
this.$set(item, 'show', true)
this.$set(item, 'more', false)
item['show'] = true
item['more'] = false
});
}
});
@@ -437,7 +435,7 @@ export default {
ul {
display: block;
}
.ivu-icon {
.el-icon {
transform: rotate(180deg);
}
}
@@ -493,7 +491,7 @@ export default {
&:hover {
border-color: $theme_color;
background-color: #fff;
.ivu-icon {
.el-icon {
color: #fff;
background-color: $theme_color;
}
@@ -503,7 +501,7 @@ export default {
color: $theme_color;
}
.ivu-icon {
.el-icon {
position: absolute;
right: 0;
top: 0;
@@ -589,7 +587,7 @@ export default {
border-top: 20px solid transparent;
border-right: 20px solid $theme_color;
}
.ivu-icon {
.el-icon {
font-size: 12px;
position: absolute;
bottom: 0;
@@ -630,10 +628,10 @@ export default {
.multBtn {
text-align: center;
margin-top: 10px;
.ivu-btn {
.el-button {
font-size: 12px !important;
}
.ivu-btn:last-child {
.el-button:last-child {
margin-left: 10px;
}
}
@@ -708,10 +706,10 @@ export default {
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
.ivu-btn {
.el-button {
font-size: 12px !important;
}
.ivu-btn:last-child {
.el-button:last-child {
margin-left: 10px;
}
}

View File

@@ -0,0 +1,144 @@
<template>
<div class="user-center-page">
<div class="user-center-title">{{ title }}</div>
<div class="user-center-panel">
<div v-if="showToolbar" class="user-center-toolbar">
<div class="user-center-tabs">
<div
v-for="(item, index) in tabs"
:key="index"
class="user-center-tab"
:class="{ active: activeTab === index }"
@click="$emit('tab-change', index)"
>
{{ item }}
</div>
</div>
<div class="user-center-extra">
<slot name="extra">
<el-button v-if="moreText" type="primary" @click="handleMore">{{ moreText }}</el-button>
</slot>
</div>
</div>
<div class="user-center-content">
<slot />
</div>
</div>
</div>
</template>
<script>
export default {
name: "UserCenterLayout",
props: {
title: {
type: String,
required: true,
},
tabs: {
type: Array,
default: () => [],
},
activeTab: {
type: Number,
default: 0,
},
moreText: {
type: String,
default: "",
},
moreTo: {
type: [String, Object],
default: "",
},
},
emits: ["tab-change", "more"],
computed: {
showToolbar() {
return this.tabs.length || this.moreText || this.$slots.extra;
},
},
methods: {
handleMore() {
this.$emit("more");
if (!this.moreTo) return;
if (typeof this.moreTo === "string") {
this.$router.push(this.moreTo);
} else {
this.$router.push(this.moreTo);
}
},
},
};
</script>
<style scoped lang="scss">
.user-center-page {
margin-bottom: 40px;
}
.user-center-title {
height: 54px;
line-height: 54px;
padding: 0 20px;
margin-bottom: 12px;
background: #fff;
border: 1px solid #eee;
color: #333;
font-size: 16px;
font-weight: 600;
}
.user-center-panel {
padding: 0 16px 16px;
background: #fff;
border: 1px solid #eee;
}
.user-center-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 54px;
border-bottom: 1px solid #eee;
}
.user-center-tabs {
display: flex;
align-items: center;
height: 54px;
}
.user-center-tab {
position: relative;
height: 54px;
line-height: 54px;
margin-right: 28px;
color: #333;
cursor: pointer;
&:hover,
&.active {
color: $theme_color;
}
&.active::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -1px;
height: 2px;
background: $theme_color;
}
}
.user-center-extra {
display: flex;
align-items: center;
}
.user-center-content {
padding-top: 14px;
}
</style>

View File

@@ -3,20 +3,23 @@
<div class="imgBox" :style="{width:data.originalWidth+'px',height:data.originalHeight + 'px'}">
<img :src="data.backImage" style="width:100%;height:100%" alt="">
<img class="slider" :src="data.slidingImage" :style="{left:distance+'px',top:data.randomY+'px'}" :width="data.sliderWidth" :height="data.sliderHeight" alt="">
<Icon type="md-refresh" class="refresh" @click="init" />
<el-icon class="refresh" @click="init"><Refresh /></el-icon>
</div>
<div class="handle" :style="{width:data.originalWidth+'px'}">
<span class="bgcolor" :style="{width:distance + 'px',background:bgColor}"></span>
<span class="swiper" :style="{left:distance + 'px'}" @mousedown="mouseDown">
<Icon type="md-arrow-round-forward" />
<el-icon><ArrowRight /></el-icon>
</span>
<span class="text">{{verifyText}}</span>
</div>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { Refresh, ArrowRight } from '@element-plus/icons-vue';
import { getVerifyImg, postVerifyImg } from './verify.js';
export default {
components: { Refresh, ArrowRight },
props: {
// 传入数据,判断是登录、注册、修改密码
verifyType: {
@@ -94,6 +97,9 @@ export default {
this.init()
});
},
close () {
this.show = false;
},
init () { // 初始化数据
this.flag = false;
this.downX = 0;
@@ -105,7 +111,7 @@ export default {
this.data = res.result;
this.show = true;
} else {
this.$Message.warning('请求失败请重试!')
Message.warning('请求失败请重试!')
}
});
}
@@ -174,7 +180,7 @@ export default {
display: flex;
align-items: center;
justify-content: center;
.ivu-icon {
.el-icon {
font-size: 20px;
}
}

View File

@@ -1,70 +1,58 @@
import Vue from "vue";
import App from "./App";
import router from "./router";
import ViewUI from "view-design";
import { createApp } from "vue";
import "core-js/stable";
import "./assets/styles/global-layout.scss";
import "./assets/styles/theme.less";
// import './assets/iconfont/iconfont.css';
import * as filters from "./plugins/filters";
import App from "./App.vue";
import { router } from "./router/index";
import store from "@/vuex/store";
import { setupElementPlus } from "@/plugins/element";
import { setupLegacyMessage } from "@/utils/message";
import { registerGlobalComponents } from "@/components/global.js";
import * as filters from "./plugins/filters";
import storage from "@/plugins/storage";
// 全局引入封装组件
import {InstallAll} from "@/components/global.js";
import { fetchAndApplyTheme } from "@/utils/theme";
import { getThemeSetting } from "@/api/common.js";
import config from "@/config";
const {aMapSecurityJsCode, title, inputMaxLength} = require("@/config");
const { aMapSecurityJsCode, inputMaxLength } = config;
const copyViewUi = {...ViewUI}
copyViewUi.Input.props.maxlength.default = inputMaxLength // 挂载最大输入值
Vue.use(copyViewUi);
Vue.use(InstallAll);
Vue.config.productionTip = false;
Object.keys(filters).forEach((key) => {
Vue.filter(key, filters[key]);
});
// 高德安全密钥
if (aMapSecurityJsCode) {
window._AMapSecurityConfig = {
securityJsCode: aMapSecurityJsCode,
};
}
router.beforeEach((to, from, next) => {
ViewUI.LoadingBar.start();
window.document.title = to.meta.title === undefined ? title : to.meta.title;
next();
});
const app = createApp(App);
router.afterEach((route) => {
ViewUI.LoadingBar.finish();
});
setupElementPlus(app);
setupLegacyMessage(app);
registerGlobalComponents(app);
Vue.prototype.linkTo = function (url) {
app.use(router);
app.use(store);
app.config.globalProperties.$filters = filters;
app.config.globalProperties.Cookies = storage;
app.config.globalProperties.$inputMaxLength = inputMaxLength;
app.config.globalProperties.linkTo = function (url) {
if (!url) return;
if (url.substr(0, 1) === "/") {
// 非外部链接没有origin只有路由地址
if (router.mode === "hash") {
window.open(location.origin + "/#" + url, "_blank");
} else {
window.open(location.origin + url, "_blank");
}
} else {
// 外部链接完整的url地址
window.open(url, "_blank");
}
};
// 联系客服
Vue.prototype.connectCs = function (
app.config.globalProperties.connectCs = function (
sign = "37ef9b97807d03c6741298ed4eb5b536d2d238e08a3c00fb01fe48f03a569974c99ad767e72c04b3165ef29aca2c488b505fe4ca"
) {
const url = "https://yzf.qq.com/xv/web/static/chat/index.html?sign=" + sign;
const url =
"https://yzf.qq.com/xv/web/static/chat/index.html?sign=" + sign;
window.open(url, "_blank");
};
Vue.prototype.Cookies = storage;
/* eslint-disable no-new */
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
router.isReady().then(async () => {
await fetchAndApplyTheme(getThemeSetting);
app.mount("#app");
});

View File

@@ -6,66 +6,61 @@
<div>
<router-link to="/"><img :src="$store.state.logoImg" /></router-link>
<div>
购物车(<span>{{ goodsTotal }}</span
>)
购物车(<span>{{ goodsTotal }}</span>)
</div>
</div>
</div>
<Divider />
<el-divider />
<!-- 购物车主体 -->
<div class="cart-content width_1200 center">
<div class="available-area">
<div class="cart-steps">
<span :class="stepIndex == 0 ? 'active' : ''">1.我的购物车</span>
<Icon
:class="stepIndex == 0 ? 'active-arrow' : ''"
custom="icomoon icon-next"
></Icon>
<i class="icomoon icon-next" :class="stepIndex == 0 ? 'active-arrow' : ''"></i>
<span :class="stepIndex == 1 ? 'active' : ''">2.填写订单信息</span>
<Icon
:class="stepIndex == 1 ? 'active-arrow' : ''"
custom="icomoon icon-next"
></Icon>
<i class="icomoon icon-next" :class="stepIndex == 1 ? 'active-arrow' : ''"></i>
<span :class="stepIndex == 2 ? 'active' : ''">3.成功提交订单</span>
</div>
</div>
<!-- 购物车商品列表 -->
<div class="cart-goods">
<div class="cart-goods-table">
<div class="cart-goods-title">
<div class="width_60">
<Checkbox
<div class="width_60 cart-col-check">
<el-checkbox
v-model="allChecked"
@on-change="changeChecked(allChecked, 'all')"
>全选</Checkbox
@change="changeChecked(allChecked, 'all')"
>全选</el-checkbox
>
</div>
<div class="goods-title">商品</div>
<div class="width_150">单价</div>
<div class="width_100">数量</div>
<div class="width_150">小计</div>
<div class="width_100">操作</div>
<div class="cart-ops-col">操作</div>
</div>
<div v-if="cartList.length === 0" class="cart-empty">
<p>购物车空空如也</p>
<router-link to="/">去选购&gt;</router-link>
</div>
<div v-else class="cart-goods-list-box">
<div
v-else
class="cart-goods-items"
class="cart-shop-group"
v-for="(shop, index) in cartList"
:key="index"
>
<div class="shop-name">
<div>
<Checkbox
<div class="width_60 cart-col-check">
<el-checkbox
v-model="shop.checked"
@on-change="changeChecked(shop.checked, 'shop', shop.storeId)"
></Checkbox>
@change="changeChecked(shop.checked, 'shop', shop.storeId)"
></el-checkbox>
</div>
<div class="shop-info">
<span class="go-shop-page" @click="goShopPage(shop.storeId)">{{
shop.storeName
}}</span>
</div>
<span
class="shop-coupon"
v-if="shop.couponList.length"
@@ -76,38 +71,37 @@
<div v-if="couponAvailable === index">
<div
class="coupon-item"
v-for="(item, index) in shop.couponList"
:key="index"
v-for="(item, couponIndex) in shop.couponList"
:key="couponIndex"
>
<span v-if="item.couponType === 'PRICE'"
>{{ item.price }}</span
>
>{{ item.price }}</span>
<span v-if="item.couponType === 'DISCOUNT'"
>{{ item.couponDiscount }}</span
>
>{{ item.couponDiscount }}</span>
<span>{{ item.consumeThreshold }}元可用</span>
<Button
<el-button
class="coupon-btn"
size="small"
type="primary"
@click="receiveShopCoupon(item)"
:disabled="item.disabled"
>{{ item.disabled ? "已领取" : "领取" }}</Button
>{{ item.disabled ? "已领取" : "领取" }}</el-button
>
</div>
</div>
</span>
<div class="promotion-notice">{{ shop.promotionNotice }}</div>
</div>
<template v-for="(goods, goodsIndex) in shop.skuList">
<div class="goods-item" :key="goodsIndex">
<div class="width_60">
<Checkbox
</div>
<template v-for="(goods, goodsIndex) in shop.skuList" :key="goods.goodsSku?.id || goodsIndex">
<div class="goods-item">
<div class="width_60 cart-col-check">
<el-checkbox
v-model="goods.checked"
@on-change="
@change="
changeChecked(goods.checked, 'goods', goods.goodsSku.id)
"
></Checkbox>
></el-checkbox>
</div>
<div
class="goods-title"
@@ -125,13 +119,13 @@
<p>
{{ goods.goodsSku.goodsName }}
</p>
<p><Tag
<p><el-tag
v-if="goods.goodsSku.salesModel === 'WHOLESALE'"
class="goods-show-tag"
color="purple"
>
批发商品
</Tag></p>
</el-tag></p>
<template
v-for="(promotion, promotionIndex) in goods.promotions"
>
@@ -164,57 +158,58 @@
</template>
</div>
</div>
<div class="width_150">
{{ goods.purchasePrice | unitPrice("¥") }}
<div class="width_150 cart-col-price">
{{ $filters.unitPrice(goods.purchasePrice, "¥") }}
</div>
<div class="width_100">
<InputNumber
<div class="width_100 cart-col-qty">
<el-input-number
:min="1"
size="small"
v-model="goods.num"
@on-change="changeNum(goods.num, goods.goodsSku.id)"
></InputNumber>
<div class="fontsize_12">
@change="changeNum(goods.num, goods.goodsSku.id)"
></el-input-number>
<div class="fontsize_12 cart-qty-stock">
{{ goods.goodsSku.quantity > 0 ? "有货" : "无货" }}
</div>
</div>
<div class="width_150">
{{ goods.subTotal | unitPrice("¥") }}
<div class="width_150 cart-col-subtotal">
{{ $filters.unitPrice(goods.subTotal, "¥") }}
</div>
<div class="width_100">
<Button
v-if="!goods.errorMessage"
<div class="cart-ops-col">
<div v-if="!goods.errorMessage" class="cart-ops">
<el-button
size="small"
type="primary"
class="cart-del-btn"
@click="delGoods(goods.goodsSku.id)"
>删除</Button
>删除</el-button
>
<Button
v-if="!goods.errorMessage"
<el-button
size="small"
type="info"
class="cart-collect-btn"
@click="collectGoods(goods.goodsSku.id)"
style="margin-left: 10px"
>收藏</Button
>收藏</el-button
>
</div>
</div>
<div class="error-goods" v-if="goods.errorMessage">
<div style="margin-top: 20px">{{ goods.errorMessage }}</div>
<Button type="primary" @click="delGoods(goods.goodsSku.id)"
>删除</Button
<el-button type="primary" @click="delGoods(goods.goodsSku.id)"
>删除</el-button
>
</div>
</div>
</template>
</div>
</div>
</div>
<!-- 底部支付栏 -->
<div class="cart-goods-footer">
<div>
<div class="width_60">
<Checkbox
<div class="width_60 cart-col-check">
<el-checkbox
v-model="allChecked"
@on-change="changeChecked(allChecked, 'all')"
>全选</Checkbox
@change="changeChecked(allChecked, 'all')"
>全选</el-checkbox
>
</div>
<div class="width_100 handle-btn" @click="delGoods()">
@@ -227,29 +222,27 @@
</div>
<div>
<div class="selected-count">
已选择<span>{{ checkedNum }}</span
>件商品
已选择<span>{{ checkedNum }}</span>件商品
</div>
<div class="ml_20 save-price">
已节省<span>{{
priceDetailDTO.discountPrice | unitPrice("¥")
}}</span>
已节省<span>{{ $filters.unitPrice(priceDetailDTO.discountPrice, "¥") }}</span>
</div>
<div class="ml_20 total-price">
总价不含运费:
<div>{{ priceDetailDTO.flowPrice | unitPrice("¥") }}</div>
<div>{{ $filters.unitPrice(priceDetailDTO.flowPrice, "¥") }}</div>
</div>
<div class="pay ml_20" @click="pay">去结算</div>
</div>
</div>
</div>
<Spin size="large" fix v-if="loading"></Spin>
<el-skeleton size="large" fix v-if="loading"></el-skeleton>
</div>
<BaseFooter class="footer"></BaseFooter>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import Promotion from "@/components/goodsDetail/Promotion";
import Search from "@/components/Search";
import * as APICart from "@/api/cart";
@@ -297,13 +290,13 @@ export default {
},
// 收藏商品
collectGoods(id) {
this.$Modal.confirm({
Modal.confirm({
title: "收藏",
content: "<p>商品收藏后可在个人中心我的收藏查看</p>",
content: "商品收藏后可在个人中心我的收藏查看",
onOk: () => {
APIMember.collectGoods("GOODS", id).then((res) => {
if (res.success) {
this.$Message.success("收藏商品成功");
Message.success("收藏商品成功");
this.getCartList();
}
});
@@ -326,16 +319,16 @@ export default {
} else {
idArr.push(id);
}
this.$Modal.confirm({
Modal.confirm({
title: "删除",
content: "<p>确定要删除该商品吗?</p>",
content: "确定要删除该商品吗?",
onOk: () => {
APICart.delCartGoods({ skuIds: idArr.toString() }).then((res) => {
if (res.success) {
this.$Message.success("删除成功");
Message.success("删除成功");
this.getCartList();
} else {
this.$Message.error(res.message);
Message.error(res.message);
}
});
},
@@ -343,16 +336,16 @@ export default {
},
// 清空购物车
clearCart() {
this.$Modal.confirm({
Modal.confirm({
title: "提示",
content: "<p>确定要清空购物车吗?清空后不可恢复</p>",
content: "确定要清空购物车吗?清空后不可恢复",
onOk: () => {
APICart.clearCart().then((res) => {
if (res.success) {
this.$Message.success("清空购物车成功");
Message.success("清空购物车成功");
this.getCartList();
} else {
this.$Message.error(res.message);
Message.error(res.message);
}
});
},
@@ -363,7 +356,7 @@ export default {
if (this.checkedNum) {
this.$router.push({ path: "/pay", query: { way: "CART" } });
} else {
this.$Message.warning("请至少选择一件商品");
Message.warning("请至少选择一件商品");
}
},
// 展示优惠券
@@ -400,10 +393,10 @@ export default {
async receiveShopCoupon(item) {
let res = await APIMember.receiveCoupon(item.id);
if (res.success) {
this.$set(item, "disabled", true);
this.$Message.success("领取成功");
item["disabled"] = true;
Message.success("领取成功");
} else {
this.$Message.error(res.message);
Message.error(res.message);
}
},
// 购物车列表
@@ -500,7 +493,7 @@ export default {
padding: 0 15px;
}
.ivu-icon {
.el-icon {
@include content_color($light_content_color);
font-size: 20px;
margin: 0 15px;
@@ -525,6 +518,10 @@ export default {
}
/** 商品列表 */
.cart-goods {
.cart-goods-table {
border: 1px solid #e0e0e0;
@include background_color($light_white_background_color);
}
&-title {
height: 50px;
@include background_color($light_white_background_color);
@@ -532,7 +529,8 @@ export default {
display: flex;
align-items: center;
padding: 0 20px;
div {
border-bottom: 1px solid #e0e0e0;
div:not(.cart-col-check) {
text-align: center;
}
.goods-title {
@@ -545,7 +543,26 @@ export default {
height: 300px;
padding-top: 100px;
}
&-items {
.cart-goods-list-box {
@include background_color($light_white_background_color);
}
.cart-col-check {
width: 60px;
min-width: 60px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-start;
text-align: left;
:deep(.el-checkbox) {
margin-right: 0;
}
}
.cart-shop-group {
&:not(:last-child) {
border-bottom: 1px solid #e0e0e0;
}
.shop-name {
height: 50px;
display: flex;
@@ -553,8 +570,14 @@ export default {
padding: 0 20px;
position: relative;
@include title_color($light_title_color);
> * {
width: 50%;
.shop-info {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
.promotion-notice {
margin-left: auto;
}
}
.go-shop-page:hover {
color: $theme_color;
@@ -572,6 +595,7 @@ export default {
.shop-coupon {
width: 80px;
height: 24px;
flex-shrink: 0;
position: relative;
background: url(../assets/images/cart-coupon-icons02.png) 0 0 no-repeat;
> div {
@@ -582,7 +606,6 @@ export default {
height: 300px;
background-color: #fff;
border: 1px solid $theme_color;
// border-radius: 3px;
z-index: 1;
padding: 10px 20px;
&::before {
@@ -625,6 +648,8 @@ export default {
.promotion-notice {
text-align: right;
font-size: 12px;
flex-shrink: 0;
margin-left: 10px;
}
.shop-coupon-show {
background-position-y: -34px;
@@ -633,12 +658,16 @@ export default {
.goods-item {
position: relative;
@extend .cart-goods-title;
@include background_color($light_white_background_color);
background: transparent;
border-top: 1px solid #f0f0f0;
padding: 10px 20px;
height: auto;
.cart-col-check {
padding-left: 20px;
box-sizing: border-box;
}
> div:nth-child(1) {
padding-left: 15px;
width: 30px;
width: 60px;
}
> div:nth-child(2) {
cursor: pointer;
@@ -660,9 +689,38 @@ export default {
}
}
}
> div:nth-child(5) {
.cart-col-price,
.cart-col-subtotal {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
text-align: center;
}
.cart-col-subtotal {
font-weight: bold;
}
.cart-col-qty {
position: relative;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
:deep(.el-input-number) {
width: 100%;
}
.cart-qty-stock {
position: absolute;
top: calc(100% + 4px);
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
line-height: 1.2;
color: #999;
}
}
.num-input {
width: 60px;
border: 1px solid #999;
@@ -743,7 +801,7 @@ export default {
width: 1200px;
margin: 0 auto;
}
.ivu-divider {
.el-divider {
background: $theme_color;
height: 2px;
}
@@ -779,9 +837,51 @@ export default {
display: flex;
align-items: center;
}
.cart-ops-col {
width: 130px;
min-width: 130px;
flex-shrink: 0;
}
.cart-ops {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex-wrap: nowrap;
}
.cart-del-btn {
background-color: #ff7d79 !important;
border-color: #ff7d79 !important;
color: #fff !important;
margin: 0 !important;
&:hover,
&:focus {
background-color: #ff6b66 !important;
border-color: #ff6b66 !important;
color: #fff !important;
}
}
.cart-collect-btn {
background-color: #fa6419 !important;
border-color: #fa6419 !important;
color: #fff !important;
margin: 0 !important;
&:hover,
&:focus {
background-color: #e55a15 !important;
border-color: #e55a15 !important;
color: #fff !important;
}
}
</style>
<style>
.ivu-input-number-input {
.el-input-number .el-input__inner {
text-align: center;
}

View File

@@ -8,38 +8,31 @@
<img src="../assets/images/logo.png" width="120" alt="" />
</router-link>
<p>领券中心</p>
<Input
search
<el-input
v-model="keyword"
style="width: 400px"
@on-search="search"
enter-button="搜索"
@keyup.enter="search"
placeholder="搜索优惠券"
/>
</div>
<div class="fontsize_18 recommend">推荐好券</div>
<empty v-if="list.length === 0" />
<ul class="coupon-list" v-else>
<empty v-if="!loading && list.length === 0" />
<ul class="coupon-list" v-else-if="list.length > 0">
<li v-for="(item, index) in list" class="coupon-item" :key="index">
<div class="c-left">
<div>
<span
v-if="item.couponType === 'PRICE'"
class="fontsize_12 global_color"
><span class="price">{{
item.price | unitPrice
}}</span></span
>
><span class="price">{{ $filters.unitPrice(item.price) }}</span></span>
<span
v-if="item.couponType === 'DISCOUNT'"
class="fontsize_12 global_color"
><span class="price">{{ item.couponDiscount }}</span
></span
>
><span class="price">{{ item.couponDiscount || item.discount }}</span></span>
<span class="describe"
>{{ item.consumeThreshold }}元可用</span
>
>{{ item.consumeThreshold }}元可用</span>
</div>
<p>使用范围{{ useScope(item.scopeType, item.storeName) }}</p>
<p>有效期{{ item.endTime }}</p>
@@ -50,28 +43,28 @@
<i class="circle-bottom"></i>
</li>
</ul>
<Page
<el-pagination v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="total"
@on-change="changePageNum"
@current-change="changePageNum"
class="pageration"
@on-page-size-change="changePageSize"
:page-size="params.pageSize"
show-total
show-sizer
>
</Page>
@size-change="changePageSize"
layout="total, sizes, prev, pager, next"></el-pagination>
</div>
</div>
<BaseFooter></BaseFooter>
</div>
</template>
<script>
import { Modal } from "@/utils/message";
import { couponList, receiveCoupon } from "@/api/member.js";
export default {
data() {
return {
list: [], // 优惠券列表
total: 0, // 优惠券总数
loading: false,
keyword: "",
params: {
// 请求参数
getType: "FREE",
@@ -82,25 +75,31 @@ export default {
},
methods: {
// 搜索优惠券
search(item) {
this.params.couponName = item;
search() {
this.params.couponName = this.keyword.trim();
this.params.pageNumber = 1;
this.getList();
},
// 获取优惠券列表
getList() {
this.$Spin.show();
if (this.loading) return;
this.loading = true;
couponList(this.params)
.then((res) => {
this.$Spin.hide();
this.loading = false;
if (res.success) {
this.list = res.result.records;
this.total = res.result.total;
if (res && res.success && res.result) {
this.list = res.result.records || [];
this.total = res.result.total || 0;
} else {
this.list = [];
this.total = 0;
}
})
.catch(() => {
this.$Spin.hide();
this.list = [];
this.total = 0;
})
.finally(() => {
this.loading = false;
});
},
// 分页 改变页码
@@ -116,12 +115,12 @@ export default {
},
// 领取优惠券
receive(item) {
receiveCoupon(item.id).then((res) => {
if (res.success) {
console.log(item);
this.$Modal.confirm({
receiveCoupon(item.id)
.then((res) => {
if (!res || !res.success) return;
Modal.confirm({
title: "领取优惠券",
content: "<p>优惠券领取成功,可到我的优惠券页面查看</p>",
content: "优惠券领取成功,可到我的优惠券页面查看",
okText: "我的优惠券",
cancelText: "立即使用",
closable: true,
@@ -135,8 +134,8 @@ export default {
});
},
});
}
});
})
.catch(() => {});
},
// 优惠券可用范围
useScope(type, storeName) {
@@ -190,8 +189,54 @@ export default {
text-align: center;
}
.coupon-item {
overflow: hidden;
$claim-width: 55px;
.c-left {
width: calc(100% - #{$claim-width});
box-sizing: border-box;
}
b {
background: url("../assets/images/small-circle.png") top left repeat-y;
right: $claim-width;
width: 0;
background: none;
border-right: 1px dashed #e0e0e0;
}
.c-right {
width: $claim-width;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
writing-mode: vertical-rl;
text-orientation: mixed;
letter-spacing: 4px;
font-size: 16px;
line-height: 1;
cursor: pointer;
text-decoration: none;
user-select: none;
&:hover {
opacity: 0.9;
}
}
i.circle-top,
i.circle-bottom {
right: calc(#{$claim-width} - 9px);
width: 18px;
height: 18px;
}
i.circle-top {
top: -9px;
}
i.circle-bottom {
bottom: -9px;
}
}
.pageration {

View File

@@ -1,5 +1,5 @@
<template>
<div class="forget-password" @click='$refs.verify.show = false'>
<div class="forget-password" @click="hideVerify">
<div style="height:50px;"></div>
<!-- 顶部logo -->
<div class="logo-box">
@@ -11,76 +11,72 @@
</div>
<div class="login-container">
<!-- 验证手机号 -->
<Form
<el-form
ref="formFirst"
:model="formFirst"
:rules="ruleInline"
style="width:300px;"
v-show="step === 0"
>
<FormItem prop="mobile">
<i-input
<el-form-item prop="mobile">
<el-input
type="text"
v-model="formFirst.mobile"
clearable
placeholder="手机号"
>
<Icon type="md-phone-portrait" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="code">
<i-input
<template #prepend><el-icon><Iphone /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input
type="text"
v-model="formFirst.code"
clearable
placeholder="手机验证码"
>
<Icon
type="ios-text-outline"
style="font-weight: bold"
slot="prepend"
/>
<Button slot="append" @click="sendCode">{{ codeMsg }}</Button>
</i-input>
</FormItem>
<FormItem>
<Button @click="verifyBtnClick" long :type="verifyStatus?'success':'default'">{{verifyStatus?'验证通过':'点击完成安全验证'}}</Button>
</FormItem>
<FormItem>
<Button type="error" @click="next" :loading="loading" long>下一步</Button>
</FormItem>
</Form>
<Form
<template #prepend><el-icon style="font-weight: bold"><Document /></el-icon></template>
<template #append><el-button @click="sendCode">{{ codeMsg }}</el-button></template>
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="verifyBtnClick" style="width:100%" :type="verifyStatus?'success':'default'">{{verifyStatus?'验证通过':'点击完成安全验证'}}</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger" @click="next" :loading="loading" style="width:100%">下一步</el-button>
</el-form-item>
</el-form>
<el-form
ref="form"
:model="form"
:rules="ruleInline"
style="width:300px;"
v-show="step === 1"
>
<FormItem prop="password">
<i-input
<el-form-item prop="password">
<el-input
type="password"
v-model="form.password"
clearable
placeholder="请输入至少六位密码"
>
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="password">
<i-input
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
v-model="form.oncePasd"
clearable
placeholder="请再次输入密码"
>
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem>
<Button type="error" size="large" @click="handleSubmit" :loading="loading1" long>提交</Button>
</FormItem>
</Form>
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="danger" size="large" @click="handleSubmit" :loading="loading1" style="width:100%">提交</el-button>
</el-form-item>
</el-form>
<!-- 拼图验证码 -->
<Verify
ref="verify"
@@ -91,35 +87,38 @@
<div class="login-btn"><a @click="$router.push('login')">前往登录</a></div>
</div>
<div class="foot">
<Row type="flex" justify="space-around" class="help">
<div type="flex" justify="space-around" class="help">
<a class="item" href="https://pickmall.cn/" target="_blank">帮助</a>
<a class="item" href="https://pickmall.cn/" target="_blank">隐私</a>
<a class="item" href="https://pickmall.cn/" target="_blank">条款</a>
</Row>
<Row type="flex" justify="center" class="copyright">
</div>
<div type="flex" justify="center" class="copyright">
Copyright © {{year}} - Present
<a href="https://pickmall.cn/" target="_blank" style="margin: 0 5px"
>{{config.title}}</a
>
版权所有
</Row>
</div>
</div>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import * as RegExp from '@/plugins/RegExp.js';
import { md5 } from '@/plugins/md5.js';
import * as apiLogin from '@/api/login.js';
import { sendSms } from '@/api/common.js';
import Verify from '@/components/verify';
import { Lock, Iphone, Document } from '@element-plus/icons-vue';
import config from '@/config';
export default {
name: 'ForgetPassword',
components: { Verify },
components: { Verify, Lock, Iphone, Document },
data () {
return {
config:require('@/config'),
config,
loading: false, // 加载状态
loading1: false, // 第二步加载状态
formFirst: { // 手机验证码表单
@@ -154,6 +153,9 @@ export default {
};
},
methods: {
hideVerify() {
this.$refs.verify?.close?.();
},
// 提交短信验证码,修改密码
next () {
this.$refs.formFirst.validate((valid) => {
@@ -163,10 +165,10 @@ export default {
apiLogin.validateCode(data).then((res) => {
this.loading = false;
if (res.success) {
// this.$Message.success('');
// Message.success('');
this.step = 1;
} else {
this.$Message.warning(res.message);
Message.warning(res.message);
}
}).catch(() => { this.loading = false; });
} else {}
@@ -177,7 +179,7 @@ export default {
if (valid) {
let params = JSON.parse(JSON.stringify(this.form));
if (params.password !== params.oncePasd) {
this.$Message.warning('两次输入密码不一致');
Message.warning('两次输入密码不一致');
return;
};
params.mobile = this.formFirst.mobile;
@@ -189,7 +191,7 @@ export default {
this.loading1 = false;
console.log(res);
if (res.success) {
this.$Message.success('修改密码成功');
Message.success('修改密码成功');
this.$router.push('login');
}
}).catch(() => { this.loading = false; });
@@ -199,11 +201,11 @@ export default {
sendCode () { // 发送验证码
if (this.time === 60) {
if (this.formFirst.mobile === '') {
this.$Message.warning('请先填写手机号');
Message.warning('请先填写手机号');
return;
}
if (!this.verifyStatus) {
this.$Message.warning('请先完成安全验证');
Message.warning('请先完成安全验证');
return;
}
let params = {
@@ -212,7 +214,7 @@ export default {
};
sendSms(params).then(res => {
if (res.success) {
this.$Message.success('验证码发送成功');
Message.success('验证码发送成功');
let that = this;
this.interval = setInterval(() => {
that.time--;
@@ -226,19 +228,19 @@ export default {
}
}, 1000);
} else {
this.$Message.warning(res.message);
Message.warning(res.message);
}
});
}
},
verifyChange (con) { // 验证通过
if (!con.status) return;
this.$refs.verify.show = false;
this.hideVerify();
this.verifyStatus = true;
},
verifyBtnClick () {
if (!this.verifyStatus) {
this.$refs.verify.init();
this.$refs.verify?.init();
}
}
},
@@ -294,7 +296,7 @@ export default {
.other-login {
margin: 0 auto;
.ivu-icon {
.el-icon {
font-size: 24px;
}
}

View File

@@ -4,23 +4,24 @@
<Search></Search>
<div class="shop-item-path">
<div class="shop-nav-container">
<Breadcrumb>
<BreadcrumbItem to="/">首页</BreadcrumbItem>
<BreadcrumbItem v-for="(item, index) in categoryBar" :to="goGoodsList(index)" target="_blank" :key="index">
<el-breadcrumb>
<el-breadcrumb-item to="/">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in categoryBar" :to="goGoodsList(index)" target="_blank" :key="index">
{{ item.name }}
</BreadcrumbItem>
</Breadcrumb>
<div class="store-collect" v-if="!takeDownSale">
</el-breadcrumb-item>
</el-breadcrumb>
<div class="store-collect" v-if="!takeDownSale && goodsMsg.data">
<span class="mr_10" v-if="goodsMsg.data">
<router-link :to="'Merchant?id=' + goodsMsg.data.storeId">{{
goodsMsg.data.storeName
}}</router-link>
<router-link
class="store-name-link"
:to="{ path: '/merchant', query: { id: goodsMsg.data.storeId } }"
>{{ goodsMsg.data.storeName }}</router-link>
</span>
<span @click="collect">
<Icon type="ios-heart" :color="storeCollected ? '#ed3f14' : '#666'" />
<span class="store-collect-btn" @click="collect">
<el-icon :color="storeCollected ? '#ffc107' : '#666'"><StarFilled /></el-icon>
{{ storeCollected? "已收藏店铺": "收藏店铺" }}
</span>
<span class="ml_10" @click="IMService(goodsMsg.data.storeId,goodsMsg.data.goodsId,goodsMsg.data.id)">联系客服</span>
<span class="ml_10 contact-service-btn" @click="IMService(goodsMsg.data.storeId,goodsMsg.data.goodsId,goodsMsg.data.id)">联系客服</span>
</div>
</div>
</div>
@@ -32,16 +33,18 @@
<empty _Title='当前商品已下架' v-if="takeDownSale">
<div class="sale-btn">
<Button size="small" class="mr_10" @click="target('/')">返回首页</Button>
<Button size="small" @click="target('goodsList')">返回商品列表</Button>
<el-button size="small" class="mr_10" @click="target('/')">返回首页</el-button>
<el-button size="small" @click="target('goodsList')">返回商品列表</el-button>
</div>
</empty>
<Spin size="large" fix v-if="isLoading"></Spin>
<el-skeleton size="large" fix v-if="isLoading"></el-skeleton>
<BaseFooter></BaseFooter>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { StarFilled } from '@element-plus/icons-vue';
import Search from "@/components/Search";
import ShowGoods from "@/components/goodsDetail/ShowGoods";
@@ -129,7 +132,7 @@ export default {
});
});
this.categoryBar = cateArr;
this.$set(this, "goodsMsg", res.result);
this["goodsMsg"] = res.result;
if (!this.goodsMsg.data.intro) {
this.goodsMsg.data.intro = ''
}
@@ -152,7 +155,7 @@ export default {
});
}
} else {
this.$Message.error(res.message);
Message.error(res.message);
this.isLoading = false
}
})
@@ -175,55 +178,84 @@ export default {
},
async collect () {
// 收藏店铺
const storeId = this.goodsMsg.data && this.goodsMsg.data.storeId;
if (!storeId) {
return;
}
if (!this.Cookies.getItem("userInfo")) {
Message.warning("请先登录");
this.$router.push("/login");
return;
}
if (this.storeCollected) {
let cancel = await cancelStoreCollect("STORE", this.goodsMsg.data.storeId);
let cancel = await cancelStoreCollect("STORE", storeId);
if (cancel.success) {
this.$Message.success("已取消收藏");
Message.success("已取消收藏");
this.storeCollected = false;
}
} else {
let collect = await collectStore("STORE", this.goodsMsg.data.storeId);
let collect = await collectStore("STORE", storeId);
if (collect.code === 200) {
this.storeCollected = true;
this.$Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
}
}
},
},
watch: {},
components: {
Search,
components: { Search,
ShowGoods,
ShowGoodsDetail,
empty
},
empty, StarFilled },
};
</script>
<style scoped lang="scss">
.shop-item-path {
height: 38px;
@include background_color($light_background_color);
line-height: 38px;
color: #2c2c2c;
}
.shop-nav-container {
width: 1200px;
height: 100%;
margin: 0 auto;
position: relative;
display: flex;
align-items: center;
:deep(.el-breadcrumb) {
line-height: 1;
}
.store-collect {
position: absolute;
right: 20px;
top: 0;
top: 50%;
transform: translateY(-50%);
color: #999;
span {
span:not(.store-collect-btn):not(.contact-service-btn) {
&:hover {
cursor: pointer;
color: $theme_color;
}
}
.store-collect-btn,
.contact-service-btn {
cursor: pointer;
}
.store-name-link {
color: $theme_color;
text-decoration: none;
&:hover {
color: $theme_color;
text-decoration: none;
}
}
}
}
.sale-btn{

View File

@@ -19,23 +19,18 @@
:key="index"
@click="orderBy(item.en, index)"
>
<span :class="{ 'goods-list-tool-active': index === sortIndex }"
>{{ item.title }}<Icon type="ios-arrow-round-down"
/></span>
<span :class="{ 'goods-list-tool-active': index === sortIndex }">
{{ item.title }}
<el-icon><ArrowDown /></el-icon>
</span>
</li>
<li @click="orderBy('price', 5, 'up')" class="price-sort">
<span :class="{ 'goods-list-tool-active': 5 === sortIndex }">
价格
<div>
<Icon
type="md-arrow-dropup"
:class="{ 'price-color': sortPriceIndex == 'desc' }"
/>
<Icon
type="md-arrow-dropdown"
:class="{ 'price-color': sortPriceIndex == 'asc' }"
/>
</div>
<span class="price-arrows">
<el-icon :class="{ 'price-color': sortPriceIndex == 'desc' }"><ArrowUp /></el-icon>
<el-icon :class="{ 'price-color': sortPriceIndex == 'asc' }"><ArrowDown /></el-icon>
</span>
</span>
</li>
</ul>
@@ -51,79 +46,76 @@
@click="goGoodsDetail(item.id, item.goodsId)"
>
<div class="goods-show-img">
<img width="220" height="220" :src="item.thumbnail" />
<img :src="item.thumbnail" />
</div>
<div class="goods-show-price">
<span>
<span class="seckill-price text-danger">{{
item.price | unitPrice("¥")
}}</span>
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
</span>
</div>
<div class="goods-show-detail">
<Tag
<el-tag
v-if="item.salesModel === 'WHOLESALE'"
class="goods-show-tag"
color="purple"
class="goods-show-tag goods-show-tag-wholesale"
>
批发
</Tag>
</el-tag>
<span>{{ item.goodsName }}</span>
</div>
<div class="goods-show-num">
已有<span>{{ item.commentNum || 0 }}</span
>人评价
已有<span>{{ item.commentNum || 0 }}</span>人评价
</div>
<div class="goods-show-seller">
<span class="text-bottom" style="color: #e4393c">{{
item.storeName
}}</span>
<span
class="text-bottom hover-pointer"
style="color: #e4393c"
@click.stop="goShopPage(item.storeId)"
>{{ item.storeName }}</span>
</div>
<div class="goods-show-right">
<Tag
class="goods-show-tag"
color="red"
<el-tag
class="goods-show-tag goods-show-tag-self"
v-if="item.selfOperated"
>
自营
</Tag>
<Tag
class="goods-show-tag"
color="blue"
</el-tag>
<el-tag
class="goods-show-tag goods-show-tag-physical"
v-if="item.goodsType === 'VIRTUAL_GOODS'"
>
虚拟
</Tag>
<Tag
class="goods-show-tag"
color="blue"
</el-tag>
<el-tag
class="goods-show-tag goods-show-tag-physical"
v-else-if="item.goodsType === 'PHYSICAL_GOODS'"
>
实物
</Tag>
</el-tag>
</div>
</div>
</div>
</div>
</div>
<div class="goods-page">
<Page
show-total
show-sizer
@on-change="changePageNum"
@on-page-size-change="changePageSize"
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
@current-change="changePageNum"
@size-change="changePageSize"
:total="total"
:page-size="params.pageSize"
></Page>
layout="total, prev, pager, next, sizes"
></el-pagination>
</div>
</div>
<Spin size="large" fix v-if="loading"></Spin>
<el-skeleton size="large" fix v-if="loading"></el-skeleton>
<BaseFooter></BaseFooter>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import GoodsClassNav from "@/components/nav/GoodsClassNav";
import * as apiGoods from "@/api/goods";
export default {
@@ -209,6 +201,17 @@ export default {
});
window.open(routeUrl.href, "_blank");
},
goShopPage(id) {
if (!id) {
Message.warning("店铺信息不存在");
return;
}
const routeUrl = this.$router.resolve({
path: "/merchant",
query: { id },
});
window.open(routeUrl.href, "_blank");
},
// 分页 修改页码
changePageNum(val) {
this.params.pageNumber = val;
@@ -255,6 +258,8 @@ export default {
},
components: {
GoodsClassNav,
ArrowDown,
ArrowUp,
},
};
</script>
@@ -285,6 +290,40 @@ export default {
padding: 0 3px;
}
.goods-show-tag-self {
--el-tag-bg-color: #fa2c19;
--el-tag-border-color: #fa2c19;
--el-tag-text-color: #fff;
background-color: #fa2c19;
border-color: #fa2c19;
color: #fff;
:deep(.el-tag__content) {
color: #fff;
}
}
.goods-show-tag-wholesale {
margin-right: 5px;
background-color: #f9f0ff;
border-color: #d3adf7;
color: #9254de;
:deep(.el-tag__content) {
color: #9254de;
}
}
.goods-show-tag-physical {
background-color: #e6f4ff;
border-color: #b3d8ff;
color: #409eff;
:deep(.el-tag__content) {
color: #409eff;
}
}
.goods-show-seller {
// padding:3px 0;
vertical-align: middle;
@@ -306,6 +345,8 @@ export default {
.goods-show-right {
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
margin-top: 5px;
}
@@ -325,38 +366,40 @@ export default {
background-color: #f1f1f1;
}
.goods-list-tool ul {
display: flex;
align-items: center;
height: 100%;
padding-left: 15px;
font-size: 12px;
margin-top: 10px;
&::after {
content: "";
display: block;
clear: left;
}
margin: 0;
list-style: none;
.price-sort {
span {
display: inline-block;
vertical-align: middle;
width: 50px;
height: 22px;
position: relative;
line-height: 15px;
top: -2px;
left: 0;
}
span > div {
display: inline-block;
.ivu-icon {
font-size: 12px;
position: absolute;
&:nth-child(1) {
top: 1px;
}
&:nth-child(2) {
top: 7px;
.price-arrows {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 12px;
height: 12px;
flex-shrink: 0;
padding: 0;
border: none;
margin: 0;
background: transparent;
line-height: 0;
.el-icon {
font-size: 8px;
line-height: 1;
height: 6px;
:deep(svg) {
width: 8px;
height: 8px;
}
}
.price-color {
color: #b3b3b3;
}
@@ -365,28 +408,37 @@ export default {
}
.goods-list-tool li {
cursor: pointer;
float: left;
}
.goods-list-tool span {
.goods-list-tool li > span {
display: inline-flex;
align-items: center;
gap: 2px;
padding: 3px 5px;
border: 1px solid #ccc;
margin-left: -1px;
background-color: #fff;
line-height: 1;
white-space: nowrap;
box-sizing: border-box;
}
.goods-list-tool span:hover {
.goods-list-tool li > span:hover {
border-color: $theme_color;
position: relative;
text-decoration: none;
z-index: 1;
}
.goods-list-tool .ivu-icon {
font-weight: bold;
font-size: 16px;
.goods-list-tool li > span .el-icon {
font-size: 12px;
line-height: 1;
}
.goods-list-tool-active {
color: #fff;
border-left: 1px solid #ccc;
background-color: $theme_color !important;
.price-color {
color: rgba(255, 255, 255, 0.65) !important;
}
}
/* ---------------商品栏结束------------------- */

View File

@@ -1,27 +1,25 @@
<template>
<div class="container">
<Modal v-model="showCpmodel" width="350">
<el-dialog v-model="showCpmodel" width="350">
<template #header>
<p style="color:black;text-align:left">
<!-- <Icon type="ios-information-circle"></Icon> -->
<!-- <el-icon><InfoFilled /></el-icon> -->
<span>活动优惠券</span>
</p>
</template>
<template>
<Scroll :on-reach-bottom="handleReachBottom">
<Card dis-hover v-for="(item, index) in autoCoupList" :key="index" style="margin: 10px 15px">
<span v-if="item.couponType === 'PRICE'" class="fontsize_12 global_color" style="font-size: 15px;">¥<span class="price">{{item.price | unitPrice}}</span></span>
<div class="coupon-scroll" @scroll="handleReachBottom">
<el-card shadow="never" v-for="(item, index) in autoCoupList" :key="index" style="margin: 10px 15px">
<span v-if="item.couponType === 'PRICE'" class="fontsize_12 global_color" style="font-size: 15px;">¥<span class="price">{{ $filters.unitPrice(item.price) }}</span></span>
<span v-if="item.couponType === 'DISCOUNT'" class="fontsize_12 global_color" style="font-size: 15px;"><span class="price">{{item.discount}}</span></span>
<span class="describe" style="font-size: 15px;margin-left: 5px;">{{item.consumeThreshold}}元可用</span>
<p style="font-size: 10px;">使用范围{{useScope(item.scopeType, item.storeName)}}</p>
<p style="font-size: 10px;">有效期{{item.endTime}}</p>
</Card>
</Scroll>
</template>
</el-card>
</div>
<template #footer>
<Button type="error" @click="showCpmodel=false">确定</Button>
<el-button type="danger" @click="showCpmodel=false">确定</el-button>
</template>
</Modal>
</el-dialog>
<!-- 固定头部 -->
<hover-search class="hover-search" :class="{show: topSearchShow}"></hover-search>
@@ -31,10 +29,11 @@
<BaseHeader></BaseHeader>
<!-- 搜索框logo -->
<Search></Search>
<!-- 商品分类 -->
<div class="index-main">
<!-- 商品分类 + 楼层装修同一宽度容器保证与分类栏左缘对齐 -->
<cateNav :showAlways="true" v-if="showNav" :large="carouselLarge" :opacity="carouselOpacity"></cateNav>
<!-- 楼层装修部分 -->
<model-form ref="modelForm" :data="modelForm"></model-form>
</div>
<!-- 底部栏 -->
<BaseFooter></BaseFooter>
<!-- 侧边栏 -->
@@ -43,6 +42,7 @@
</template>
<script>
import { InfoFilled } from '@element-plus/icons-vue';
import Search from '@/components/Search';
import ModelForm from '@/components/indexDecorate/ModelForm';
import HoverSearch from '@/components/header/hoverSearch';
@@ -125,7 +125,6 @@ export default {
}
},
getcps(){
console.log(123123)
let data = new Date()
let datas = data.getDate()
getAutoCoup().then(res=>{ //调用自动发券
@@ -146,27 +145,34 @@ export default {
}
storage.setItem('getTimes',datas)//存储缓存
}
})
}).catch(() => {});
},
handleReachBottom(){
} ,
getIndexData () {
if(this.pageData){
this.parsePageData(JSON.stringify(this.pageData))
this.parsePageData(JSON.stringify(this.pageData)).catch(() => {});
}
else{
// 获取首页装修数据
indexData({ clientType: 'PC' }).then(async (res) => {
if (res.success && res.result) {
this.parsePageData(res.result.pageData)
if (res.success && res.result?.pageData) {
try {
await this.parsePageData(res.result.pageData);
} catch {
// 装修数据解析失败时保持默认空楼层
}
});
}
}).catch(() => {});
}
},
async parsePageData(pageData){
let dataJson = JSON.parse(pageData);
const dataJson = typeof pageData === "string" ? JSON.parse(pageData) : pageData;
if (!dataJson?.list?.length) {
return;
}
// 秒杀活动不是装修的数据,需要调用接口判断是否有秒杀商品
// 轮播图根据不同轮播,样式不同
for (let i = 0; i < dataJson.list.length; i++) {
@@ -182,26 +188,31 @@ export default {
}
}
this.modelForm = dataJson;
if (dataJson.list[1]) {
storage.setItem('navList', dataJson.list[1])
}
this.showNav = true
if (dataJson.list[0]) {
this.topAdvert = dataJson.list[0];
}
},
async getListByDay () { // 当天秒杀活动
try {
const res = await seckillByDay()
if (res.success && res.result.length) {
if (res.success && res.result?.length) {
return res.result
} else {
}
} catch {
// 秒杀接口失败时不影响首页其他模块
}
return []
}
}
},
components: {
Search,
components: { Search,
ModelForm,
HoverSearch,
fixedBar
}
fixedBar, InfoFilled }
};
</script>
@@ -224,12 +235,7 @@ export default {
transition: 0.35s;
}
.show {
transform: translateY(0);
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
transform: translateY(0) translateZ(0);
top: 0;
}
.fixed-bar{
@@ -243,12 +249,7 @@ export default {
.show-fixed{
height: 354px !important;
opacity: 1 !important;
transform: translateY(0);
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
transform: translateY(0) translateZ(0);
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="login" @keyup.enter="handleSubmit('formInline')">
<!-- 顶部logo -->
<div class="top-content" @click='$refs.verify.show = false'>
<div class="top-content" @click="hideVerify">
<div class="logo-box">
<img :src="$store.state.logoImg" @click="$router.push('/')" />
<div>欢迎登录</div>
@@ -10,15 +10,15 @@
<!-- 登录主体 包含轮播图 登录模块 -->
<div class="login-container">
<!-- 轮播 -->
<Carousel loop :autoplay-speed="5000" class="login-carousel" arrow="never">
<CarouselItem>
<div class="demo-carousel" @click='$refs.verify.show = false'>
<el-carousel :interval="5000" arrow="never" class="login-carousel" height="550px">
<el-carousel-item>
<div class="demo-carousel" @click="hideVerify">
<img src="https://lili-system.oss-cn-beijing.aliyuncs.com/background.jpg" />
</div>
</CarouselItem>
</Carousel>
</el-carousel-item>
</el-carousel>
<!-- 登录模块 -->
<div class="form-box" @click='$refs.verify.show = false'>
<div class="form-box" @click="hideVerify">
<div class="account-number">
<div class="tab-switch">
<span>{{ type ? '账号登录' : '验证码登录' }}</span>
@@ -34,7 +34,7 @@
<span>
二维码已失效
</span>
<Button size='small' @click="createPCLoginSession">刷新二维码</Button>
<el-button size='small' @click="createPCLoginSession">刷新二维码</el-button>
</div>
<vue-qr
:text="qrCode"
@@ -69,45 +69,45 @@
<div>
<div v-show="!scannerCodeLoginFLag">
<!-- 账号密码登录 -->
<Form ref="formInline" :model="formData" :rules="ruleInline" v-show="type === true"
@click.self='$refs.verify.show = false'>
<FormItem prop="username">
<i-input type="text" v-model="formData.username" clearable placeholder="用户名">
<Icon type="md-person" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="password">
<i-input type="password" v-model="formData.password" clearable placeholder="密码">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem>
<Button type="error" @click.stop="handleSubmit('formInline')" long>登录</Button>
</FormItem>
</Form>
<el-form ref="formInline" :model="formData" :rules="ruleInline" v-show="type === true"
@click.self="hideVerify">
<el-form-item prop="username">
<el-input type="text" v-model="formData.username" clearable placeholder="用户名">
<template #prepend><el-icon><User /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="formData.password" clearable placeholder="密码">
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="danger" @click.stop="handleSubmit('formInline')" style="width:100%">登录</el-button>
</el-form-item>
</el-form>
<!-- 验证码登录 -->
<Form ref="formSms" :model="formSms" :rules="ruleInline" v-show="type === false"
@click.self='$refs.verify.show = false'>
<FormItem prop="mobile">
<i-input type="text" v-model="formSms.mobile" clearable placeholder="手机号">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="code">
<i-input type="text" v-model="formSms.code" placeholder="手机验证码">
<Icon type="ios-text-outline" style="font-weight: bold" slot="prepend"/>
<Button slot="append" @click="sendCode">{{ codeMsg }}</Button>
</i-input>
</FormItem>
<FormItem>
<Button @click.stop="verifyBtnClick" long
<el-form ref="formSms" :model="formSms" :rules="ruleInline" v-show="type === false"
@click.self="hideVerify">
<el-form-item prop="mobile">
<el-input type="text" v-model="formSms.mobile" clearable placeholder="手机号">
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input type="text" v-model="formSms.code" placeholder="手机验证码">
<template #prepend><el-icon style="font-weight: bold"><Document /></el-icon></template>
<template #append><el-button @click="sendCode">{{ codeMsg }}</el-button></template>
</el-input>
</el-form-item>
<el-form-item>
<el-button @click.stop="verifyBtnClick" style="width:100%"
:type="verifyStatus?'success':'default'">{{ verifyStatus ? '验证通过' : '点击完成安全验证' }}
</Button>
</FormItem>
<FormItem>
<Button type="error" @click="handleSubmit('formSms')" long>登录</Button>
</FormItem>
</Form>
</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger" @click="handleSubmit('formSms')" style="width:100%">登录</el-button>
</el-form-item>
</el-form>
</div>
<div class="other">
<div class="other-login">
@@ -126,8 +126,8 @@
</svg>
</div>
<div class="register">
<span style="color:red" @click="$router.push('signUp')">还没有账号点击立即注册</span>
<span @click="$router.push('forgetPassword')">忘记密码</span>
<span style="color:red" @click.stop="$router.push('signUp')">还没有账号点击立即注册</span>
<span @click.stop="$router.push('forgetPassword')">忘记密码</span>
</div>
</div>
</div>
@@ -138,22 +138,23 @@
<verify ref="verify" class="verify-con" verifyType="LOGIN" @change="verifyChange"></verify>
</div>
<div class="foot">
<Row type="flex" justify="space-around" class="help">
<div type="flex" justify="space-around" class="help">
<router-link to="/article" class="item" target="_blank">帮助</router-link>
<router-link to="/article?id=1371779927900160000" class="item" target="_blank">隐私</router-link>
<router-link to="/article?id=1371992704333905920" class="item" target="_blank">条款</router-link>
</Row>
<Row type="flex" justify="center" class="copyright">
</div>
<div type="flex" justify="center" class="copyright">
Copyright © {{year}} - Present
<a href="https://pickmall.cn" target="_blank" style="margin: 0 5px">{{config.title}}</a>
版权所有
</Row>
</div>
</div>
</div>
</template>
<script>
import { Message, Spin } from "@/utils/message";
import * as RegExp from "@/plugins/RegExp.js";
import { md5 } from "@/plugins/md5.js";
import * as apiLogin from "@/api/login.js";
@@ -162,12 +163,17 @@ import { webLogin, loginCallback,sCLogin,getSCLoginCode} from "@/api/login.js";
import storage from "@/plugins/storage.js";
import verify from "@/components/verify";
import vueQr from "vue-qr";
import { User, Lock, Document } from "@element-plus/icons-vue";
import config from "@/config";
export default {
name: "Login",
components: {
verify,
vueQr
vueQr,
User,
Lock,
Document,
},
data() {
return {
@@ -178,7 +184,7 @@ export default {
scannerCodeLoginFLag: false,
scannerCodeLoginStatus: 0,
qrCodeTimer:null,
config: require('@/config'),
config,
type: true, // true 账号登录 false 验证码登录
formData: {
// 登录表单
@@ -220,19 +226,22 @@ export default {
}
},
methods: {
hideVerify() {
this.$refs.verify?.close?.();
},
// 登录
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
if (this.type) {
this.$refs.verify.init();
this.$refs.verify?.init();
} else {
let data = JSON.parse(JSON.stringify(this.formSms));
apiLogin.smsLogin(data).then((res) => {
this.$refs.verify.show = false;
this.hideVerify();
if (res.success) {
this.$Message.success("登录成功");
Message.success("登录成功");
storage.setItem("accessToken", res.result.accessToken);
storage.setItem("refreshToken", res.result.refreshToken);
storage.setItem('getTimes',0)
@@ -251,7 +260,7 @@ export default {
}
});
} else {
this.$Message.error(res.message);
Message.error(res.message);
}
});
}
@@ -262,11 +271,11 @@ export default {
sendCode() {
if (this.time === 60) {
if (this.formSms.mobile === "") {
this.$Message.warning("请先填写手机号");
Message.warning("请先填写手机号");
return;
}
if (!this.verifyStatus) {
this.$Message.warning("请先完成安全验证");
Message.warning("请先完成安全验证");
return;
}
let params = {
@@ -275,7 +284,7 @@ export default {
};
sendSms(params).then((res) => {
if (res.success) {
this.$Message.success("验证码发送成功");
Message.success("验证码发送成功");
let that = this;
this.interval = setInterval(() => {
that.time--;
@@ -289,7 +298,7 @@ export default {
}
}, 1000);
} else {
this.$Message.warning(res.message);
Message.warning(res.message);
}
});
}
@@ -302,30 +311,30 @@ export default {
// 账号密码登录
let data = JSON.parse(JSON.stringify(this.formData));
data.password = md5(data.password);
this.$refs.verify.show = false;
this.$Spin.show();
this.hideVerify();
Spin.show();
apiLogin
.login(data)
.then((res) => {
if (res.success) {
this.loginSuccess(res.result.accessToken,res.result.refreshToken);
} else {
this.$Spin.hide();
this.$Message.error(res.message);
Spin.hide();
Message.error(res.message);
}
})
.catch(() => {
this.$Spin.hide();
Spin.hide();
});
} else {
this.verifyStatus = true;
this.$refs.verify.show = false;
this.hideVerify();
}
},
// 开启滑块验证
verifyBtnClick() {
if (!this.verifyStatus) {
this.$refs.verify.init();
this.$refs.verify?.init();
}
},
handleWebLogin(type) {
@@ -333,11 +342,11 @@ export default {
webLogin(type);
},
loginSuccess(accessToken,refreshToken){
this.$Message.success("登录成功");
Message.success("登录成功");
storage.setItem("accessToken", accessToken);
storage.setItem("refreshToken", refreshToken);
apiLogin.getMemberMsg().then((res) => {
this.$Spin.hide();
Spin.hide();
if (res.success) {
storage.setItem("userInfo", res.result);
let query = this.$route.query;
@@ -413,7 +422,7 @@ export default {
});
},
},
destroyed() {
beforeUnmount() {
this.clearQRLoginInfo();
},
mounted() {
@@ -444,7 +453,7 @@ export default {
this.$refs.formSms.resetFields();
}
this.verifyStatus = false;
this.$refs.verify.show = false;
this.hideVerify();
clearInterval(this.interval);
this.codeMsg = "发送验证码";
this.time = 60;
@@ -610,11 +619,12 @@ export default {
.register {
display: flex;
margin-top: -10px;
font-size: 12px;
span {
margin-left: 10px;
font-size: 12px;
&:hover {
cursor: pointer;

View File

@@ -2,20 +2,35 @@
<div class="merchant">
<BaseHeader/>
<!-- 搜索栏 -->
<Search :storeId="storeMsg.storeId" @search="search"></Search>
<Search :storeId="storeMsg.storeId || $route.query.id" @search="search"></Search>
<!-- 店铺logo -->
<div class="shop-logo">
<div>
<p>{{ storeMsg.storeName || '店铺' }}</p>
<p :alt="storeMsg.storeDesc" class="ellipsis" v-html="storeMsg.storeDesc"></p>
<div class="shop-logo-inner">
<div class="shop-info">
<p class="shop-name">{{ storeMsg.storeName || '店铺' }}</p>
<p
v-if="storeMsg.storeDesc"
:alt="storeMsg.storeDesc"
class="shop-desc ellipsis"
v-html="storeMsg.storeDesc"
></p>
</div>
<div>
<span class="hover-pointer" @click="collect"><Icon :color="storeCollected ? '#ed3f14' : '#fff'"
type="ios-heart"/>{{
<div class="shop-actions">
<span class="hover-pointer store-collect-btn" @click="collect">
<el-icon :color="storeCollected ? '#ffc107' : '#fff'">
<StarFilled />
</el-icon>
<span>{{
storeCollected ? '已收藏店铺' : '收藏店铺'
}}</span>
<span class="hover-pointer ml_10" style="width:80px" @click="IMService(storeMsg.storeId,null,null)"><Icon
custom="icomoon icon-customer-service"/>联系客服</span>
</span>
<span
class="hover-pointer store-service-btn"
@click="IMService(storeMsg.storeId, null, null)"
>
<i class="icomoon icon-customer-service"></i>联系客服
</span>
</div>
</div>
</div>
<div class="store-category">
@@ -27,22 +42,21 @@
首页
</li>
<li v-for="(cate, index) in cateList" :key="index" class="cate-item">
<Dropdown v-if="cate.children.length">
<el-dropdown v-if="cate.children.length" trigger="hover">
<div @click.self="searchByCate(cate)">
{{ cate.labelName }}
<Icon type="ios-arrow-down"></Icon>
<el-icon><ArrowDown /></el-icon>
</div>
<DropdownMenu slot="list">
<DropdownItem
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="sec in cate.children"
:key="sec.id"
:name="sec.id"
@click.native="searchByCate(sec)"
>{{ sec.labelName }}
</DropdownItem
>
</DropdownMenu>
</Dropdown>
@click="searchByCate(sec)"
>{{ sec.labelName }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span v-else @click.self="searchByCate(cate)">{{
cate.labelName
}}</span>
@@ -51,13 +65,13 @@
</div>
<div v-if="storeMsg.pageShow&&storeMsg.pageShow==='1'">
<div v-if="showFloorView">
<!-- 楼层装修部分 -->
<model-form ref="modelForm" :data="modelForm"></model-form>
</div>
<div v-else>
<div v-else class="store-goods-section">
<div class="promotion-decorate">{{ cateName }}</div>
<div class="goods-list">
<empty v-if="goodsList.length === 0"/>
@@ -70,32 +84,28 @@
>
<div class="goods-show-img">
<img :src="item.small" height="220" width="220" alt=""/>
<img :src="item.small" alt=""/>
</div>
<div class="goods-show-price">
<span>
<span class="seckill-price text-danger">{{
item.price | unitPrice("¥")
}}</span>
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
</span>
</div>
<div class="goods-show-detail">
<span>{{ item.goodsName }}</span>
</div>
<div class="goods-show-num">
已有<span>{{ item.commentNum || 0 }}</span
>人评价
已有<span>{{ item.commentNum || 0 }}</span>人评价
</div>
</div>
</div>
<div class="goods-page">
<Page
:page-size="params.pageSize"
<el-pagination v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="total"
show-sizer
@on-change="changePageNum"
@on-page-size-change="changePageSize"
></Page>
@current-change="changePageNum"
@size-change="changePageSize"
layout="sizes, prev, pager, next"></el-pagination>
</div>
</div>
@@ -107,23 +117,26 @@
<script>
import { Message } from "@/utils/message";
import { ArrowDown, StarFilled } from '@element-plus/icons-vue';
import {getCateById, getDetailById} from "@/api/shopentry";
import {cancelStoreCollect, collectStore} from "@/api/member";
import {
cancelStoreCollect,
collectStore,
isStoreCollection,
} from "@/api/member";
import {goodsList} from "@/api/goods";
import Search from "@/components/Search";
import ModelForm from "@/components/indexDecorate/ModelForm";
import HoverSearch from "@/components/header/hoverSearch";
import storage from "@/plugins/storage";
import {getFloorStoreData} from "@/api/index.js";
import { seckillByDay } from "@/api/promotion";
import imTalk from '@/components/mixes/talkIm'
export default {
name: "Merchant",
components: {
Search,
ModelForm,
HoverSearch,
},
components: { Search, ModelForm, HoverSearch, StarFilled, ArrowDown },
mixins: [imTalk],
data() {
return {
@@ -136,6 +149,7 @@ export default {
carouselOpacity: false, // 不同轮播分类样式,
enablePageData: false, //是否显示楼层装修内容
basePageData: false, //基础店铺信息
showFloorView: false, // 是否展示楼层装修(有有效装修数据时为 true
storeMsg: {}, // 店铺信息
cateList: [], // 店铺分裂
goodsList: [], // 商品列表
@@ -154,92 +168,109 @@ export default {
},
created() {
this.getStoreMsg();
window.onscroll = () => {
const top =
document.documentElement.scrollTop || document.body.scrollTop;
this.topSearchShow = top > 300;
};
},
methods: {
isFloorEnabled() {
const pageShow = this.storeMsg && this.storeMsg.pageShow;
return pageShow === "1" || pageShow === 1 || pageShow === "OPEN" || pageShow === true;
},
loadStoreGoodsView() {
this.showFloorView = false;
this.getGoodsList();
this.getCateList();
},
getIndexData() {
// 获取首页装修数据
getFloorStoreData({clientType: "PC", num: this.$route.query.id, pageType: 'STORE'}).then(
(res) => {
if (res.success) {
let dataJson = JSON.parse(res.result.pageData);
// 秒杀活动不是装修的数据,需要调用接口判断是否有秒杀商品
// 轮播图根据不同轮播,样式不同
for (const element of dataJson.list) {
let type = element.type;
getFloorStoreData({
clientType: "PC",
num: this.$route.query.id,
pageType: "STORE",
})
.then(async (res) => {
if (res.success && res.result?.pageData) {
try {
await this.parsePageData(res.result.pageData);
this.showFloorView = true;
} catch {
this.loadStoreGoodsView();
}
} else {
this.loadStoreGoodsView();
}
})
.catch(() => {
this.loadStoreGoodsView();
});
},
async parsePageData(pageData) {
const dataJson = JSON.parse(pageData);
for (let i = 0; i < dataJson.list.length; i++) {
const type = dataJson.list[i].type;
if (type === "carousel2") {
this.carouselLarge = true;
} else if (type === "carousel1") {
this.carouselLarge = true;
this.carouselOpacity = true;
} else if (type === "seckill") {
dataJson.list[i].options.list = await this.getListByDay();
}
// else if (type === "seckill") {
// let seckill = this.getListByDay();
// dataJson.list[i].options.list = seckill;
// }
}
this.modelForm = dataJson;
storage.setItem("navList", dataJson.list[1]);
this.showNav = true;
this.topAdvert = dataJson.list[0];
},
async getListByDay() {
const res = await seckillByDay();
if (res.success && res.result?.length) {
return res.result;
}
}
);
return [];
},
// getStoreMsg () { // 店铺信息
// getDetailById(this.$route.query.id).then(res => {
// if (res.success) {
// this.storeMsg = res.result
// document.title = this.storeMsg.storeName
// if (this.Cookies.getItem('userInfo')) {
// isCollection('STORE', this.storeMsg.storeId).then(res => {
// if (res.success && res.result) {
// this.storeCollected = true;
// }
// })
// }
// }
// })
// },
// async getListByDay() {
// // 当天秒杀活动
// const res = await seckillByDay();
// if (res.success && res.result.length) {
// return res.result;
// } else {
// return [];
// }
// },
checkStoreCollectionStatus() {
const storeId = this.storeMsg && this.storeMsg.storeId;
if (!storeId || !this.Cookies.getItem("userInfo")) {
return;
}
isStoreCollection("STORE", storeId)
.then((res) => {
if (res.success && res.result) {
this.storeCollected = true;
}
})
.catch(() => {});
},
getStoreMsg() {
// 店铺信息
getDetailById(this.$route.query.id).then((res) => {
if (res.success) {
const storeId = this.$route.query.id;
if (!storeId) {
Message.warning("店铺不存在");
return;
}
getDetailById(storeId)
.then((res) => {
if (res.success && res.result) {
this.storeMsg = res.result;
console.log(this.storeMsg)
//判定如果开启楼层展示,则获取页面信息 否则读取商品信息
if (this.storeMsg.pageShow && this.storeMsg.pageShow === '1') {
document.title = this.storeMsg.storeName || "店铺";
this.checkStoreCollectionStatus();
if (this.isFloorEnabled()) {
this.getIndexData();
} else {
this.getGoodsList();
this.getCateList();
this.loadStoreGoodsView();
}
let that = this;
window.onscroll = function () {
let top =
document.documentElement.scrollTop || document.body.scrollTop;
if (top > 300) {
that.topSearchShow = true;
} else {
that.topSearchShow = false;
}
};
this.storeMsg = { storeId };
this.loadStoreGoodsView();
}
})
.catch(() => {
this.storeMsg = { storeId };
this.loadStoreGoodsView();
});
},
getCateList() {
@@ -296,17 +327,35 @@ export default {
},
async collect() {
// 收藏店铺
const storeId = this.storeMsg && this.storeMsg.storeId;
if (!storeId) {
return;
}
if (!this.Cookies.getItem("userInfo")) {
Message.warning("请先登录");
this.$router.push("/login");
return;
}
try {
if (this.storeCollected) {
let cancel = await cancelStoreCollect("STORE", this.storeMsg.storeId);
const cancel = await cancelStoreCollect("STORE", storeId);
if (cancel.success) {
this.$Message.success("已取消收藏");
Message.success("已取消收藏");
this.storeCollected = false;
}
} else {
let collect = await collectStore("STORE", this.storeMsg.storeId);
if (collect.code === 200) {
const collect = await collectStore("STORE", storeId);
if (collect.success || collect.code === 200) {
this.storeCollected = true;
Message.success(
"收藏店铺成功,可以前往个人中心我的收藏查看"
);
}
}
} catch (err) {
const msg = err?.message || err?.data?.message || "";
if (msg.includes("重复收藏")) {
this.storeCollected = true;
this.$Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
}
}
},
@@ -328,31 +377,56 @@ export default {
padding: 4px;
color: #fff;
> div {
.shop-logo-inner {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
width: 1200px;
margin: 0 auto;
}
.shop-info {
display: flex;
flex-direction: row;
align-items: center;
> div:nth-child(2) {
margin-left: 10px;
flex: 1;
gap: 12px;
min-width: 0;
}
> div:nth-child(3) {
width: 200px;
}
}
p:nth-child(1) {
.shop-name {
margin: 0;
font-size: 20px;
flex-shrink: 0;
}
p:nth-child(2) {
.shop-desc {
margin: 0;
font-size: 14px;
max-height: 40px;
max-width: 400px;
}
.shop-actions {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
flex-shrink: 0;
}
.store-collect-btn {
display: inline-flex;
align-items: center;
gap: 6px;
}
.store-service-btn {
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 80px;
}
}
.store-category {
@@ -381,4 +455,32 @@ export default {
.promotion-decorate::after {
background-image: url("/src/assets/images/sprite@2x.png");
}
.store-goods-section {
width: 1200px;
max-width: 100%;
margin: 0 auto;
padding: 0 10px;
box-sizing: border-box;
.goods-list {
width: 100%;
margin: 0;
column-gap: 10px;
}
.goods-show-info {
width: calc(20% - 8px);
min-width: 0;
}
.seckill-price {
font-size: 16px;
}
.goods-show-detail {
height: 32px;
line-height: 16px;
}
}
</style>

View File

@@ -13,7 +13,7 @@
</div>
<div class="add-info-box-row">
<div class="add-info-img">
<img :src="this.detail.thumbnail" alt="">
<img :src="detail.thumbnail" alt="">
</div>
<div class="add-info-intro">
<p>{{detail.goodsName}}</p>
@@ -24,7 +24,7 @@
<div class="car-btn-group">
<div></div>
<div class="car-btn-row">
<router-link :to="`/goodsDetail?skuId=${detail.id}&goodsId=${detail.goodsId}`">
<router-link :to="{ path: '/goodsDetail', query: { skuId: detail.skuId, goodsId: detail.goodsId } }">
<button class="btn-car hover-pointer btn-car-to-detail">查看商品详情</button>
</router-link>
<router-link to="/cart">
@@ -51,8 +51,13 @@ export default {
};
},
mounted () {
let detail = this.$route.query.detail;
if (detail) this.detail = detail;
const { skuId, goodsId, thumbnail, goodsName } = this.$route.query;
this.detail = {
skuId,
goodsId,
thumbnail,
goodsName
};
}
};
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div class="sign-up" @click='$refs.verify.show = false'>
<div class="sign-up" @click="hideVerify">
<div style="height:50px;"></div>
<div class="logo-box">
<img
@@ -11,67 +11,63 @@
</div>
<div class="login-container">
<!-- 注册 -->
<Form
<el-form
ref="formRegist"
:model="formRegist"
:rules="ruleInline"
style="width:300px;"
>
<FormItem prop="username">
<i-input
<el-form-item prop="username">
<el-input
type="text"
v-model="formRegist.username"
clearable
placeholder="用户名"
>
<Icon type="md-person" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="password">
<i-input
<template #prepend><el-icon><User /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
v-model="formRegist.password"
clearable
placeholder="请输入大于6位密码"
>
<Icon type="md-lock" slot="prepend"> </Icon>
</i-input>
</FormItem>
<FormItem prop="mobilePhone">
<i-input
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="mobilePhone">
<el-input
type="text"
v-model="formRegist.mobilePhone"
clearable
placeholder="手机号"
>
<Icon type="md-phone-portrait" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem prop="code">
<i-input
<template #prepend><el-icon><Iphone /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input
type="text"
v-model="formRegist.code"
clearable
placeholder="手机验证码"
>
<Icon
type="ios-text-outline"
style="font-weight: bold"
slot="prepend"
/>
<Button slot="append" @click="sendCode">{{ codeMsg }}</Button>
</i-input>
</FormItem>
<FormItem>
<Button @click="verifyBtnClick" long :type="verifyStatus?'success':'default'">{{verifyStatus?'验证通过':'点击完成安全验证'}}</Button>
</FormItem>
<FormItem>
<Button type="error" size="large" @click="handleRegist" long
>注册</Button
<template #prepend><el-icon style="font-weight: bold"><Document /></el-icon></template>
<template #append><el-button @click="sendCode">{{ codeMsg }}</el-button></template>
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="verifyBtnClick" style="width:100%" :type="verifyStatus?'success':'default'">{{verifyStatus?'验证通过':'点击完成安全验证'}}</el-button>
</el-form-item>
<el-form-item>
<el-button type="danger" size="large" @click="handleRegist" style="width:100%"
>注册</el-button
>
</FormItem>
<FormItem><span class="color999 ml_20">点击注册表示您同意<router-link to="/article?id=1371992704333905920" target="_blank">商城用户协议</router-link></span></FormItem>
</Form>
</el-form-item>
<el-form-item><span class="color999 ml_20">点击注册表示您同意<router-link to="/article?id=1371992704333905920" target="_blank">商城用户协议</router-link></span></el-form-item>
</el-form>
<!-- 拼图验证码 -->
<Verify
ref="verify"
@@ -79,36 +75,39 @@
:verifyType="verifyType"
@change="verifyChange"
></Verify>
<div class="login-btn">已有账号<a @click="$router.push('login')">立即登录</a></div>
<div class="login-btn">已有账号<a @click.stop="$router.push('login')">立即登录</a></div>
</div>
<div class="foot">
<Row type="flex" justify="space-around" class="help">
<div type="flex" justify="space-around" class="help">
<router-link to="/article" class="item" target="_blank">帮助</router-link>
<router-link to="/article?id=1371779927900160000" class="item" target="_blank">隐私</router-link>
<router-link to="/article?id=1371992704333905920" class="item" target="_blank">条款</router-link>
</Row>
<Row type="flex" justify="center" class="copyright">
</div>
<div type="flex" justify="center" class="copyright">
Copyright © {{year}} - Present
<a href="https://pickmall.cn" target="_blank" style="margin: 0 5px">{{config.title}}</a>
版权所有
</Row>
</div>
</div>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import * as RegExp from '@/plugins/RegExp.js';
import { md5 } from '@/plugins/md5.js';
import * as apiLogin from '@/api/login.js';
import { sendSms } from '@/api/common.js';
import Verify from '@/components/verify';
import { User, Lock, Iphone, Document } from '@element-plus/icons-vue';
import config from '@/config';
export default {
name: 'SignUp',
components: { Verify },
components: { Verify, User, Lock, Iphone, Document },
data () {
return {
config:require('@/config'),
config,
year: new Date().getFullYear(),
formRegist: {
// 注册表单
@@ -142,32 +141,36 @@ export default {
};
},
methods: {
hideVerify() {
this.$refs.verify?.close?.();
},
// 注册
handleRegist () {
this.$refs.formRegist.validate((valid) => {
if (valid) {
let data = JSON.parse(JSON.stringify(this.formRegist));
if (!valid) {
return;
}
const data = JSON.parse(JSON.stringify(this.formRegist));
data.password = md5(data.password);
apiLogin.regist(data).then((res) => {
if (res.success) {
this.$Message.success('注册成功!');
Message.success('注册成功!');
this.$router.push('login');
} else {
this.$Message.warning(res.message);
Message.warning(res.message || '注册失败');
}
});
} else {}
});
}).catch(() => {});
}).catch(() => {});
},
// 发送短信验证码
sendCode () {
if (this.time === 60) {
if (this.formRegist.mobilePhone === '') {
this.$Message.warning('请先填写手机号');
Message.warning('请先填写手机号');
return;
}
if (!this.verifyStatus) {
this.$Message.warning('请先完成安全验证');
Message.warning('请先完成安全验证');
return;
}
let params = {
@@ -176,7 +179,7 @@ export default {
};
sendSms(params).then(res => {
if (res.success) {
this.$Message.success('验证码发送成功');
Message.success('验证码发送成功');
let that = this;
this.interval = setInterval(() => {
that.time--;
@@ -190,21 +193,21 @@ export default {
}
}, 1000);
} else {
this.$Message.warning(res.message);
Message.warning(res.message || '验证码发送失败');
}
});
}).catch(() => {});
}
},
// 图片验证码成功回调
verifyChange (con) {
if (!con.status) return;
this.$refs.verify.show = false;
this.hideVerify();
this.verifyStatus = true;
},
// 打开图片验证码
verifyBtnClick () {
if (!this.verifyStatus) {
this.$refs.verify.init();
this.$refs.verify?.init();
}
}
},
@@ -254,7 +257,7 @@ export default {
.other-login {
margin: 0 auto;
.ivu-icon {
.el-icon {
font-size: 24px;
}
}

View File

@@ -12,7 +12,7 @@
<script>
import { articleDetail } from "@/api/common.js";
const config = require("@/config/index");
import config from "@/config/index";
export default {
data() {
return {

View File

@@ -5,39 +5,34 @@
<div class="title-bg"><p>文章帮助中心</p></div>
<div class="container width_1200">
<Layout class="layoutAll">
<Sider class="side-bar" ref="side" :collapsed-width="78">
<div class="layoutAll">
<el-aside class="side-bar" width="200px">
<div class="article-cate">文章分类列表</div>
<Menu
<el-menu
class="side-menu"
theme="light"
width="auto"
ref='menu'
:active-name="activeName"
:open-names="openName"
@on-select="onSelect"
:key="menuKey"
:default-active="String(activeName)"
:default-openeds="openName"
@select="onSelect"
>
<!-- 循环导航栏 -->
<Submenu
v-show="menu.children"
<el-sub-menu
v-for="(menu, index) in list"
v-show="menu.children"
:key="index"
:name="menu.articleCategoryName"
:index="menu.articleCategoryName"
>
<template slot="title">
<template #title>
<span>{{ menu.articleCategoryName }}</span>
</template>
<MenuItem
<el-menu-item
v-for="(chlidren, i) in menu.children"
:key="i"
:name="chlidren.id"
>{{ chlidren.articleCategoryName }}</MenuItem
>
</Submenu>
</Menu>
</Sider>
<Layout class="layout ml_10">
<Content class="content">
:index="String(chlidren.id)"
>{{ chlidren.articleCategoryName }}</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<el-main class="content ml_10">
<ul class="article-list" v-show="showList">
<li v-for="(article, index) in articleList" :key="index" @click="getDetail(article.id)">
{{ article.title }}
@@ -50,41 +45,41 @@
<div class="mt_10 mb_10" v-html="detail.content"></div>
</div>
</transition>
</Content>
</Layout>
</Layout>
</el-main>
</div>
</div>
</div>
</template>
<script>
import { articleCateList, articleDetail, articleList } from '@/api/common.js'
export default {
name: 'Home',
name: 'ArticleIndex',
data () {
return {
list: [], // 分类列表
detail: '', // 文章详情
articleList: [], // 分类下的文章列表
activeName: '1347456734864367616', // 左侧激活项
openName: [], // 展开的名称
params: { // 请求参数
list: [],
detail: '',
articleList: [],
activeName: '1347456734864367616',
openName: [],
menuKey: 0,
params: {
pageNumber: 1,
pageSize: 100,
categoryId: '',
sort: 'sort'
},
showList: true // 展示文章列表
showList: true
};
},
methods: {
// 每次点击左侧bar的callback
onSelect (id) {
this.getList(id)
this.detail = ''
this.showList = true
},
getCateList () { // 文章分类列表
getCateList () {
articleCateList().then(res => {
if (res.success) {
this.list = res.result
@@ -105,15 +100,12 @@ export default {
this.activeName = this.list[0].children[0].id
this.openName.push(this.list[0].articleCategoryName)
}
this.$nextTick(() => {
this.$refs.menu.updateOpened()
this.$refs.menu.updateActiveName()
})
this.menuKey++
this.getList(this.activeName)
}
})
},
getList (id) { // 文章列表
getList (id) {
this.params.categoryId = id
articleList(this.params).then(res => {
if (res.success) {
@@ -121,7 +113,7 @@ export default {
}
})
},
async getDetail (id) { // 文章详情
async getDetail (id) {
await articleDetail(id).then(res => {
if (res.success) {
this.detail = res.result
@@ -162,6 +154,7 @@ export default {
}
.layoutAll {
display: flex;
min-height: 1200px;
@include background_color($light_background_color);
}
@@ -177,7 +170,6 @@ export default {
.layout-footer-center {
padding: 0px 15px;
padding-bottom: 15px;
text-align: center;
}
@@ -191,7 +183,6 @@ export default {
height: 100px;
line-height: 100px;
color: #fff;
line-height: 100px;
margin: 0 auto;
padding-left: 10px;
}

View File

@@ -1,89 +1,98 @@
<template>
<div class="wrapper">
<card _Title="账户安全"/>
<UserCenterLayout title="账户安全" :tabs="['账户安全']">
<div class="safeList">
<!-- 密码 -->
<Row class="safeItem">
<Col :span="2">
<Icon size="40" type="md-key"/>
</Col>
<Col :span="16">
<div class="safeItem">
<div class="safeItem-icon">
<el-icon :size="40"><Key /></el-icon>
</div>
<div class="safeItem-content">
<div class="setDivItem">登录密码</div>
<div class="setDivItem theme">互联网账号存在被盗风险建议您定期更改密码以保护账户安全</div>
</Col>
<Col :span="4">
<Button @click="modifyPwd">修改密码</Button>
</Col>
</Row>
</div>
<div class="safeItem-action">
<el-button @click="modifyPwd">修改密码</el-button>
</div>
</div>
<Row class="safeItem">
<Col :span="2">
<Icon size="40" type="md-link" />
</Col>
<Col :span="16">
<div class="safeItem safeItem-connect">
<div class="safeItem-main">
<div class="safeItem-icon">
<el-icon :size="40"><Link /></el-icon>
</div>
<div class="safeItem-content">
<div class="setDivItem">第三方账户绑定</div>
<div class="setDivItem theme">绑定后可使用第三方账号快捷登录</div>
</div>
<div class="safeItem-action">
<el-button v-if="!isBound(wechatConnect.type)" type="primary" @click="openBindModal(wechatConnect)">绑定</el-button>
<el-button v-else @click="confirmUnbind(wechatConnect)">解绑</el-button>
</div>
</div>
<div class="connectList">
<div class="connectRow">
<div class="connectRowLeft">
<span class="connectName">{{ wechatConnect.label }}</span>
<Tag v-if="isBound(wechatConnect.type)" color="success">已绑定</Tag>
<Tag v-else>未绑定</Tag>
<el-tag v-if="isBound(wechatConnect.type)" color="success">已绑定</el-tag>
<el-tag v-else>未绑定</el-tag>
</div>
</div>
<Spin size="large" fix v-if="connectLoading"></Spin>
<el-skeleton size="large" fix v-if="connectLoading"></el-skeleton>
</div>
</Col>
<Col :span="4">
<Button v-if="!isBound(wechatConnect.type)" type="primary" @click="openBindModal(wechatConnect)">绑定</Button>
<Button v-else @click="confirmUnbind(wechatConnect)">解绑</Button>
</Col>
</Row>
</div>
</div>
</UserCenterLayout>
<Modal
<el-dialog
v-model="bindModalVisible"
title="绑定第三方账户"
:mask-closable="false"
:footer-hide="bindForm.type === 'WECHAT'"
:ok-text="bindModalOkText"
:loading="bindSubmitLoading"
@on-ok="submitBind"
@on-cancel="resetBindForm"
:close-on-click-modal="false"
@closed="resetBindForm"
>
<Form :label-width="110">
<FormItem label="类型">
<Input v-model="bindForm.typeLabel" disabled />
</FormItem>
<el-form label-width="110px">
<el-form-item label="类型">
<el-input v-model="bindForm.typeLabel" disabled />
</el-form-item>
<FormItem v-if="bindForm.type === 'WECHAT'" label="公众号二维码">
<el-form-item v-if="bindForm.type === 'WECHAT'" label="公众号二维码">
<div class="wechatQrBox">
<div class="wechatQrImgBox">
<Spin fix v-if="qrCodeLoading"></Spin>
<el-skeleton fix v-if="qrCodeLoading"></el-skeleton>
<img v-if="qrCodeSrc" class="wechatQrImg" :src="qrCodeSrc" alt="微信公众号二维码" />
<div v-else class="wechatQrEmpty">二维码获取失败请刷新重试</div>
</div>
<div class="wechatQrActions">
<Button @click="fetchWechatQrCode">刷新二维码</Button>
<Button type="primary" @click="submitBind">我已完成绑定</Button>
<el-button :loading="qrCodeLoading" @click="fetchWechatQrCode">刷新二维码</el-button>
<el-button type="primary" :loading="bindSubmitLoading" @click="submitBind">我已完成绑定</el-button>
</div>
<div class="wechatQrTip">请使用微信扫码关注/授权完成后点击我已完成绑定检测绑定状态</div>
</div>
</FormItem>
</el-form-item>
<FormItem v-if="bindForm.type !== 'WECHAT'" label="OpenID/UnionID">
<Input v-model="bindForm.unionID" placeholder="请输入 OpenID/UnionID" />
</FormItem>
</Form>
</Modal>
<el-form-item v-if="bindForm.type !== 'WECHAT'" label="OpenID/UnionID">
<el-input v-model="bindForm.unionID" placeholder="请输入 OpenID/UnionID" />
</el-form-item>
</el-form>
<template v-if="bindForm.type !== 'WECHAT'" #footer>
<el-button @click="bindModalVisible = false">取消</el-button>
<el-button type="primary" :loading="bindSubmitLoading" @click="submitBind">
{{ bindModalOkText }}
</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import { Key, Link } from '@element-plus/icons-vue';
// import { getPwdStatus } from '@/api/account';
import storage from '@/plugins/storage'
import { bindThirdAccount, getThirdAccountBindList, getWechatH5QrCode, unbindThirdAccount } from '@/api/login.js'
export default {
components: { Key, Link },
name: 'AccountSafe',
data () {
return {
@@ -181,7 +190,7 @@ export default {
const userId = userInfo && userInfo.id ? String(userInfo.id) : ''
if (!userId) {
this.qrCodeBase64 = ''
this.$Message.error('未获取到当前用户ID请重新登录后重试')
Message.error('未获取到当前用户ID请重新登录后重试')
return
}
this.qrCodeLoading = true
@@ -192,7 +201,7 @@ export default {
return
}
this.qrCodeBase64 = ''
this.$Message.error((res && res.message) || '二维码获取失败')
Message.error((res && res.message) || '二维码获取失败')
})
.catch(() => {
this.qrCodeBase64 = ''
@@ -208,12 +217,12 @@ export default {
this.refreshConnectBinds()
.then(() => {
if (this.isBound(this.bindForm.type)) {
this.$Message.success('绑定成功')
Message.success('绑定成功')
this.bindModalVisible = false
this.resetBindForm()
return
}
this.$Message.warning('暂未检测到绑定,请扫码后重试')
Message.warning('暂未检测到绑定,请扫码后重试')
this.$nextTick(() => {
this.bindModalVisible = true
})
@@ -224,11 +233,11 @@ export default {
return
}
if (!this.bindForm.type) {
this.$Message.error('绑定类型不能为空')
Message.error('绑定类型不能为空')
return
}
if (!this.bindForm.unionID) {
this.$Message.error('OpenID/UnionID 不能为空')
Message.error('OpenID/UnionID 不能为空')
this.$nextTick(() => {
this.bindModalVisible = true
})
@@ -238,7 +247,7 @@ export default {
this.bindSubmitLoading = true
bindThirdAccount({ unionID: this.bindForm.unionID, type: this.bindForm.type })
.then(() => {
this.$Message.success('绑定成功')
Message.success('绑定成功')
this.bindModalVisible = false
this.resetBindForm()
this.refreshConnectBinds()
@@ -248,12 +257,12 @@ export default {
})
},
confirmUnbind (item) {
this.$Modal.confirm({
Modal.confirm({
title: '解绑确认',
content: `<p>确定解绑 ${item.label} 吗?</p>`,
onOk: () => {
unbindThirdAccount({ type: item.type }).then(() => {
this.$Message.success('解绑成功')
Message.success('解绑成功')
this.refreshConnectBinds()
})
}
@@ -264,11 +273,6 @@ export default {
</script>
<style scoped lang="scss">
::v-deep .ivu-col-span-2, .ivu-col-span-4 {
text-align: center;
color: $theme_color;
}
.theme {
color: $theme_color;
}
@@ -278,18 +282,47 @@ export default {
}
.safeItem {
display: flex;
align-items: center;
gap: 16px;
border-bottom: 1px solid $border_color;
padding: 16px 0;
::v-deep .ivu-col {
padding: 8px 0;
}
.safeItem-connect {
display: block;
}
.safeItem-main {
display: flex;
align-items: center;
gap: 16px;
}
.safeItem-icon {
flex-shrink: 0;
width: 56px;
display: flex;
align-items: center;
justify-content: center;
color: $theme_color;
}
.safeItem-content {
flex: 1;
min-width: 0;
}
.safeItem-action {
flex-shrink: 0;
display: flex;
align-items: center;
}
.connectList {
position: relative;
margin-top: 8px;
margin-left: 72px;
}
.connectRow {

View File

@@ -1,15 +1,16 @@
<template>
<div class="wrapper">
<card _Title="评论/晒单" />
<UserCenterLayout title="评论/晒单" :tabs="['评论/晒单']">
<div class="order">
<div class="order-title">
<Row class="order-row title">
<i-col span="12">订单详情</i-col>
<i-col span="4">收货人</i-col>
<i-col span="4">评价</i-col>
<i-col span="6"></i-col>
</Row>
<div class="order-row title">
<div class="order-detail-title col-10">订单详情</div>
<div class="col-4">收货人</div>
<div class="col-4">评价等级</div>
<div class="col-4">评价内容</div>
<div class="col-2"></div>
</div>
</div>
<empty v-if="list.length === 0" />
@@ -20,41 +21,42 @@
<span class="color999 ml_10">{{item.createTime}}</span>
<span class="hover-pointer fontsize_12 eval-detail" @click="evaluateDetail(item.id)">评价详情</span>
</div>
<Row class="order-item-view">
<i-col span="12" class="item-view-name">
<div class="order-item-view">
<div class="item-view-name col-10">
<div class="order-img hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
<img :src="item.goodsImage" alt="" />
</div>
<div class="order-name hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
{{item.goodsName}}
</div>
</i-col>
<i-col span="4">{{item.createBy | secrecyMobile}}</i-col>
<i-col span="4">
</div>
<div class="col-4">{{ $filters.secrecyMobile(item.createBy ) }}</div>
<div class="col-4">
{{item.grade==='GOOD'?'好评' : item.grade === 'WORSE'?'差评' : '中评'}}
</i-col>
<i-col span="4">
<Tooltip transfer>
</div>
<div class="col-4">
<el-tooltip>
<div class="content">{{item.content}}</div>
<div style="white-space: normal;word-break:break-all" slot="content">
<template #content>
<div style="white-space: normal;word-break:break-all">
{{item.content}}
</div>
</Tooltip>
</i-col>
</Row>
</template>
</el-tooltip>
</div>
<div class="col-2"></div>
</div>
</div>
<Spin v-if="loading"></Spin>
</div>
<el-skeleton v-if="loading"></el-skeleton>
</div>
<!-- 分页 -->
<div class="page-size">
<Page :total="total" @on-change="changePageNum"
@on-page-size-change="changePageSize"
:page-size="params.pageSize"
show-total
show-sizer>
</Page>
<el-pagination v-model:current-page="params.pageNumber" v-model:page-size="params.pageSize"
:total="total" @current-change="changePageNum"
@size-change="changePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</UserCenterLayout>
</div>
</template>
@@ -144,8 +146,45 @@ export default {
font-size: 13px;
position: relative;
}
.order-row,
.order-item-view {
display: flex;
align-items: center;
> [class*='col-'] {
box-sizing: border-box;
padding: 0 8px;
}
> .col-12 {
flex: 0 0 50%;
max-width: 50%;
}
> .col-10 {
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
> .col-4 {
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
> .col-2 {
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
> .col-6 {
flex: 0 0 25%;
max-width: 25%;
}
}
.order-item-view {
padding: 10px 20px;
align-items: flex-start;
}
.order-item {
text-align: center;
@@ -156,6 +195,10 @@ export default {
padding: 10px 0;
text-align: center;
}
.order-detail-title {
text-align: left;
padding-left: 20px !important;
}
.content {
color: #999;
max-height: 60px;

View File

@@ -1,7 +1,7 @@
<template>
<div class="add-eval">
<UserCenterLayout title="订单投诉" :tabs="['订单投诉']">
<div class="title">
<card _Title="订单投诉" :_Size="16"></card>
<p>
<span class="fontsize_16 global_color">{{ statusLabel[detail.complainStatus] }}</span>
<span class="color999 ml_20">创建时间</span><span>{{ detail.createTime }}</span>
@@ -9,7 +9,7 @@
</p>
</div>
<Alert class="l_title" show-icon type="warning">我的申诉信息</Alert>
<el-alert class="l_title" show-icon type="warning">我的申诉信息</el-alert>
<table cellspacing="0" cellpadding='0' border="1">
<tr>
<td>投诉商品</td>
@@ -31,7 +31,7 @@
<div class="demo-upload-list" v-for="(img, index) in detail.images.split(',')" :key="index">
<img :src="img">
<div class="demo-upload-list-cover">
<Icon type="ios-eye-outline" @click.native="handleView(img)"></Icon>
<el-icon @click="handleView(img)"><View /></el-icon>
</div>
</div>
</template>
@@ -41,7 +41,7 @@
</tr>
</table>
<Alert class="l_title" show-icon type="warning">商家申诉信息</Alert>
<el-alert class="l_title" show-icon type="warning">商家申诉信息</el-alert>
<table cellspacing="0" cellpadding='0' border="1">
<tr>
<td>申诉时间</td>
@@ -60,7 +60,7 @@
<div class="demo-upload-list" v-for="(img, index) in detail.appealImages.split(',')" :key="index">
<img :src="img">
<div class="demo-upload-list-cover">
<Icon type="ios-eye-outline" @click.native="handleView(img)"></Icon>
<el-icon @click="handleView(img)"><View /></el-icon>
</div>
</div>
</template>
@@ -70,7 +70,7 @@
</tr>
</table>
<Alert class="l_title" show-icon type="warning">平台仲裁</Alert>
<el-alert class="l_title" show-icon type="warning">平台仲裁</el-alert>
<table cellspacing="0" cellpadding='0' border="1">
<tr>
<td>仲裁意见</td>
@@ -78,7 +78,7 @@
</tr>
</table>
<Alert class="l_title" show-icon type="warning">对话详情</Alert>
<el-alert class="l_title" show-icon type="warning">对话详情</el-alert>
<div class="speak-way" v-if="detail.orderComplaintCommunications && detail.orderComplaintCommunications.length">
<div
class="speak-msg seller"
@@ -108,22 +108,26 @@
<tr>
<td></td>
<td>
<Button type="primary" :loading="submitLoading" @click="handleSubmit" style="margin-left: 5px">
<el-button type="primary" :loading="submitLoading" @click="handleSubmit" style="margin-left: 5px">
回复
</Button>
</el-button>
</td>
</tr>
</table>
<Modal title="View Image" v-model="visible">
<el-dialog title="View Image" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%">
</Modal>
</el-dialog>
</UserCenterLayout>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { View } from '@element-plus/icons-vue';
import {getComplainDetail} from '@/api/member.js';
import {communication} from '@/api/order';
export default {
components: { View },
data () {
return {
detail: {}, // 评价详情
@@ -167,7 +171,7 @@ export default {
// 回复消息
handleSubmit () {
if (this.params.content === '') {
this.$Message.error('请填写对话内容');
Message.error('请填写对话内容');
return;
}
this.submitLoading = true;
@@ -175,7 +179,7 @@ export default {
communication(this.params).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success('对话成功');
Message.success('对话成功');
this.params.content = '';
this.getDetail();
}

View File

@@ -1,15 +1,16 @@
<template>
<div class="wrapper">
<card _Title="我的投诉" />
<UserCenterLayout title="我的投诉" :tabs="['我的投诉']">
<div class="order">
<div class="order-title">
<Row class="order-row title">
<i-col span="12">商品信息</i-col>
<i-col span="4">投诉状态</i-col>
<i-col span="4">投诉主题</i-col>
<i-col span="6"></i-col>
</Row>
<div class="order-row title">
<div class="order-detail-title col-10">商品信息</div>
<div class="col-4">投诉状态</div>
<div class="col-4">投诉主题</div>
<div class="col-4">投诉内容</div>
<div class="col-2"></div>
</div>
</div>
<empty v-if="list.length === 0" />
@@ -21,45 +22,47 @@
<span class="hover-pointer fontsize_12 eval-detail" @click="goDetail(item.id)">投诉详情</span>
<span class="hover-pointer fontsize_12 eval-detail" style="right: 90px" v-if="item.complainStatus === 'APPLYING' || item.complainStatus === 'NEW'" @click="cancel(item.id)">取消投诉</span>
</div>
<Row class="order-item-view">
<i-col span="12" class="item-view-name">
<div class="order-item-view">
<div class="item-view-name col-10">
<div class="order-img hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
<img :src="item.goodsImage" alt="" />
</div>
<div class="order-name hover-color" @click="linkTo(`/goodsDetail?goodsId=${item.goodsId}&skuId=${item.skuId}`)">
{{item.goodsName}}
</div>
</i-col>
<i-col span="4">{{statusLabel[item.complainStatus]}}</i-col>
<i-col span="4">
</div>
<div class="col-4">{{statusLabel[item.complainStatus]}}</div>
<div class="col-4">
<div class="content">{{item.complainTopic}}</div>
</i-col>
<i-col span="4">
<Tooltip >
</div>
<div class="col-4">
<el-tooltip>
<div class="content">{{item.content}}</div>
<div style="white-space: normal;" slot="content">
<template #content>
<div style="white-space: normal;">
{{item.content}}
</div>
</Tooltip>
</i-col>
</Row>
</template>
</el-tooltip>
</div>
<div class="col-2"></div>
</div>
</div>
<Spin v-if="loading"></Spin>
</div>
<el-skeleton v-if="loading"></el-skeleton>
</div>
<!-- 分页 -->
<div class="page-size">
<Page :total="total" @on-change="changePageNum"
@on-page-size-change="changePageSize"
:page-size="params.pageSize"
show-total
show-sizer>
</Page>
<el-pagination v-model:current-page="params.pageNumber" v-model:page-size="params.pageSize"
:total="total" @current-change="changePageNum"
@size-change="changePageSize" layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</UserCenterLayout>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import {complainList, clearComplain} from '@/api/member.js';
export default {
name: 'ComplainList',
@@ -105,14 +108,14 @@ export default {
this.getList()
},
cancel (id) { // 取消投诉
this.$Modal.confirm({
Modal.confirm({
title: '取消投诉',
content: '<p>确定取消投诉吗?</p>',
content: '确定取消投诉吗?',
onOk: () => {
clearComplain(id).then((res) => {
if (res.success) {
this.$Message.success('取消投诉成功');
this.getCartList();
Message.success('取消投诉成功');
this.getList();
}
});
},
@@ -160,8 +163,35 @@ export default {
font-size: 13px;
position: relative;
}
.order-row,
.order-item-view {
display: flex;
align-items: center;
> [class*='col-'] {
box-sizing: border-box;
padding: 0 8px;
}
> .col-10 {
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
> .col-4 {
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
> .col-2 {
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
}
.order-item-view {
padding: 10px 20px;
align-items: flex-start;
}
.order-item {
text-align: center;
@@ -172,11 +202,18 @@ export default {
padding: 10px 0;
text-align: center;
}
.order-detail-title {
text-align: left;
padding-left: 20px !important;
}
.content {
color: #999;
max-height: 60px;
overflow: hidden;
word-wrap: break-word;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.eval-detail {
@@ -187,6 +224,7 @@ export default {
}
}
.page-size{
text-align: right;
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -1,42 +1,41 @@
<template>
<div class="wrapper">
<card _Title="我的分销" />
<UserCenterLayout title="我的分销" :tabs="['我的分销']">
<!-- 分销申请 -->
<div v-if="status === 0">
<Alert type="warning">分销商申请</Alert>
<Form ref="form" :model="applyForm" :rules="rules">
<FormItem label="姓名" prop="name">
<Input v-model="applyForm.name"></Input>
</FormItem>
<FormItem label="身份证号" prop="idNumber">
<Input v-model="applyForm.idNumber"></Input>
</FormItem>
<FormItem label="银行开户行" prop="settlementBankBranchName">
<Input v-model="applyForm.settlementBankBranchName"></Input>
</FormItem>
<FormItem label="银行开户名" prop="settlementBankAccountName">
<Input v-model="applyForm.settlementBankAccountName"></Input>
</FormItem>
<FormItem label="银行账号" prop="settlementBankAccountNum">
<Input v-model="applyForm.settlementBankAccountNum"></Input>
</FormItem>
<el-alert type="warning">分销商申请</el-alert>
<el-form ref="form" :model="applyForm" :rules="rules">
<el-form-item label="姓名" prop="name">
<el-input v-model="applyForm.name"></el-input>
</el-form-item>
<el-form-item label="身份证号" prop="idNumber">
<el-input v-model="applyForm.idNumber"></el-input>
</el-form-item>
<el-form-item label="银行开户行" prop="settlementBankBranchName">
<el-input v-model="applyForm.settlementBankBranchName"></el-input>
</el-form-item>
<el-form-item label="银行开户名" prop="settlementBankAccountName">
<el-input v-model="applyForm.settlementBankAccountName"></el-input>
</el-form-item>
<el-form-item label="银行账号" prop="settlementBankAccountNum">
<el-input v-model="applyForm.settlementBankAccountNum"></el-input>
</el-form-item>
<FormItem>
<Button type="primary" :loading="applyLoading" @click="apply"
>提交申请</Button
<el-form-item>
<el-button type="primary" :loading="applyLoading" @click="apply"
>提交申请</el-button
>
</FormItem>
</Form>
</el-form-item>
</el-form>
</div>
<!-- 分销审核 -->
<div v-if="status === 1">
<Alert type="success">
您提交的信息正在审核
<template slot="desc"
>提交认证申请后工作人员将在三个工作日进行核对完成审核</template
>
</Alert>
<el-alert
type="success"
title="您提交的信息正在审核"
description="提交认证申请后工作人员将在三个工作日进行核对完成审核"
/>
</div>
<!-- 分销提现商品订单 -->
<div v-if="status === 2">
@@ -54,144 +53,116 @@
<div class="mb_20 account-price">
<span class="subTips">可提现金额</span>
<span class="fontsize_48 global_color"
>{{ result.canRebate | unitPrice }}</span
>
>{{ $filters.unitPrice(result.canRebate) }}</span>
<span class="subTips">冻结金额</span>
<span class="">{{ result.commissionFrozen | unitPrice }}</span>
<span class="">{{ $filters.unitPrice(result.commissionFrozen) }}</span>
<span class="subTips">返利总金额</span>
<span class="">{{ result.rebateTotal | unitPrice }}</span>
<Button
<span class="">{{ $filters.unitPrice(result.rebateTotal) }}</span>
<el-button
type="primary"
size="small"
class="ml_20"
@click="withdrawApplyModal = true"
>申请提现</Button
>申请提现</el-button
>
</div>
</div>
<Tabs :value="tabName" @on-click="tabPaneChange">
<TabPane label="已选商品" name="goodsChecked">
<Table stripe :columns="goodsColumns" :data="goodsData.records">
<template slot-scope="{ row }" slot="name">
<el-tabs v-model="tabName" @tab-click="(tab) => tabPaneChange(tab.paneName)">
<el-tab-pane label="已选商品" name="goodsChecked">
<el-table stripe :data="goodsData.records || []">
<el-table-column label="商品名称" width="400">
<template #default="{ row }">
<div
class="goods-msg"
@click="
linkTo(
`/goodsDetail?skuId=${row.skuId}&goodsId=${row.goodsId}`
)
"
@click="linkTo(`/goodsDetail?skuId=${row.skuId}&goodsId=${row.goodsId}`)"
>
<img
style="vertical-align: top"
:src="row.thumbnail"
width="60"
height="60"
alt=""
/>&nbsp; {{ row.goodsName }}
<img style="vertical-align: top" :src="row.thumbnail" width="60" height="60" alt="" />
&nbsp; {{ row.goodsName }}
</div>
</template>
<template slot-scope="{ row }" slot="price">
<span> {{ row.price | unitPrice }}</span>
</el-table-column>
<el-table-column label="商品价格">
<template #default="{ row }">
<span> {{ $filters.unitPrice(row.price) }}</span>
</template>
<template slot-scope="{ row }" slot="commission">
<span> {{ row.commission | unitPrice }}</span>
</el-table-column>
<el-table-column label="佣金">
<template #default="{ row }">
<span> {{ $filters.unitPrice(row.commission) }}</span>
</template>
<template slot-scope="{ row }" slot="action">
<Button
type="success"
size="small"
style="margin-right: 5px"
@click="fenxiao(row)"
>分销商品</Button
>
<Button
type="error"
size="small"
@click="selectGoods(row.id, false)"
>取消选择</Button
>
</el-table-column>
<el-table-column label="操作" min-width="120">
<template #default="{ row }">
<el-button type="success" size="small" style="margin-right: 5px" @click="fenxiao(row)">分销商品</el-button>
<el-button type="danger" size="small" @click="selectGoods(row.id, false)">取消选择</el-button>
</template>
</Table>
</el-table-column>
</el-table>
<div class="page-size">
<Page
:current="params.pageNumber"
<el-pagination v-model:current-page="params.pageNumber"
:total="goodsData.total"
:page-size="params.pageSize"
@on-change="changePage"
size="small"
show-total
show-elevator
></Page>
v-model:page-size="params.pageSize"
:page-sizes="[10, 20, 50]"
@current-change="changePage"
@size-change="changePageSize"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</TabPane>
<TabPane label="未选商品" name="goodsUncheck">
<Table stripe :columns="goodsColumns" :data="goodsData.records">
<template slot-scope="{ row }" slot="name">
</el-tab-pane>
<el-tab-pane label="未选商品" name="goodsUncheck">
<el-table stripe :data="goodsData.records || []">
<el-table-column label="商品名称" width="400">
<template #default="{ row }">
<div
class="goods-msg"
@click="
linkTo(
`/goodsDetail?skuId=${row.skuId}&goodsId=${row.goodsId}`
)
"
@click="linkTo(`/goodsDetail?skuId=${row.skuId}&goodsId=${row.goodsId}`)"
>
<img
style="vertical-align: top"
:src="row.thumbnail"
width="60"
height="60"
alt=""
/>&nbsp; {{ row.goodsName }}
<img style="vertical-align: top" :src="row.thumbnail" width="60" height="60" alt="" />
&nbsp; {{ row.goodsName }}
</div>
</template>
<template slot-scope="{ row }" slot="price">
<span> {{ row.price | unitPrice }}</span>
</el-table-column>
<el-table-column label="商品价格">
<template #default="{ row }">
<span> {{ $filters.unitPrice(row.price) }}</span>
</template>
<template slot-scope="{ row }" slot="commission">
<span> {{ row.commission | unitPrice }}</span>
</el-table-column>
<el-table-column label="佣金">
<template #default="{ row }">
<span> {{ $filters.unitPrice(row.commission) }}</span>
</template>
<template slot-scope="{ row }" slot="action">
<Button
type="primary"
size="small"
style="margin-right: 5px"
@click="selectGoods(row.id, true)"
>选择商品</Button
>
</el-table-column>
<el-table-column label="操作" min-width="120">
<template #default="{ row }">
<el-button type="primary" size="small" style="margin-right: 5px" @click="selectGoods(row.id, true)">选择商品</el-button>
</template>
</Table>
</el-table-column>
</el-table>
<div class="page-size">
<Page
:current="params.pageNumber"
<el-pagination v-model:current-page="params.pageNumber"
:total="goodsData.total"
:page-size="params.pageSize"
@on-change="changePage"
size="small"
show-total
show-elevator
></Page>
v-model:page-size="params.pageSize"
:page-sizes="[10, 20, 50]"
@current-change="changePage"
@size-change="changePageSize"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</TabPane>
<TabPane label="提现记录" name="log">
<Table stripe :columns="logColumns" :data="logData.records">
<template slot-scope="{ row }" slot="sn">
<span>{{ row.sn }}</span>
</template>
<template slot-scope="{ row }" slot="time">
<span>{{ row.createTime }}</span>
</template>
<template slot-scope="{ row }" slot="price">
<span
v-if="row.distributionCashStatus == 'VIA_AUDITING'"
style="color: green"
>
+{{ row.price | unitPrice }}</span
>
</el-tab-pane>
<el-tab-pane label="提现记录" name="log">
<el-table stripe :data="logData.records || []">
<el-table-column prop="sn" label="编号" />
<el-table-column prop="createTime" label="申请时间" />
<el-table-column label="提现金额">
<template #default="{ row }">
<span v-if="row.distributionCashStatus == 'VIA_AUDITING'" style="color: green">
+{{ $filters.unitPrice(row.price) }}
</span>
<span v-else style="color: red">
-{{ row.price | unitPrice }}</span
>
-{{ $filters.unitPrice(row.price) }}
</span>
</template>
<template slot-scope="{ row }" slot="status">
</el-table-column>
<el-table-column label="提现状态">
<template #default="{ row }">
<span>
{{
row.distributionCashStatus == "APPLY"
@@ -199,66 +170,59 @@
: row.distributionCashStatus == "VIA_AUDITING"
? "通过"
: "拒绝"
}}</span
>
}}
</span>
</template>
</Table>
</el-table-column>
</el-table>
<div class="page-size">
<Page
:current="logParams.pageNumber"
<el-pagination v-model:current-page="logParams.pageNumber"
:total="logData.total"
:page-size="logParams.pageSize"
@on-change="changePageLog"
size="small"
show-total
show-elevator
></Page>
v-model:page-size="logParams.pageSize"
:page-sizes="[10, 20, 50]"
@current-change="changePageLog"
@size-change="changePageSizeLog"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
</TabPane>
</Tabs>
</el-tab-pane>
</el-tabs>
</div>
<!-- 未开放 -->
<div v-if="status === 3">
<Alert type="error">
分销功能暂未开启
<template slot="desc"
>提交认证申请后工作人员将在三个工作日进行核对完成审核</template
>
</Alert>
<el-alert type="danger" title="分销功能暂未开启" />
</div>
<!-- 分销资格被清退 -->
<div v-if="status === 4">
<Alert type="error">
<el-alert type="danger">
您的分销资格已被清退请联系管理员或进行申诉
<Button style="margin-left: 50px;" type="warning" @click="repaying">申诉</Button>
</Alert>
<el-button style="margin-left: 50px;" type="warning" @click="repaying">申诉</el-button>
</el-alert>
</div>
<!-- 分销申诉审核 -->
<div v-if="status === 5">
<Alert type="success">
您提交的申诉正在审核
<template slot="desc"
>提交认证申请后工作人员将在三个工作日进行核对完成审核</template
>
</Alert>
<el-alert
type="success"
title="您提交的申诉正在审核"
description="提交认证申请后工作人员将在三个工作日进行核对完成审核"
/>
</div>
<Modal v-model="withdrawApplyModal" width="530">
<p slot="header">
<Icon type="edit"></Icon>
<el-dialog v-model="withdrawApplyModal" width="530">
<template #header><p>
<el-icon><Edit /></el-icon>
<span>提现金额</span>
</p>
</p></template>
<div>
<Input v-model="withdrawPrice" size="large" number maxlength="9"
><span slot="append"></span></Input
<el-input v-model="withdrawPrice" size="large" number maxlength="9"
><template #append><span></span></template></el-input
>
</div>
<div slot="footer" style="text-align: center">
<Button type="primary" size="large" @click="withdraw">提现</Button>
</div>
</Modal>
<Modal v-model="qrcodeShow" title="分销商品" width="800">
<Alert type="warning"> 下载二维码或者复制链接分享商品 </Alert>
<template #footer><div style="text-align: center">
<el-button type="primary" size="large" @click="withdraw">提现</el-button>
</div></template>
</el-dialog>
<el-dialog v-model="qrcodeShow" title="分销商品" width="800">
<el-alert type="warning"> 下载二维码或者复制链接分享商品 </el-alert>
<div class="qrcode">
<div style="width: 150px; height: 150px; border: 1px solid #eee">
<vue-qr
@@ -270,8 +234,8 @@
:size="150"
></vue-qr>
<div class="qrcode-platform">PC端</div>
<Button class="download-btn" type="success" @click="downloadQrcode"
>下载二维码</Button
<el-button class="download-btn" type="success" @click="downloadQrcode"
>下载二维码</el-button
>
</div>
<div style="width: 150px; height: 150px; border: 1px solid #eee">
@@ -284,20 +248,22 @@
:size="150"
></vue-qr>
<div class="qrcode-platform">移动应用端</div>
<Button class="download-btn" type="success" @click="downloadQrcodeH5"
>下载二维码</Button
<el-button class="download-btn" type="success" @click="downloadQrcodeH5"
>下载二维码</el-button
>
</div>
</div>
<div class="mt_10" style="margin-top: 100px;">
商品链接<Input style="width: 600px" v-model="qrcode"></Input>
</div>
</Modal>
商品链接<el-input style="width: 600px" v-model="qrcode"></el-input>
</div></el-dialog>
</UserCenterLayout>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { Edit } from '@element-plus/icons-vue';
import {
distribution,
applyDistribution,
@@ -309,12 +275,13 @@ import {
import { IDCard } from "@/plugins/RegExp.js";
import { checkBankno } from "@/plugins/Foundation";
import vueQr from "vue-qr";
import config from "@/config";
export default {
name: "Distribution",
components: { vueQr },
components: { vueQr, Edit },
data() {
return {
config:require('@/config'),
config,
status: 0, // 申请状态0为未申请 1 申请中 2 申请完成 3 功能暂未开启
applyForm: {}, // 申请表单
rules: {
@@ -358,35 +325,21 @@ export default {
withdrawPrice: 0, // 提现金额
goodsData: {}, // 商品数据
logData: {}, // 日志数据
goodsColumns: [
// 商品表头
{ title: "商品名称", slot: "name", width: 400 },
{ title: "商品价格", slot: "price" },
{ title: "佣金", slot: "commission" },
{ title: "操作", slot: "action", minWidth: 120 },
],
logColumns: [
// 日志表头
{ title: "编号", slot: "sn" },
{ title: "申请时间", slot: "time" },
{ title: "提现金额", slot: "price" },
{ title: "提现状态", slot: "status" },
],
params: {
// 商品请求参数
pageNumber: 1,
pageSize: 20,
pageSize: 10,
checked: true,
},
orderParams: {
// 订单商品请求参数
pageNumber: 1,
pageSize: 20,
pageSize: 10,
},
logParams: {
// 日志参数
pageNumber: 1,
pageSize: 20,
pageSize: 10,
sort: "createTime",
order: "desc",
},
@@ -410,25 +363,43 @@ export default {
applyDistribution(this.applyForm).then((res) => {
this.applyLoading = false;
if (res.success) {
this.$Message.success("申请已提交,请等待管理员审核");
Message.success("申请已提交,请等待管理员审核");
this.status = 1;
}
});
}
});
},
normalizeWithdrawError (message) {
if (!message) return '提现失败,请稍后重试';
const text = String(message);
if (text.includes('最少提现金额为1元')) {
return '提现金额单次最少提现金额为1元';
}
const colonIndex = text.indexOf(':');
if (colonIndex > -1 && colonIndex < text.length - 1) {
return text.slice(colonIndex + 1).trim();
}
return text;
},
withdraw() {
// 申请提现
const price = Number(this.withdrawPrice);
if (!price || price < 1) {
Message.error('提现金额单次最少提现金额为1元');
return;
}
distCash({ price: this.withdrawPrice }).then((res) => {
this.withdrawApplyModal = false;
this.price = 0;
if (res.success) {
this.$Message.success("申请已提交,请等待审核");
this.withdrawApplyModal = false;
this.withdrawPrice = 0;
Message.success('申请已提交,请等待审核');
this.distribution();
this.getLog();
} else {
this.$Message.error(res.message);
Message.error(this.normalizeWithdrawError(res.message));
}
}).catch((err) => {
Message.error(this.normalizeWithdrawError(err?.message || err?.data?.message));
});
},
qrcodeData(data64) {
@@ -474,11 +445,23 @@ export default {
this.params.pageNumber = val;
this.getGoodsData();
},
changePageSize(val) {
// 修改每页条数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getGoodsData();
},
changePageLog(val) {
// 修改页码 日志
this.logParams.pageNumber = val;
this.getLog();
},
changePageSizeLog(val) {
// 修改每页条数 日志
this.logParams.pageNumber = 1;
this.logParams.pageSize = val;
this.getLog();
},
selectGoods(id, checked) {
// 选择商品
let params = {
@@ -487,7 +470,7 @@ export default {
};
selectDistGoods(params).then((res) => {
if (res.success) {
this.$Message.success("操作成功!");
Message.success("操作成功!");
this.getGoodsData();
}
});
@@ -516,17 +499,18 @@ export default {
applyDistribution().then((res) => {
this.applyLoading = false;
if (res.success) {
this.$Message.success("申诉已提交,请等待管理员审核");
Message.success("申诉已提交,请等待管理员审核");
// this.status = 1;
}
});
},
distribution() {
// 获取分销商信息
distribution().then((res) => {
if (res.result) {
distribution()
.then((res) => {
if (res?.result) {
this.result = res.result;
let type = res.result.distributionStatus;
const type = res.result.distributionStatus;
if (type === "PASS") {
this.status = 2;
this.getGoodsData();
@@ -539,12 +523,18 @@ export default {
} else {
this.status = 1;
}
} else if (!res.data.success && res.data.code === 22000) {
} else if (res?.code === 22000) {
// 分销功能未开启
this.status = 3;
} else {
// 没有资格申请 先去实名认证
// 没有资格申请,显示申请表单
this.status = 0;
}
})
.catch((err) => {
if (err?.code === 22000) {
this.status = 3;
}
});
},
},
@@ -556,8 +546,9 @@ export default {
margin: 20px 0;
}
.page-size {
display: flex;
justify-content: flex-end;
margin: 15px 0px;
text-align: right;
}
.account-price {
font-weight: bold;
@@ -584,7 +575,7 @@ export default {
margin-left: 25px;
margin-top: 5px
}
::v-deep .ivu-alert-message {
:deep(.el-alert__content) {
p {
margin: 4px 0;
}

View File

@@ -1,8 +1,12 @@
<template>
<div class="wrapper">
<card _Title="近期收藏" :_Tabs="favoriteWay" @_Change="change" :_Size="16" v-if="!homePage" />
<card _Title="近期收藏" :_Size="16" :_Tabs="favoriteWay" @_Change="change" _More="全部收藏" _Src="/home/Favorites" v-else>
</card>
<UserCenterLayout
v-if="!homePage"
title="近期收藏"
:tabs="favoriteWay"
:active-tab="activeTabIndex"
@tab-change="change"
>
<div v-if="list.length">
<div v-for="(item, index) in list" :key="index">
<div class="goodsItem" :key="item.skuId">
@@ -17,28 +21,65 @@
</div>
<div v-else class="goodsTitle hover-pointer" @click="buynow(item.skuId, item.goodsId,item.id)" >
{{ item.storeName }}
<Tag color="error" class="operated" v-if="item.selfOperated">商家自营</Tag>
<el-tag color="error" class="operated" v-if="item.selfOperated">商家自营</el-tag>
</div>
<div class="goodsPrice">
<span v-if="params.type === 'GOODS'">{{ item.price | unitPrice('') }}</span>
<span v-if="params.type === 'GOODS'">{{ $filters.unitPrice(item.price, '') }}</span>
</div>
<div class="goodsBuy">
<Button size="small" type="primary" @click="buynow(item.skuId, item.goodsId)"
v-if="params.type === 'GOODS'">立即购买</Button>
<Button size="small" type="primary" @click="goShop(item.id)" v-else>店铺购买</Button>
<Button size="small" v-if="params.type === 'GOODS'" @click="cancel(item.skuId)">取消收藏</Button>
<Button size="small" v-if="params.type === 'STORE'" @click="cancelStore(item.id)">取消收藏</Button>
<el-button size="small" type="primary" @click="buynow(item.skuId, item.goodsId)"
v-if="params.type === 'GOODS'">立即购买</el-button>
<el-button size="small" type="primary" @click="goShop(item.id)" v-else>店铺购买</el-button>
<el-button size="small" v-if="params.type === 'GOODS'" @click="cancel(item.skuId)">取消收藏</el-button>
<el-button size="small" v-if="params.type === 'STORE'" @click="cancelStore(item.id)">取消收藏</el-button>
</div>
</div>
</div>
<Spin size="large" fix v-if="spinShow"></Spin>
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
</div>
<empty v-else />
</UserCenterLayout>
<template v-else>
<card _Title="近期收藏" :_Size="16" :_Tabs="favoriteWay" :_ActiveTab="activeTabIndex" @_Change="change" _More="全部收藏" _Src="/home/Favorites"></card>
<div v-if="list.length">
<div v-for="(item, index) in list" :key="index">
<div class="goodsItem" :key="item.skuId">
<div class="goodsImg hover-pointer" v-if="params.type === 'GOODS'">
<img :src="item.image" />
</div>
<div class="goodsImg hover-pointer" v-else>
<img :src="item.storeLogo" />
</div>
<div class="goodsTitle hover-color" v-if="params.type === 'GOODS'" @click="buynow(item.skuId, item.goodsId,item.id)">
{{ item.goodsName }}
</div>
<div v-else class="goodsTitle hover-pointer" @click="buynow(item.skuId, item.goodsId,item.id)" >
{{ item.storeName }}
<el-tag color="error" class="operated" v-if="item.selfOperated">商家自营</el-tag>
</div>
<div class="goodsPrice">
<span v-if="params.type === 'GOODS'">{{ $filters.unitPrice(item.price, '') }}</span>
</div>
<div class="goodsBuy">
<el-button size="small" type="primary" @click="buynow(item.skuId, item.goodsId)"
v-if="params.type === 'GOODS'">立即购买</el-button>
<el-button size="small" type="primary" @click="goShop(item.id)" v-else>店铺购买</el-button>
<el-button size="small" v-if="params.type === 'GOODS'" @click="cancel(item.skuId)">取消收藏</el-button>
<el-button size="small" v-if="params.type === 'STORE'" @click="cancelStore(item.id)">取消收藏</el-button>
</div>
</div>
</div>
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
</div>
<empty v-else />
</template>
</div>
</template>
<script>
import { Modal } from "@/utils/message";
import { collectList, cancelCollect, storeCollectList, cancelStoreCollect } from '@/api/member.js'
export default {
name: 'Favorites',
@@ -61,20 +102,42 @@ export default {
spinShow: false // 加载状态
};
},
computed: {
activeTabIndex() {
return this.params.type === 'STORE' ? 1 : 0;
},
},
methods: {
getList() { // 获取收藏列表
loadFavorites() {
if (this.params.type === 'STORE') {
this.getStoreList();
} else {
this.getList();
}
},
getList() { // 获取商品收藏列表
this.spinShow = true
collectList(this.params).then(res => {
if (res.success && res.result) {
this.list = res.result.records || [];
}
}).catch(() => {
this.list = [];
}).finally(() => {
this.spinShow = false
if (res.success) this.list = res.result.records;
})
},
getStoreList() { // 获取收藏列表
getStoreList() { // 获取店铺收藏列表
this.spinShow = true
storeCollectList(this.params).then(res => {
if (res.success && res.result) {
this.list = res.result.records || [];
}
}).catch(() => {
this.list = [];
}).finally(() => {
this.spinShow = false
if (res.success) this.list = res.result.records;
})
},
change(index) { // tab栏切换
@@ -83,9 +146,9 @@ export default {
},
cancel(id) { // 取消收藏
let typeName = this.params.type === 'GOODS' ? '商品' : '店铺'
this.$Modal.confirm({
title: 'Title',
content: `<p>确定取消收藏该${typeName}吗?</p>`,
Modal.confirm({
title: '取消收藏',
content: `确定取消收藏该${typeName}吗?`,
onOk: () => {
cancelCollect(this.params.type, id).then(res => {
if (res.success) {
@@ -97,9 +160,9 @@ export default {
},
cancelStore(id) { // 取消收藏
let typeName = this.params.type === 'GOODS' ? '商品' : '店铺'
this.$Modal.confirm({
title: 'Title',
content: `<p>确定取消收藏该${typeName}吗?</p>`,
Modal.confirm({
title: '取消收藏',
content: `确定取消收藏该${typeName}吗?`,
onOk: () => {
cancelStoreCollect(this.params.type, id).then(res => {
if (res.success) {
@@ -137,8 +200,7 @@ export default {
},
mounted() {
if (this.homePage) this.params.pageSize = 5;
this.getList()
this.loadFavorites();
}
};
</script>
@@ -162,14 +224,15 @@ export default {
.goodsItem {
display: flex;
align-items: center;
height: 60px;
line-height: 60px;
margin-bottom: 10px;
>.goodsImg {
width: 60px;
height: 60px;
overflow: hidden;
flex-shrink: 0;
>img {
width: 100%;
@@ -179,12 +242,15 @@ export default {
>.goodsPrice,
.goodsShop {
width: 150px;
width: 120px;
flex-shrink: 0;
text-align: center;
}
>.goodsTitle {
width: 400px;
flex: 1;
min-width: 0;
line-height: 60px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -192,10 +258,17 @@ export default {
}
.goodsBuy {
margin-left: 80px;
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
gap: 8px;
width: 170px;
margin-left: 16px;
line-height: normal;
>* {
margin: 0 4px;
:deep(.el-button) {
margin: 0;
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="memberGrade">
<card _Title="我的等级"/>
<UserCenterLayout title="我的等级" :tabs="['我的等级']">
<div class="summary">
<div class="gradeCard">
<div class="gradeLeft">
@@ -9,7 +9,7 @@
<div class="gradeNameRow">
<span class="gradeNameText">{{ currentGrade.gradeName || '暂无等级' }}</span>
<span v-if="currentGradeBenefits.length" class="benefitTagsInline">
<Tag v-for="b in currentGradeBenefits" :key="b.id" class="benefitTag" color="blue">{{ b.benefitName || '-' }}</Tag>
<el-tag v-for="b in currentGradeBenefits" :key="b.id" class="benefitTag" type="primary">{{ b.benefitName || '-' }}</el-tag>
</span>
</div>
<div class="gradeExp">
@@ -24,17 +24,18 @@
</div>
</div>
<Tabs value="rules">
<TabPane label="获取经验值方式" name="rules">
<Table :columns="ruleColumns" :data="ruleList" :loading="rulesLoading">
<template slot-scope="{ row }" slot="ruleItem">
<el-tabs v-model="activeGradeTab">
<el-tab-pane label="获取经验值方式" name="rules">
<el-table v-loading="rulesLoading" :data="ruleList">
<el-table-column label="获取方式" min-width="720">
<template #default="{ row }">
<div class="ruleItem">
<div class="ruleTop">
<div class="ruleName">
<span>{{ row.ruleName || row.ruleKey || '-' }}</span>
</div>
<Tag v-if="row.completed === true" color="success">已完成</Tag>
<Tag v-else color="default">未完成</Tag>
<el-tag v-if="row.completed === true" color="success">已完成</el-tag>
<el-tag v-else color="default">未完成</el-tag>
</div>
<div class="ruleDesc">{{ getRuleDesc(row.ruleKey) }}</div>
<div class="ruleMeta">
@@ -44,34 +45,40 @@
</div>
</div>
</template>
</Table>
</el-table-column>
</el-table>
<div class="rulesDesc" v-if="rulesDescription">{{ rulesDescription }}</div>
</TabPane>
<TabPane label="经验值记录" name="logs">
<Table :columns="logColumns" :data="experienceLogs.records || []" :loading="logLoading">
<template slot-scope="{ row }" slot="rule">
</el-tab-pane>
<el-tab-pane label="经验值记录" name="logs">
<el-table v-loading="logLoading" :data="experienceLogs.records || []">
<el-table-column prop="createTime" label="时间" align="center" min-width="160" />
<el-table-column label="规则" align="center" min-width="160">
<template #default="{ row }">
<span>{{ getRuleName(row.ruleKey) }}</span>
</template>
<template slot-scope="{ row }" slot="delta">
</el-table-column>
<el-table-column label="变动值" align="center" min-width="120">
<template #default="{ row }">
<div :style="{ color: getLogDeltaColor(row) }">
<span v-if="getLogDeltaSign(row) === '+'">+</span>{{ getLogDeltaAbsValue(row) }}
</div>
</template>
</Table>
<Page
style="float:right;margin-top:10px"
:current="params.pageNumber"
</el-table-column>
</el-table>
<div class="grade-pagination">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="experienceLogs.total || 0"
:page-size="params.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
size="small"
show-total
show-elevator
></Page>
</TabPane>
</Tabs>
@current-change="changePage"
@size-change="changePageSize"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next, jumper"
/>
</div>
</el-tab-pane>
</el-tabs>
</UserCenterLayout>
</div>
@@ -94,40 +101,14 @@ export default {
currentExperience: 0,
currentGradeId: '',
selectedRuleKey: '',
activeGradeTab: 'rules',
logLoading: false,
gradeLoading: false,
params: {
pageNumber: 1,
pageSize: 10
},
experienceLogs: {},
ruleColumns: [
{
title: '获取方式',
slot: 'ruleItem',
minWidth: 720
}
],
logColumns: [
{
title: '时间',
key: 'createTime',
align: 'center',
minWidth: 160
},
{
title: '规则',
slot: 'rule',
align: 'center',
minWidth: 160
},
{
title: '变动值',
slot: 'delta',
align: 'center',
minWidth: 120
}
]
experienceLogs: {}
}
},
computed: {
@@ -428,4 +409,10 @@ h3 {
font-weight: 600;
}
.grade-pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
</style>

View File

@@ -1,78 +1,81 @@
<template>
<div class="wrapper">
<card _Title="账户安全"/>
<UserCenterLayout title="账户安全" :tabs="['账户安全']">
<!-- 手机号验证 -->
<Form ref="formData" :model="formData" label-position="left" :label-width="100" :rules="ruleInLines"
<el-form ref="formData" :model="formData" label-position="left" label-width="100px" :rules="ruleInLines"
v-if="(status == 2 || status == 3) && !showPwd">
<FormItem label="手机号">
<el-form-item label="手机号">
<div class="phone">1234567890</div>
</FormItem>
<FormItem label="图片验证码" prop="picture" style="width: 350px">
<i-input v-model="formData.picture" size="large" maxlength="4"></i-input>
</FormItem>
<FormItem label="短信验证码" prop="msg">
<i-input v-model="formData.msg" size="large" maxlength="6" style="width: 250px">
<span slot="append">输入验证码</span>
</i-input>
</FormItem>
</Form>
<div slot="footer" v-if="(status == 2 || status == 3) && !showPwd" style="text-align: center;width: 50%">
<Button type="primary" class="ml_10" @click="submitRegister">下一步</Button>
</el-form-item>
<el-form-item label="图片验证码" prop="picture" style="width: 350px">
<el-input v-model="formData.picture" size="large" maxlength="4"></el-input>
</el-form-item>
<el-form-item label="短信验证码" prop="msg">
<el-input v-model="formData.msg" size="large" maxlength="6" style="width: 250px">
<template #append><span>输入验证码</span></template>
</el-input>
</el-form-item>
</el-form>
<div v-if="(status == 2 || status == 3) && !showPwd" style="text-align: center;width: 50%">
<el-button type="primary" class="ml_10" @click="submitRegister">下一步</el-button>
</div>
<!-- 修改 -->
<Form ref="formRegister" :model="formRegister" :rules="ruleInline" :label-width="80" v-if="status == 1 && !showPwd">
<FormItem label="旧密码" prop="password">
<i-input type="password" v-model="formRegister.password" clearable size="large" placeholder="请输入旧密码"
<el-form ref="formRegister" :model="formRegister" :rules="ruleInline" label-width="80px" v-if="status == 1 && !showPwd">
<el-form-item label="旧密码" prop="password">
<el-input type="password" v-model="formRegister.password" clearable size="large" placeholder="请输入旧密码"
style="width: 300px"
maxlength="12">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem label="新密码" prop="newPassword">
<i-input type="password" v-model="formRegister.newPassword" clearable size="large" placeholder="请输入新密码"
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input type="password" v-model="formRegister.newPassword" clearable size="large" placeholder="请输入新密码"
style="width: 300px"
maxlength="12">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem label="确认密码" prop="againPassword">
<i-input type="password" v-model="formRegister.againPassword" clearable size="large" placeholder="请输入确认密码"
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item label="确认密码" prop="againPassword">
<el-input type="password" v-model="formRegister.againPassword" clearable size="large" placeholder="请输入确认密码"
style="width: 300px"
maxlength="12">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
</Form>
<div slot="footer" v-if="status == 1 && !showPwd" style="width: 50%;text-align: center">
<Button type="primary" class="ml_10" @click="handleRegist">修改</Button>
</el-form>
<div v-if="status == 1 && !showPwd" style="width: 50%;text-align: center">
<el-button type="primary" class="ml_10" @click="handleRegist">修改</el-button>
</div>
<!-- 设置&修改的第二种情况 -->
<Form ref="formDataUpdate" :model="formDataUpdate" label-position="left" :label-width="100" :rules="ruleIn"
<el-form ref="formDataUpdate" :model="formDataUpdate" label-position="left" label-width="100px" :rules="ruleIn"
v-if="showPwd">
<FormItem label="新密码" prop="newPassword">
<i-input type="password" v-model="formDataUpdate.newPassword" clearable size="large" placeholder="请输入新密码"
<el-form-item label="新密码" prop="newPassword">
<el-input type="password" v-model="formDataUpdate.newPassword" clearable size="large" placeholder="请输入新密码"
style="width: 300px"
maxlength="12">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
<FormItem label="确认密码" prop="againPassword">
<i-input type="password" v-model="formDataUpdate.againPassword" clearable size="large" placeholder="请输入确认密码"
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item label="确认密码" prop="againPassword">
<el-input type="password" v-model="formDataUpdate.againPassword" clearable size="large" placeholder="请输入确认密码"
style="width: 300px"
maxlength="12">
<Icon type="md-lock" slot="prepend"></Icon>
</i-input>
</FormItem>
</Form>
<div slot="footer" v-if="showPwd" style="text-align: center;width: 50%">
<Button type="primary" class="ml_10" @click="setPassword">设置</Button>
<template #prepend><el-icon><Lock /></el-icon></template>
</el-input>
</el-form-item>
</el-form>
<div v-if="showPwd" style="text-align: center;width: 50%">
<el-button type="primary" class="ml_10" @click="setPassword">设置</el-button>
</div>
</UserCenterLayout>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { Lock } from '@element-plus/icons-vue';
import {
setPwd,
editPwd
@@ -80,6 +83,7 @@ import {
import {md5} from '@/plugins/md5.js'
export default {
components: { Lock },
name: 'modifyPwd',
data () {
return {
@@ -136,14 +140,21 @@ export default {
this.status = this.$route.query.status
},
methods: {
normalizePwdError (message) {
if (!message) return '修改失败,请稍后重试';
if (String(message).includes('旧密码') || String(message).includes('密码不正确')) {
return '旧密码不正确';
}
return message;
},
// 修改
handleRegist () {
this.$refs['formRegister'].validate((valid) => {
if (valid) {
const {newPassword, againPassword, password} = this.formRegister
if (newPassword !== againPassword) {
this.$Message.error({
content: '新密码不一致'
Message.error({
content: '新密码和确认密码不一致'
});
return
}
@@ -151,10 +162,14 @@ export default {
params.newPassword = md5(newPassword)
params.password = md5(password)
editPwd(params).then(res => {
if (res.message === 'success' && res.result) {
this.$Message.success('修改密码成功');
if (res.success && res.result) {
Message.success('修改密码成功');
this.$router.push('/home')
} else {
Message.error(this.normalizePwdError(res.message));
}
}).catch((err) => {
Message.error(this.normalizePwdError(err?.message || err?.data?.message));
});
}
})
@@ -173,14 +188,14 @@ export default {
if (valid) {
const {newPassword, againPassword} = this.formDataUpdate
if (newPassword === '') {
this.$Message.error({
Message.error({
content: '请输入密码'
});
return
}
if (newPassword !== againPassword) {
this.$Message.error({
content: '新密码不一致'
Message.error({
content: '新密码和确认密码不一致'
});
return
}
@@ -188,7 +203,7 @@ export default {
params.password = md5(newPassword)
setPwd(params).then(res => {
if (res.message === 'success' && res.result) {
this.$Message.success('支付密码设置成功');
Message.success('支付密码设置成功');
this.$router.push('/home')
}
});

View File

@@ -1,31 +1,35 @@
<template>
<div class="point">
<card _Title="我的积分"/>
<UserCenterLayout title="我的积分" :tabs="['我的积分']">
<div class="point-content">
<span>剩余积分<span>{{ pointObj.point || 0 }}</span></span>
<span>累计获得<span>{{ pointObj.totalPoint || 0 }}</span></span>
</div>
<h3>积分日志</h3>
<Table :columns="logColumns" :data="logData.records">
<template slot-scope="{ row }" slot="point">
<el-table :data="logData.records || []">
<el-table-column prop="content" label="日志内容" align="center" />
<el-table-column prop="createTime" label="时间" align="center" />
<el-table-column label="积分明细" align="center">
<template #default="{ row }">
<div :style="{ color: row.pointType === 'INCREASE' ? 'green' : 'red' }">
<span v-if="row.pointType === 'INCREASE'">+</span>{{ row.variablePoint }}
</div>
</template>
</Table>
</el-table-column>
</el-table>
<!-- 分页 -->
<Page
style="float:right;margin-top:10px"
:current="params.pageNumber"
<div class="point-pagination">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="logData.total"
:page-size="params.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
size="small"
show-total
show-elevator
></Page>
@current-change="changePage"
@size-change="changePageSize"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next, jumper"
/>
</div>
</UserCenterLayout>
</div>
</template>
<script>
@@ -42,23 +46,6 @@ export default {
pageNumber: 1,
pageSize: 10
},
logColumns: [ // table展示数据
{
title: '日志内容',
key: 'content',
align: 'center'
},
{
title: '时间',
key: 'createTime',
align: 'center'
},
{
title: '积分明细',
slot: 'point',
align: 'center'
}
]
}
},
mounted () {
@@ -108,4 +95,10 @@ h3 {
}
}
}
.point-pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
</style>

View File

@@ -1,46 +1,57 @@
<template>
<div class="wrapper">
<card _Title="用户信息" />
<Form :model="formItem" :rules="rules" ref="form" :label-width="80">
<FormItem label="头像">
<Avatar v-if="formItem.face" :src="formItem.face" size="80"/>
<Avatar v-else icon="ios-person" size="80"/>
<Upload
:show-upload-list="false"
<UserCenterLayout title="用户信息" :tabs="['用户信息']">
<el-form :model="formItem" :rules="rules" ref="form" label-width="80px">
<el-form-item label="头像" class="avatar-form-item">
<div class="avatar-upload">
<el-avatar v-if="formItem.face" :src="formItem.face" :size="80" />
<el-avatar v-else :size="80"><el-icon><User /></el-icon></el-avatar>
<el-upload
:show-file-list="false"
:on-success="handleSuccess"
:format="['jpg','jpeg','png']"
:action="action"
:headers="accessToken"
>
<Button class="mt_10">上传头像</Button>
</Upload>
</FormItem>
<FormItem label="昵称" prop="nickName">
<Input class="wrapper-user-name" style="width:187px" v-model="formItem.nickName" placeholder="" />
</FormItem>
<el-button>上传头像</el-button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input class="profile-field" v-model="formItem.nickName" placeholder="" />
</el-form-item>
<FormItem label="生日">
<DatePicker type="date" placeholder="选择您的生日" v-model="formItem.birthday"></DatePicker>
</FormItem>
<FormItem label="性别">
<RadioGroup v-model="formItem.sex" type="button" button-style="solid">
<Radio :label="1"></Radio>
<Radio :label="0"></Radio>
</RadioGroup>
</FormItem>
<FormItem>
<Button type="primary" @click="save">确认修改</Button>
<el-form-item label="生日">
<el-date-picker
class="profile-field"
type="date"
placeholder="选择您的生日"
v-model="formItem.birthday"
/>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="formItem.sex" type="button" button-style="solid">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">确认修改</el-button>
</FormItem>
</Form>
</el-form-item>
</el-form>
</UserCenterLayout>
</div>
</template>
<script>
import { Message } from "@/utils/message";
import { editMemberInfo } from '@/api/account.js';
import { commonUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage.js';
import { User } from '@element-plus/icons-vue';
export default {
components: { User },
name: 'Profile',
data () {
return {
@@ -94,7 +105,7 @@ export default {
}
editMemberInfo(params).then(res => {
if (res.success) {
this.$Message.success('修改个人资料成功')
Message.success('修改个人资料成功')
storage.setItem('userInfo', res.result)
this.$router.go(0)
}
@@ -103,7 +114,7 @@ export default {
})
},
handleSuccess (res, file) { // 上传成功
this.$set(this.formItem, 'face', res.result)
this.formItem['face'] = res.result
},
}
@@ -111,5 +122,35 @@ export default {
</script>
<style scoped lang="scss">
.avatar-form-item {
:deep(.el-form-item__label) {
height: 90px;
line-height: 90px;
display: flex;
align-items: center;
}
}
.avatar-upload {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
:deep(.el-avatar) {
margin-top: 10px;
}
:deep(.el-upload) {
display: block;
}
}
.profile-field {
width: 187px;
}
:deep(.profile-field.el-date-editor) {
width: 187px;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="add-eval">
<UserCenterLayout title="订单评价" :tabs="['订单评价']">
<div class="title">
<card _Title="订单评价" :_Size="16"></card>
<p>
<span class="color999">订单号</span><span>{{$route.query.sn}}</span>
<span class="color999 ml_20" v-if="order.order">{{order.order.paymentTime}}</span>
@@ -10,10 +10,10 @@
<!-- 物流评分服务评分 -->
<div class="delivery-rate">
<div class="fontsize_16">物流服务评价</div>
<div class="color999">
<span>物流评价<Rate v-model="form.deliveryScore" /></span>
<span>服务评价<Rate v-model="form.serviceScore" /></span>
<span>描述评价<Rate v-model="form.descriptionScore" /></span>
<div class="color999 rate-list">
<span class="rate-item">物流评价<el-rate v-model="form.deliveryScore" /></span>
<span class="rate-item">服务评价<el-rate v-model="form.serviceScore" /></span>
<span class="rate-item">描述评价<el-rate v-model="form.descriptionScore" /></span>
</div>
</div>
<!-- 添加订单评价 左侧商品详情 右侧评价框 -->
@@ -22,55 +22,67 @@
<div class="goods-con">
<img :src="orderGoods.image" class="hover-pointer" alt="" width="100" @click="goGoodsDetail(orderGoods.skuId, orderGoods.goodsId)">
<p class="hover-pointer color999" @click="goGoodsDetail(orderGoods.skuId, orderGoods.goodsId)">{{orderGoods.goodsName}}</p>
<p>{{orderGoods.goodsPrice | unitPrice('¥')}}</p>
<p>{{ $filters.unitPrice(orderGoods.goodsPrice, '¥') }}</p>
</div>
<div class="eval-con">
<div>
<span class="color999">商品评价</span>
<RadioGroup style="margin-bottom:5px;color:#999" v-model="orderGoods.grade" type="button" button-style="solid">
<Radio label="GOOD">好评</Radio>
<Radio label="MODERATE">中评</Radio>
<Radio label="WORSE">差评</Radio>
</RadioGroup>
<Input type="textarea" maxlength="500" show-word-limit :rows="4" v-model="orderGoods.content" />
<div class="goods-grade-row">
<span class="color999 goods-grade-label">商品评价</span>
<el-radio-group v-model="orderGoods.grade">
<el-radio-button value="GOOD">好评</el-radio-button>
<el-radio-button value="MODERATE">中评</el-radio-button>
<el-radio-button value="WORSE">差评</el-radio-button>
</el-radio-group>
</div>
<div style="display:flex;align-items:center;">
<div>
<el-input type="textarea" maxlength="500" show-word-limit :rows="4" v-model="orderGoods.content" />
</div>
<div class="upload-section">
<div class="upload-row">
<div class="demo-upload-list" v-for="(img, index) in orderGoods.uploadList" :key="index">
<img :src="img">
<div class="demo-upload-list-cover">
<Icon type="ios-eye-outline" @click.native="handleView(img)"></Icon>
<Icon type="ios-trash-outline" @click.native="handleRemove(index)"></Icon>
<el-icon @click="handleView(img)"><View /></el-icon>
<el-icon @click="handleRemove(index)"><Delete /></el-icon>
</div>
</div>
<Upload
:show-upload-list="false"
<el-upload
class="upload-trigger"
:show-file-list="false"
:on-success="handleSuccess"
:before-upload="handleBeforeUpload"
:format="['jpg','jpeg','png']"
:action="action"
:headers="accessToken"
style="display: inline-block;width:58px;">
<div class="hover-pointer icon-upload" style="">
<Icon type="ios-camera" size="20"></Icon>
>
<div class="hover-pointer icon-upload">
<el-icon :size="20"><Camera /></el-icon>
</div>
</Upload>
</el-upload>
</div>
<div class="describe">上传评价图片最多9张</div>
</div>
</div>
</li>
</ul>
<Button type="primary" class="mt_10" :loading="loading" @click="save">发表</Button>
<Modal title="View Image" v-model="visible">
<div class="submit-row">
<el-button type="primary" :loading="loading" @click="save">发表</el-button>
</div>
<el-dialog title="View Image" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%">
</Modal>
</el-dialog>
</UserCenterLayout>
</div>
</template>
<script>
import { Message, Notice } from "@/utils/message";
import { Camera, Delete, View } from '@element-plus/icons-vue';
import { orderDetail } from '@/api/order.js';
import { addEvaluation } from '@/api/member.js';
import { commonUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage';
export default {
components: { Camera, Delete, View },
data () {
return {
order: {}, // 订单详情
@@ -92,18 +104,24 @@ export default {
orderDetail(this.$route.query.sn).then(res => {
this.order = res.result
this.orderGoods = res.result.orderItems[this.$route.query.index]
this.$set(this.orderGoods, 'grade', 'GOOD')
this.orderGoods['grade'] = 'GOOD'
this.orderGoods.uploadList = []
})
},
save () { // 保存评价
if (!this.form.serviceScore || !this.form.deliveryScore) {
this.$Message.warning('物流服务评价不能为空')
Message.warning('物流服务评价不能为空')
return false;
}
if (!this.form.descriptionScore) {
this.$Message.warning('描述评价不能为空')
Message.warning('描述评价不能为空')
return false;
}
if (!this.orderGoods.content || !String(this.orderGoods.content).trim()) {
Message.warning('评论内容不能为空')
return false;
}
this.loading = true;
@@ -116,17 +134,20 @@ export default {
serviceScore: this.form.serviceScore,
deliveryScore: this.form.deliveryScore,
grade: goods.grade,
content: goods.content || '',
images: goods.uploadList.toString()
content: goods.content.trim(),
images: goods.uploadList.length ? goods.uploadList.join(',') : ''
}
addEvaluation(params).then(res => {
this.loading = false
if (res.success) {
this.$Message.success('评价成功')
Message.success('评价成功')
this.$router.push('/home/CommentList')
} else {
Message.error(res.message || '评价失败,请稍后重试')
}
}).catch(() => {
}).catch((err) => {
this.loading = false;
Message.error(err?.message || '评价失败,请稍后重试')
})
},
goGoodsDetail (skuId, goodsId) { // 跳转商品详情
@@ -149,9 +170,9 @@ export default {
this.$forceUpdate()
},
handleBeforeUpload () { // 上传之前钩子
const check = this.orderGoods.uploadList.length < 10;
const check = this.orderGoods.uploadList.length < 9;
if (!check) {
this.$Notice.warning({
Notice.warning({
title: '最多可以上传九张图片'
});
return check;
@@ -177,6 +198,16 @@ export default {
font-weight: bold;
}
}
.rate-list {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 18px;
}
.rate-item {
display: inline-flex;
align-items: center;
}
.goods-eval li{
display: flex;
border-bottom: 1px solid #eee;
@@ -195,9 +226,23 @@ export default {
padding: 20px;
}
}
.goods-grade-row {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.goods-grade-label {
flex-shrink: 0;
}
.submit-row {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.demo-upload-list{
display: inline-block;
display: block;
width: 60px;
height: 60px;
text-align: center;
@@ -208,8 +253,7 @@ export default {
background: #fff;
position: relative;
box-shadow: 0 1px 1px rgba(0,0,0,.2);
margin-right: 4px;
margin-top: 10px;
flex-shrink: 0;
}
.demo-upload-list img{
width: 100%;
@@ -234,17 +278,47 @@ export default {
margin: 0 2px;
}
.icon-upload {
width: 58px;
height:58px;
line-height: 58px;
width: 60px;
height: 60px;
line-height: 60px;
text-align: center;
display: inline-block;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #999;
border-radius: 4px;
margin-top: 10px;
box-sizing: border-box;
&:hover{
cursor: pointer;
border-color: $theme_color;
}
}
.upload-section {
margin-top: 10px;
}
.upload-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
}
.upload-trigger {
display: inline-flex;
align-items: center;
flex-shrink: 0;
:deep(.el-upload) {
display: inline-flex;
align-items: center;
}
}
.describe {
font-size: 12px;
color: #999;
margin-top: 8px;
}
</style>

View File

@@ -1,22 +1,19 @@
<template>
<div class="add-eval">
<UserCenterLayout title="评价详情" :tabs="['评价详情']">
<div class="title">
<card _Title="评价详情" :_Size="16"></card>
<p>
<span class="color999">创建人</span
><span>{{ orderGoods.createBy | secrecyMobile }}</span>
<span class="color999">创建人</span><span>{{ $filters.secrecyMobile( orderGoods.createBy ) }}</span>
<span class="color999 ml_20">{{ orderGoods.createTime }}</span>
</p>
</div>
<!-- 物流评分服务评分 -->
<div class="delivery-rate">
<div class="fontsize_16">物流服务评价</div>
<div class="color999">
<span>物流评价<Rate disabled :value="Number(orderGoods.deliveryScore)" /></span>
<span>服务评价<Rate disabled :value="Number(orderGoods.serviceScore)" /></span>
<span
>服务评价<Rate disabled :value="Number(orderGoods.descriptionScore)"
/></span>
<div class="color999 delivery-rate-items">
<span>物流评价<el-rate disabled :model-value="Number(orderGoods.deliveryScore) || 0" /></span>
<span>服务评价<el-rate disabled :model-value="Number(orderGoods.serviceScore) || 0" /></span>
<span>描述评价<el-rate disabled :model-value="Number(orderGoods.descriptionScore) || 0" /></span>
</div>
</div>
<!-- 添加订单评价 左侧商品详情 右侧评价框 -->
@@ -41,17 +38,17 @@
<div class="eval-con">
<div>
<span class="color999">商品评价</span>
<RadioGroup
<el-radio-group
style="margin-bottom: 5px; color: #999"
v-model="orderGoods.grade"
type="button"
button-style="solid"
>
<Radio label="GOOD" disabled>好评</Radio>
<Radio label="MODERATE" disabled>中评</Radio>
<Radio label="WORSE" disabled>差评</Radio>
</RadioGroup>
<Input
<el-radio label="GOOD" disabled>好评</el-radio>
<el-radio label="MODERATE" disabled>中评</el-radio>
<el-radio label="WORSE" disabled>差评</el-radio>
</el-radio-group>
<el-input
type="textarea"
maxlength="500"
readonly
@@ -64,7 +61,7 @@
<template v-if="orderGoods.images">
<div class="demo-upload-list" v-for="(img, index) in orderGoods.images.split(',')" :key="index">
<img :src="img" />
<div class="demo-upload-list-cover"><Icon type="ios-eye-outline" @click.native="handleView(img)"></Icon></div>
<div class="demo-upload-list-cover"><el-icon @click="handleView(img)"><View /></el-icon></div>
</div>
</template>
</div>
@@ -75,7 +72,7 @@
<template v-if="orderGoods.replyImage">
<div class="demo-upload-list" v-for="(img, index) in orderGoods.replyImage.split(',')" :key="index">
<img :src="img" />
<div class="demo-upload-list-cover"><Icon type="ios-eye-outline" @click.native="handleView(img)"></Icon></div>
<div class="demo-upload-list-cover"><el-icon @click="handleView(img)"><View /></el-icon></div>
</div>
</template>
</div>
@@ -84,14 +81,17 @@
</div>
</li>
</ul>
<Modal title="View Image" v-model="visible">
<el-dialog title="View Image" v-model="visible">
<img :src="previewImage" v-if="visible" style="width: 100%" />
</Modal>
</el-dialog>
</UserCenterLayout>
</div>
</template>
<script>
import { View } from '@element-plus/icons-vue';
import { evaluationDetail } from "@/api/member.js";
export default {
components: { View },
data() {
return {
order: {}, // 订单详情
@@ -132,13 +132,29 @@ export default {
display: flex;
align-items: center;
margin-top: 20px;
height: 50px;
min-height: 50px;
border-bottom: 1px solid #eee;
> div:nth-child(1) {
width: 120px;
font-weight: bold;
}
}
.delivery-rate-items {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px;
}
.delivery-rate-items :deep(.el-rate) {
--el-rate-fill-color: #ffb400;
--el-rate-disabled-fill-color: #ffb400;
}
:deep(.el-rate__icon.is-active) {
color: #ffb400;
}
.goods-eval li {
display: flex;
border-bottom: 1px solid #eee;

View File

@@ -1,169 +1,92 @@
<template>
<div class="msg-list">
<card _Title="我的消息" :_Tabs="status" :_Size="16" @_Change="statusChange"/>
<UserCenterLayout
title="我的消息"
:tabs="status"
:active-tab="activeStatusIndex"
@tab-change="statusChange"
>
<Table v-if="params.status != 'ALREADY_REMOVE' " :columns="messageColumns" :data="messageData.records"></Table>
<Table v-if="params.status == 'ALREADY_REMOVE' " :columns="messageDelColumns" :data="messageData.records"></Table>
<!-- 分页 -->
<Page
style="float:right;margin-top:10px"
:current="params.pageNumber"
:total="messageData.total"
:page-size="params.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
<el-table v-if="params.status != 'ALREADY_REMOVE'" :data="messageData.records || []">
<el-table-column prop="title" label="消息标题" align="left" show-overflow-tooltip />
<el-table-column prop="content" label="消息内容" align="left" show-overflow-tooltip />
<el-table-column prop="createTime" label="发送时间" align="left" width="240" />
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="{ row }">
<el-button
v-if="row.status === 'UN_READY'"
type="primary"
size="small"
show-total
show-elevator
></Page>
style="margin-right: 5px"
@click="setRead(row.id)"
>已读</el-button>
<el-button type="danger" size="small" @click="removeMessage(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-table v-else :data="messageData.records || []">
<el-table-column prop="title" label="消息标题" align="left" show-overflow-tooltip />
<el-table-column prop="content" label="消息内容" align="left" show-overflow-tooltip />
<el-table-column prop="createTime" label="发送时间" align="left" width="240" />
</el-table>
<div class="msg-pagination">
<el-pagination
v-model:current-page="params.pageNumber"
v-model:page-size="params.pageSize"
:total="messageData.total"
@current-change="changePage"
@size-change="changePageSize"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next, jumper"
/>
</div>
</UserCenterLayout>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import {memberMsgList, readMemberMsg, delMemberMsg} from '@/api/member.js'
export default {
data() {
return {
messageData: {}, // 消息数据
messageData: {},
status: ['未读', '已读', '回收站'],
params: { // 请求参数
params: {
pageNumber: 1,
pageSize: 10,
status: 'UN_READY'
},
messageDelColumns: [ // table展示数据
{
title: '消息标题',
key: 'title',
align: 'left',
tooltip: true,
},
{
title: '消息内容',
key: 'content',
align: 'left',
tooltip: true
},
{
title: '发送时间',
key: 'createTime',
align: 'left',
width: 240
},
],
messageColumns: [ // table展示数据
{
title: '消息标题',
key: 'title',
align: 'left',
tooltip: true,
},
{
title: '消息内容',
key: 'content',
align: 'left',
tooltip: true
},
{
title: '发送时间',
key: 'createTime',
align: 'left',
width: 240
},
{
title: '操作',
key: 'action',
align: 'center',
fixed: 'right',
width: 150,
render: (h, params) => {
if (params.row.status === 'UN_READY') {
return h('div', [
h(
'Button',
{
props: {
type: 'info',
size: 'small'
},
style: {
marginRight: '5px'
},
on: {
click: () => {
this.setRead(params.row.id);
}
}
},
'已读'
), h(
'Button',
{
props: {
size: 'small',
type: 'error'
},
on: {
click: () => {
this.removeMessage(params.row.id);
}
}
},
'删除'
)
]);
} else if (params.row.status === 'ALREADY_READY') {
return h('div', [
h(
'Button',
{
props: {
size: 'small',
type: 'error'
},
on: {
click: () => {
this.removeMessage(params.row.id);
}
}
},
'删除'
)
]);
} else {
}
}
}
]
computed: {
activeStatusIndex () {
const map = ['UN_READY', 'ALREADY_READY', 'ALREADY_REMOVE'];
const index = map.indexOf(this.params.status);
return index > -1 ? index : 0;
}
},
methods: {
// 消息状态发生变化
statusChange (index) {
if (index === 0) { this.params.status = 'UN_READY' }
if (index === 1) { this.params.status = 'ALREADY_READY' }
if (index === 2) { this.params.status = 'ALREADY_REMOVE' }
this.getList()
},
// 修改页码
changePage (v) {
this.params.pageNumber = v;
this.getList();
},
// 修改页数
changePageSize (v) {
this.params.pageSize = v;
this.getList();
},
getList () { // 获取消息列表
getList () {
memberMsgList(this.params).then(res => {
if (res.success) {
this.messageData = res.result;
}
})
},
// 设置消息已读
setRead (id) {
readMemberMsg(id).then(res => {
if (res.success) {
@@ -171,23 +94,18 @@ export default {
}
})
},
// 消息放入回收站
removeMessage (id) {
this.$Modal.confirm({
Modal.confirm({
title: '确认删除',
// 记得确认修改此处
content: '确认要删除此消息?',
loading: true,
onOk: () => {
// 删除
onOk: () =>
delMemberMsg(id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success('消息已成功放入回收站');
Message.success('消息已成功放入回收站');
this.getList();
}
});
},
}),
});
}
},
@@ -197,5 +115,9 @@ export default {
}
</script>
<style lang="scss" scoped>
.msg-pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
</style>

View File

@@ -1,43 +1,42 @@
<template>
<div>
<card _Title="收货地址" />
<UserCenterLayout title="收货地址" :tabs="['收货地址']">
<div class="add-box">
<Form :model="formData" ref="form" label-position="left" :label-width="100" :rules="ruleInline">
<FormItem label="收件人" prop="name">
<i-input v-model="formData.name" placeholder="请输入收件人姓名" style="width: 600px"></i-input>
</FormItem>
<FormItem label="收件地区" prop="address">
<el-form :model="formData" ref="form" label-position="left" label-width="100px" :rules="ruleInline">
<el-form-item label="收件人" prop="name">
<el-input v-model="formData.name" placeholder="请输入收件人姓名" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="收件地区" prop="address">
<span>{{ formData.address || '暂无地址' }}</span>
<Button type="default" style="margin-left: 10px;" size="small" @click="$refs.map.open()">选择</Button>
</FormItem>
<FormItem label="详细地址" prop="detail">
<i-input v-model="formData.detail" placeholder="请输入详细地址" style="width: 600px"></i-input>
</FormItem>
<FormItem label="手机号码" prop="mobile">
<i-input v-model="formData.mobile" placeholder="请输入收件人手机号" style="width: 600px"></i-input>
</FormItem>
<FormItem label="地址别名">
<i-input v-model="formData.alias" length :maxlength="4" placeholder="请输入地址别名,例如公司" style="width: 600px">
</i-input>
</FormItem>
<FormItem label="默认地址">
<i-switch v-model="formData.isDefault" />
</FormItem>
</Form>
<el-button type="default" style="margin-left: 10px;" size="small" @click="$refs.map.open()">选择</el-button>
</el-form-item>
<el-form-item label="详细地址" prop="detail">
<el-input v-model="formData.detail" placeholder="请输入详细地址" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入收件人手机号" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="地址别名">
<el-input v-model="formData.alias" length :maxlength="4" placeholder="请输入地址别名,例如公司" style="width: 600px">
</el-input>
</el-form-item>
<el-form-item label="默认地址">
<el-switch v-model="formData.isDefault" />
</el-form-item>
</el-form>
</div>
<div class="mt_20">
<Button type="primary" class="mr_10" :loading="loading" @click="save">保存收货地址</Button>
<Button @click="$router.back()">返回</Button>
<div class="action-bar mt_20">
<el-button @click="$router.back()">返回</el-button>
<el-button type="primary" class="ml_10" :loading="loading" @click="save">保存收货地址</el-button>
</div>
</UserCenterLayout>
<multipleMap ref="map" @callback="getAddress" />
</div>
</template>
<script>
import card from "@/components/card";
import multipleMap from "@/components/map/multiple-map";
import * as RegExp from "@/plugins/RegExp.js";
import {
@@ -85,8 +84,8 @@ export default {
if (this.$route.query.id) {
editMemberAddress(params)
.then((res) => {
if (res.success) {
this.loading = false;
if (res.success) {
this.$router.push("/home/MyAddress");
}
})
@@ -96,8 +95,8 @@ export default {
} else {
newMemberAddress(params)
.then((res) => {
if (res.success) {
this.loading = false;
if (res.success) {
this.$router.push("/home/MyAddress");
}
})
@@ -124,15 +123,15 @@ export default {
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
this.$set(this.formData,'address',paths)
this.$set(this.formData,'consigneeAddressIdPath',ids)
this.formData['address'] = paths
this.formData['consigneeAddressIdPath'] = ids
const coord = val.data[val.data.length - 1].center.split(',')
this.formData.lat = coord[1]
this.formData.lon = coord[0]
}else{
this.$set(this.formData, "address", val.data.addr);
this.$set(this.formData, "consigneeAddressIdPath", val.data.addrId);
this.$set(this.formData, "detail", val.data.address);
this.formData["address"] = val.data.addr;
this.formData["consigneeAddressIdPath"] = val.data.addrId;
this.formData["detail"] = val.data.address;
this.formData.lat = val.data.position.lat;
this.formData.lon = val.data.position.lng;
}
@@ -145,7 +144,6 @@ export default {
if (id) this.getAddrById(id);
},
components: {
card,
multipleMap
},
};
@@ -155,4 +153,9 @@ export default {
.add-box {
margin: 40px 0;
}
.action-bar {
display: flex;
justify-content: flex-end;
}
</style>

View File

@@ -1,35 +1,31 @@
<template>
<div class="wrapper">
<!-- 卡片组件 -->
<card _Title="我的售后" :_Size="16"></card>
<!-- 搜索 筛选 -->
<div class="mb_20 box">
<div class="global_float_right">
<Input
<UserCenterLayout title="我的售后" :tabs="['我的售后']">
<template #extra>
<el-input
class="width_300"
search
enter-button
v-model="params.keywords"
@on-search="getList"
@keyup.enter="getList"
placeholder="请输入订单号搜索"
/>
</div>
</div>
</template>
<!-- 列表 -->
<empty v-if="orderList.length === 0"/>
<div class="order-content" v-else>
<template v-for="(order, onderIndex) in orderList">
<div class="order-list" :key="onderIndex">
<template v-for="(order, onderIndex) in orderList" :key="order.sn || onderIndex">
<div class="order-list">
<div class="order-header">
<div>
<div v-if="order.serviceStatus">{{ filterOrderStatus(order.serviceStatus) }}</div>
<div>
售后单号{{ order.sn }} &nbsp; &nbsp; &nbsp;{{ order.createTime }}
&nbsp; &nbsp;{{ order.memberName | secrecyMobile }}
&nbsp; &nbsp;{{ $filters.secrecyMobile( order.memberName ) }}
</div>
</div>
<div>
<span>申请退款金额<span class="global_color">{{ order.applyRefundPrice | unitPrice("¥") }}</span></span>
<span>申请退款金额<span class="global_color">{{ $filters.unitPrice(order.applyRefundPrice, "¥") }}</span></span>
</div>
</div>
<div class="order-body">
@@ -43,37 +39,46 @@
</div>
</div>
<div>
<span @click="shopPage(order.shopId)">{{ order.storeName }}</span>
<span @click="shopPage(order.storeId)">{{ order.storeName }}</span>
</div>
<div>
<div class="order-actions">
<!-- 订单基础操作 -->
<Button @click="goDetail(order.sn)" type="info" size="small">售后详情</Button>
<Button @click="openModal(order)"
v-if="order.serviceStatus == 'PASS' &&
order.serviceType != 'RETURN_MONEY'"
type="warning" size="small">提交物流
</Button>
<Button @click="cancel(order.sn)" type="error" v-if="order.afterSaleAllowOperationVO.cancel" size="small">
取消售后
</Button>
<el-button
@click="goDetail(order.sn)"
type="warning"
size="small"
class="after-sale-action-btn"
>售后详情</el-button>
<el-button
@click="openModal(order)"
v-if="order.serviceStatus == 'PASS' && order.serviceType != 'RETURN_MONEY'"
size="small"
class="after-sale-action-btn submit-logistics-btn"
>提交物流</el-button>
<el-button
@click="cancel(order.sn)"
type="danger"
v-if="order.afterSaleAllowOperationVO.cancel"
size="small"
class="after-sale-action-btn"
>取消售后</el-button>
</div>
</div>
</div>
</template>
<Spin size="large" fix v-if="spinShow"></Spin>
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
</div>
<!-- 分页 -->
<div class="page-size">
<Page :total="total" @on-change="changePageNum"
@on-page-size-change="changePageSize"
:page-size="params.pageSize"
show-sizer>
</Page>
<el-pagination v-model:current-page="params.pageNumber" v-model:page-size="params.pageSize"
:total="total" @current-change="changePageNum"
@size-change="changePageSize" layout="sizes, prev, pager, next, jumper"></el-pagination>
</div>
<Modal v-model="logisticsShow" width="530">
<p slot="header">
</UserCenterLayout>
<el-dialog v-model="logisticsShow" width="530">
<template #header><p>
<span>提交物流信息</span>
</p>
</p></template>
<div>
<div class="goods-list modal-goods">
<img @click="goodsDetail(singleOrder.skuId, singleOrder.goodsId)" class="hover-color"
@@ -84,35 +89,41 @@
</div>
<div class="mt_10">
<span class="global_color"
>{{ singleOrder.flowPrice | unitPrice("¥") }} </span
>x {{ singleOrder.num }}
>{{ $filters.unitPrice(singleOrder.flowPrice, "¥") }} </span> x {{ singleOrder.num }}
</div>
</div>
</div>
<Form ref="form" :model="form" label-position="left" :label-width="100" :rules="rules">
<FormItem label="物流公司" prop="logisticsId">
<Select v-model="form.logisticsId" placeholder="请选择物流公司">
<Option v-for="item in companyList" :value="item.id" :key="item.id">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="物流单号" prop="logisticsNo">
<Input v-model="form.logisticsNo" placeholder="请填写物流单号"></Input>
</FormItem>
<FormItem label="发货时间" prop="mDeliverTime">
<DatePicker type="date" style="width:100%" v-model="form.mDeliverTime" @on-change="changeTime"
format="yyyy-MM-dd" placeholder="选择发货时间"></DatePicker>
</FormItem>
</Form>
<el-form ref="form" :model="form" label-position="left" label-width="100px" :rules="rules">
<el-form-item label="物流公司" prop="logisticsId">
<el-select v-model="form.logisticsId" placeholder="请选择物流公司">
<el-option v-for="item in companyList" :value="item.id" :key="item.id">{{ item.name }}</el-option>
</el-select>
</el-form-item>
<el-form-item label="物流单号" prop="logisticsNo">
<el-input v-model="form.logisticsNo" placeholder="请填写物流单号"></el-input>
</el-form-item>
<el-form-item label="发货时间" prop="mDeliverTime">
<el-date-picker
type="date"
style="width:100%"
v-model="form.mDeliverTime"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="选择发货时间"
/>
</el-form-item>
</el-form>
</div>
<div slot="footer" style="text-align: right">
<Button @click="logisticsShow = false">关闭</Button>
<Button type="primary" :loading="submitLoading" @click="submitDelivery">提交</Button>
</div>
</Modal>
<template #footer><div style="text-align: right">
<el-button @click="logisticsShow = false">关闭</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitDelivery">提交</el-button>
</div></template>
</el-dialog>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import {afterSaleList, cancelAfterSale} from '@/api/member.js';
import {afterSaleDelivery, getLogisticsCompany} from '@/api/order.js';
import {afterSaleStatusList} from '../enumeration.js'
@@ -161,13 +172,13 @@ export default {
})
},
cancel(sn) { // 取消售后申请
this.$Modal.confirm({
Modal.confirm({
title: '取消',
content: '<p>确定取消此次售后申请吗?</p>',
content: '确定取消此次售后申请吗?',
onOk: () => {
cancelAfterSale(sn).then(res => {
if (res.success) {
this.$Message.success('取消售后申请成功')
Message.success('取消售后申请成功')
this.getList()
}
})
@@ -228,32 +239,32 @@ export default {
},
// 提交物流信息
submitDelivery() {
this.submitLoading = true
this.$refs.form?.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
afterSaleDelivery(this.form).then(res => {
if (res.success) {
this.logisticsShow = false;
this.$Message.success('提交成功')
this.getList()
Message.success('提交成功');
this.getList();
}
this.submitLoading = false
this.submitLoading = false;
}).catch(() => {
this.submitLoading = false
})
this.submitLoading = false;
});
});
},
// 提交物流modal
openModal(row) {
console.log(row);
this.singleOrder = row;
this.form.afterSaleSn = row.sn
this.logisticsShow = true;
this.$refs.form.resetFields()
if (!this.companyList.length) {
this.getCompany()
this.getCompany();
}
},
// 格式化时间
changeTime(time) {
this.form.mDeliverTime = time;
this.$nextTick(() => {
this.$refs.form?.resetFields();
this.form.afterSaleSn = row.sn;
});
}
}
};
@@ -324,7 +335,7 @@ export default {
}
}
.ivu-icon {
.el-icon {
color: #ff8f23;
cursor: pointer;
@@ -334,11 +345,31 @@ export default {
}
}
> div:nth-child(3) {
width: 100px;
.order-actions {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
min-width: 100px;
.ivu-btn {
margin-bottom: 10px;
.after-sale-action-btn {
display: block;
margin: 0;
min-width: 88px;
}
:deep(.submit-logistics-btn) {
background-color: #ff9900 !important;
border-color: #ff9900 !important;
color: #fff !important;
&:hover,
&:focus {
background-color: #e68a00 !important;
border-color: #e68a00 !important;
color: #fff !important;
}
}
}
}

View File

@@ -1,23 +1,29 @@
<template>
<div class="order-detail">
<card _Title="售后详情" :_Size="16"></card>
<UserCenterLayout title="售后详情" :tabs="['售后详情']">
<!-- 操作按钮 -->
<Card class="mb_10" v-if="(afterSale.serviceStatus == 'PASS' &&
<el-card class="mb_10" shadow="never" v-if="(afterSale.serviceStatus == 'PASS' &&
afterSale.serviceType != 'RETURN_MONEY') || (afterSale.afterSaleAllowOperationVO && afterSale.afterSaleAllowOperationVO.cancel)">
<Button type="success" @click="openModal" v-if="afterSale.serviceStatus == 'PASS' &&
afterSale.serviceType != 'RETURN_MONEY'" size="small">提交物流</Button>&nbsp;
<Button type="error" @click="cancel(afterSale.sn)" v-if="afterSale.afterSaleAllowOperationVO && afterSale.afterSaleAllowOperationVO.cancel" size="small">取消售后</Button>
</Card>
<el-button
@click="openModal"
v-if="afterSale.serviceStatus == 'PASS' && afterSale.serviceType != 'RETURN_MONEY'"
size="small"
class="submit-logistics-btn"
>提交物流</el-button>&nbsp;
<el-button type="danger" @click="cancel(afterSale.sn)" v-if="afterSale.afterSaleAllowOperationVO && afterSale.afterSaleAllowOperationVO.cancel" size="small">取消售后</el-button>
</el-card>
<div class="order-card">
<h3>{{afterSale.serviceName}}</h3>
<p class="global_color fontsize_18">{{ afterSale.orderStatusValue }}</p>
<p>售后单号:{{ afterSale.sn }} &nbsp;&nbsp;&nbsp;订单号:{{afterSale.orderSn}}</p>
<div style="color:#999;" class="operation-time">创建时间{{afterSale.createTime}}</div>
<p class="order-meta">
<span>售后单号:{{ afterSale.sn }} &nbsp;&nbsp;&nbsp;订单号{{ afterSale.orderSn }}</span>
<span class="order-meta-time">创建时间:{{ afterSale.createTime }}</span>
</p>
<div class="service-after">
<div>
本次售后服务由<span>{{afterSale.storeName}}</span>为您提供
</div>
<div>
<div class="service-after-goods">
<img :src="afterSale.goodsImage" alt="" width="60" height="60">
<span>{{afterSale.goodsName}}</span>
</div>
@@ -25,14 +31,13 @@
</div>
<div class="order-card">
<h3>售后进程</h3>
<Steps class="progress" :current="logList.length" direction="vertical">
<Step
:content="'操作人:'+ log.operatorName + ' ' + log.createTime"
<el-steps class="progress" :current="logList.length" direction="vertical">
<el-step :content="'操作人:'+ log.operatorName + ' ' + log.createTime"
:title="log.message"
v-for="(log, index) in logList"
:key="index"
></Step>
</Steps>
></el-step>
</el-steps>
</div>
<div class="order-card">
<h3 class="mb_10">服务单信息</h3>
@@ -41,10 +46,10 @@
<td>退款方式</td><td>{{afterSale.refundWay == 'ORIGINAL' ? '原路退回' : '账号退款'}}</td>
</tr>
<tr>
<td>申请退款金额</td><td>{{afterSale.applyRefundPrice | unitPrice('¥')}}</td>
<td>申请退款金额</td><td>{{ $filters.unitPrice(afterSale.applyRefundPrice, '¥') }}</td>
</tr>
<tr v-if="afterSale.actualRefundPrice">
<td>实际退款金额</td><td>{{afterSale.actualRefundPrice | unitPrice('¥')}}</td>
<td>实际退款金额</td><td>{{ $filters.unitPrice(afterSale.actualRefundPrice, '¥') }}</td>
</tr>
<template v-if="afterSale.refundWay === 'OFFLINE'">
<tr>
@@ -74,33 +79,42 @@
<img :src="img" width="200" height="200" @click="perviewImg(img)" class="hover-pointer" alt="">
</div>
</div>
<Modal v-model="logisticsShow" width="530">
<p slot="header">
<el-dialog v-model="logisticsShow" width="530">
<template #header><p>
<span>提交物流信息</span>
</p>
</p></template>
<div>
<Form ref="form" :model="form" label-position="left" :label-width="100" :rules="rules">
<FormItem label="物流公司" prop="logisticsId">
<Select v-model="form.logisticsId" placeholder="请选择物流公司">
<Option v-for="item in companyList" :value="item.id" :key="item.id">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="物流单号" prop="logisticsNo">
<Input v-model="form.logisticsNo" placeholder="请填写物流单号"></Input>
</FormItem>
<FormItem label="发货时间" prop="mDeliverTime">
<DatePicker type="date" style="width:100%" v-model="form.mDeliverTime" @on-change="changeTime" format="yyyy-MM-dd" placeholder="选择发货时间"></DatePicker>
</FormItem>
</Form>
<el-form ref="form" :model="form" label-position="left" label-width="100px" :rules="rules">
<el-form-item label="物流公司" prop="logisticsId">
<el-select v-model="form.logisticsId" placeholder="请选择物流公司">
<el-option v-for="item in companyList" :value="item.id" :key="item.id">{{ item.name }}</el-option>
</el-select>
</el-form-item>
<el-form-item label="物流单号" prop="logisticsNo">
<el-input v-model="form.logisticsNo" placeholder="请填写物流单号"></el-input>
</el-form-item>
<el-form-item label="发货时间" prop="mDeliverTime">
<el-date-picker
type="date"
style="width:100%"
v-model="form.mDeliverTime"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="选择发货时间"
/>
</el-form-item>
</el-form>
</div>
<div slot="footer" style="text-align: right">
<Button @click="logisticsShow = false">关闭</Button>
<Button type="primary" :loading="submitLoading" @click="submitDelivery">提交</Button>
</div>
</Modal>
<template #footer><div style="text-align: right">
<el-button @click="logisticsShow = false">关闭</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitDelivery">提交</el-button>
</div></template>
</el-dialog>
</UserCenterLayout>
</div>
</template>
<script>
import { Message, Modal } from "@/utils/message";
import { afterSaleDetail, afterSaleLog, afterSaleReason, cancelAfterSale } from '@/api/member.js';
import { afterSaleDelivery, getLogisticsCompany } from '@/api/order.js';
import { afterSaleStatusList } from '../enumeration.js'
@@ -139,7 +153,7 @@ export default {
if (pattern3.test(this.afterSale.reason)) {
this.getReason(this.afterSale.serviceType)
} else {
this.$set(this.afterSale, 'reasonName', this.afterSale.reason)
this.afterSale['reasonName'] = this.afterSale.reason
}
}
})
@@ -150,7 +164,7 @@ export default {
this.reasonList = res.result
this.reasonList.forEach(e => {
if (e.id === this.afterSale.reason) {
this.$set(this.afterSale, 'reasonName', e.reason)
this.afterSale['reasonName'] = e.reason
}
})
}
@@ -169,13 +183,13 @@ export default {
window.open(img, '_blank')
},
cancel (sn) { // 取消售后申请
this.$Modal.confirm({
Modal.confirm({
title: '取消',
content: '<p>确定取消此次售后申请吗?</p>',
onOk: () => {
cancelAfterSale(sn).then(res => {
if (res.success) {
this.$Message.success('取消售后申请成功')
Message.success('取消售后申请成功')
this.getDetail()
}
})
@@ -197,7 +211,7 @@ export default {
afterSaleDelivery(this.form).then(res => {
if (res.success) {
this.logisticsShow = false;
this.$Message.success('提交成功')
Message.success('提交成功')
this.getDetail()
}
this.submitLoading = false
@@ -207,16 +221,14 @@ export default {
},
// 提交物流modal
openModal () {
this.form.afterSaleSn = this.afterSale.sn
this.logisticsShow = true;
this.$refs.form.resetFields()
if (!this.companyList.length) {
this.getCompany()
this.getCompany();
}
},
// 格式化时间
changeTime (time) {
this.form.mDeliverTime = time;
this.$nextTick(() => {
this.$refs.form?.resetFields();
this.form.afterSaleSn = this.afterSale.sn;
});
}
},
mounted () {
@@ -239,15 +251,21 @@ export default {
color: #999;
margin: 3px;
}
.order-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
.order-meta-time {
flex-shrink: 0;
white-space: nowrap;
}
}
h3 {
font-weight: normal;
font-size: 16px;
}
.operation-time {
position: absolute;
right: 20px;
top: 10px;
}
&:last-child{
border: none;
}
@@ -268,8 +286,19 @@ export default {
}
>div:nth-child(2){
padding-left: 15px;
img{vertical-align: top;}
span{color: #999;}
}
.service-after-goods {
display: flex;
align-items: center;
gap: 10px;
img {
flex-shrink: 0;
}
span {
color: #999;
}
}
}
/** 售后进度条 */
@@ -289,4 +318,17 @@ table{
padding: 5px;
}
}
:deep(.submit-logistics-btn) {
background-color: #ff9900 !important;
border-color: #ff9900 !important;
color: #fff !important;
&:hover,
&:focus {
background-color: #e68a00 !important;
border-color: #e68a00 !important;
color: #fff !important;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More