Merge branch 'master' of gitee.com:beijing_hongye_huicheng/lilishop-ui

This commit is contained in:
Chopper711
2026-05-17 19:54:19 +08:00
232 changed files with 12850 additions and 4525 deletions

View File

@@ -1,16 +1,12 @@
## Lilishop B2B2C商城系统
#### 欢迎交流需求,交流业务,交流技术(基础问题自行解决,其他问题先看文档后提问)
#### 不用削尖脑袋往老群里加,老群活跃度较低,很多潜水党,新群相对而言活跃一些 :tw-1f606: :tw-1f606: :tw-1f606: :tw-1f606: :tw-1f606: :tw-1f606:
#### PS **演示站点所有环境均部署master分支。如果有演示站点问题可以反馈如果演示站点没问题本地运行有问题需自行处理**
##### 交流 qq 1群 961316482已满
##### 交流 qq 2群 875294241(已满)
##### 交流 qq 3群 263785057已满
##### 交流 qq 4群 674617534 (已满)
##### 交流 qq 5群 594675235
- **[在线客服](https://work.weixin.qq.com/kfid/kfc4d8dc24a73c15f44)**
- **微信交流1群(已满)**
- **微信交流2群**:
![微信群](https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/wechat.jpg)
##### 体验 公众号/小程序/APP 体验,扫描二维码
@@ -19,6 +15,41 @@
[![star](https://gitee.com/beijing_hongye_huicheng/lilishop/badge/star.svg?theme=dark)](https://gitee.com/beijing_hongye_huicheng/lilishop/stargazers)
  ![github](https://img.shields.io/github/stars/hongyehuicheng/lilishop.svg?style=social&logo=#181717)
## 2025-10-10日更新
兼容更高的node版本16
这里我用的是node版本 v16.20.2
npm版本 8.19.4
使用yarn install 然后执行 yarn dev
yarn 安装/启动
```
// 如果没有 yarn 安装yarn
npm install yarn -g
// 切换源
yarn config set registry https://registry.npmmirror.com
// 以buyer项目为例
cd buyer
yarn install
yarn dev
```
没有二开过的项目直接拉最新代码即可,二开项目可以跟着提交记录一起同步修改 install出现问题检查的话删除 "package-lock.json" 重新install
Q&A 为什么不升级更高的node版本 :因为高node版本 OpenSSL 改动 导致旧版本 Webpack 插件会失效 试了好几次如果兼容的话 需要升级Webpack5以及其他的插件 升级内容较多 为了更稳定的还是尽量少动为主
****
## 如何在本地环境运行lilishop-ui部署视频
https://www.bilibili.com/video/BV1B28EeJEnP/
@@ -29,8 +60,11 @@ https://www.bilibili.com/video/BV1WD87eoE9F/
## 开发项目
#### 安装Node.js
保证`node`版本`14`,推荐 14.17.0
2025-10-10日拉的代码之后不限制于node版本为14这里只是以14版本为例子
可以使用 `yarn` 或者 `npm` 进行安装
#### yarn 安装/启动
@@ -49,17 +83,6 @@ yarn install
yarn dev
```
#### npm 安装/启动
```
npm config set registry https://registry.npmmirror.com
// 以buyer项目为例
cd buyer
npm run install
npm run dev
```
#### FAQ
@@ -223,16 +246,6 @@ PS手机验证码为 111111
4.限制商用如果需要商业使用请联系我们。QQ3409056806.或者加入qq群联系群主。
### 交流群
##### 交流 qq 1群 961316482已满
##### 交流 qq 2群 875294241已满
##### 交流 qq 3群 263785057已满
##### 交流 qq 4群 674617534已满
##### 交流 qq 5群 594675235
### 附录
有人有自己的学习视频、学习记录文档、希望宣传关联开源项目等均可以私聊仓库所有者。

1
buyer/.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=false

1
buyer/.yarnrc Normal file
View File

@@ -0,0 +1 @@
--ignore-engines true

View File

@@ -1,19 +0,0 @@
# new
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -3,9 +3,12 @@
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
"build": "vue-cli-service build",
"dev": "vue-cli-service serve"
},
"engines": {
"node": ">=14"
},
"dependencies": {
"@amap/amap-jsapi-loader": "0.0.7",
@@ -15,11 +18,10 @@
"less": "^2.7.0",
"less-loader": "^5.0.0",
"mv-count-down": "^0.1.15",
"node-sass": "^4.14.1",
"postcss-loader": "^7.0.1",
"sass": "^1.63.6",
"postcss-loader": "^4.3.0",
"psl": "^1.8.0",
"qs": "^6.9.4",
"sass-loader": "^7.3.1",
"uuid": "^8.3.2",
"view-design": "^4.3.2",
"vue": "^2.6.11",
@@ -32,7 +34,7 @@
"devDependencies": {
"@vue/cli-service": "~4.5.0",
"compression-webpack-plugin": "^5.0.0",
"sass-loader": "^7.3.1",
"sass-loader": "^10.4.1",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-template-compiler": "^2.6.11"
},
@@ -40,5 +42,10 @@
"> 1%",
"last 2 versions",
"not dead"
]
],
"resolutions": {
"minimatch": "^3.1.2",
"node-sass": "npm:sass@^1.63.6",
"@achrinza/node-ipc": "9.2.2"
}
}

View File

@@ -69,6 +69,32 @@ export function loginCallback (uuid) {
});
}
export function getThirdAccountBindList () {
return request({
url: '/buyer/passport/connect/bind/list',
method: Method.GET,
needToken: true
});
}
export function bindThirdAccount (data) {
return request({
url: '/buyer/passport/connect/bind',
method: Method.POST,
needToken: true,
data
});
}
export function unbindThirdAccount (data) {
return request({
url: '/buyer/passport/connect/bind/unbind',
method: Method.POST,
needToken: true,
data
});
}
/**
* 忘记密码 验证手机验证码
*/
@@ -109,3 +135,12 @@ export function sCLogin(token,params) {
params
});
}
export function getWechatH5QrCode (params) {
return request({
url: '/buyer/other/wechat/h5/qrcode',
method: Method.GET,
needToken: true,
params
})
}

View File

@@ -180,6 +180,15 @@ export function receiptList () {
});
}
// 发票详情
export function receiptDetail (id) {
return request({
url: `/buyer/trade/receipt/${id}`,
method: Method.GET,
needToken: true
});
}
// 保存发票信息
export function saveReceipt (params) {
return request({
@@ -480,6 +489,31 @@ export function memberPointHistory (params) {
params
});
}
export function memberGradeCurrent (params) {
return request({
url: `/buyer/member/memberGrade/current`,
method: Method.GET,
needToken: true,
params
})
}
export function memberGradeRules () {
return request({
url: `/buyer/member/memberGrade/rules`,
method: Method.GET,
needToken: true
})
}
export function memberGradeList () {
return request({
url: `/buyer/member/memberGrade/list`,
method: Method.GET,
needToken: true
})
}
/**
* 分页获取会员站内信
* @param {Object} params 请求参数包括pageNumber、pageSize、status

View File

@@ -173,7 +173,7 @@ export default {
border-radius: 18.9px;
/deep/ .ivu-input.ivu-input-large {
::v-deep .ivu-input.ivu-input-large {
border: 1.4px solid $theme_color;
box-sizing: border-box;
border-radius: 19.6px;
@@ -188,7 +188,7 @@ export default {
}
}
/deep/ .ivu-input-group-append {
::v-deep .ivu-input-group-append {
border-radius: 19.6px !important;
cursor: pointer;
box-sizing: border-box;

View File

@@ -113,12 +113,12 @@ export default {
}
}
/deep/ .ivu-card, .ivu-card-head, ._Card {
::v-deep .ivu-card, .ivu-card-head, ._Card {
margin-bottom: 20px;
@include white_background_color();
}
/deep/ .ivu-card-head {
::v-deep .ivu-card-head {
position: relative;
padding: 0 14px;
height: 50px;
@@ -140,7 +140,7 @@ export default {
cursor: pointer;
}
/deep/ .ivu-card-body {
::v-deep .ivu-card-body {
padding: 0 !important;
display: none;
}

View File

@@ -147,11 +147,11 @@ export default {
justify-content: center;
flex-direction: column;
}
/deep/.popup .ivu-drawer-body{
::v-deep.popup .ivu-drawer-body{
padding: 0!important;
background-color: #eee;
}
/deep/.popup .ivu-drawer-wrap{
::v-deep.popup .ivu-drawer-wrap{
z-index: 3001;
}
</style>

View File

@@ -87,14 +87,12 @@
</div>
</TabPane>
<TabPane label="商品参数">
<template v-if="detail.goodsParamsDTOList && detail.goodsParamsDTOList.length">
<div class="goods-params" style="height:inherit;" v-for="item in detail.goodsParamsDTOList" :key="item.groupId">
<span class="ml_10">{{item.groupName}}</span>
<table class="mb_10" cellpadding='0' cellspacing="0" >
<tr v-for="param in item.goodsParamsItemDTOList" :key="param.paramId">
<td style="text-align: center">{{param.paramName}}</td><td>{{param.paramValue}}</td>
</tr>
</table>
<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">
<span class="item-param-title">{{ param.paramName }}</span>
<span class="item-param-content">{{ param.paramValue || '-' }}</span>
</div>
</div>
</template>
<div v-else>暂无商品参数</div>
@@ -134,6 +132,29 @@ export default {
// 商品详情
skuDetail () {
return this.detail.data;
},
goodsParamsList () {
const list = this.detail && Array.isArray(this.detail.goodsParamsDTOList) ? this.detail.goodsParamsDTOList : [];
const flat = [];
list.forEach((item) => {
if (!item) return;
if (Array.isArray(item.goodsParamsItemDTOList)) {
flat.push(...item.goodsParamsItemDTOList);
} else {
flat.push(item);
}
});
const cleaned = flat.filter((p) => p && (p.paramName || p.paramId));
const hasSort = cleaned.some((p) => p && p.sort !== undefined && p.sort !== null);
if (!hasSort) return cleaned;
return cleaned.slice().sort((a, b) => {
const sa = Number(a && a.sort) || 0;
const sb = Number(b && b.sort) || 0;
return sa - sb;
});
}
},
methods: {
@@ -326,7 +347,7 @@ export default {
.item-intro-img {
width: 100%;
min-height: 300px;
/deep/ img{
::v-deep img{
margin:0 auto;
}
}
@@ -336,14 +357,12 @@ export default {
/************* 商品参数 *************/
.item-param-container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-between;
flex-direction: column;
}
.item-param-box {
padding: 5px;
padding-left: 30px;
width: 240px;
width: 100%;
height: 36px;
font-size: 14px;
}
@@ -472,7 +491,7 @@ export default {
.ivu-tabs-ink-bar {
background-color: $theme_color !important;
}
/deep/.ivu-tabs-bar{
::v-deep.ivu-tabs-bar{
border: none;
}
.item-tabs > .ivu-tabs > .ivu-tabs-bar .ivu-tabs-tab{

View File

@@ -84,10 +84,10 @@ export default {
border-top: 1.4px solid #e2e2e2;
}
&:hover {
/deep/ .goods-name {
::v-deep .goods-name {
color: $theme_color;
}
/deep/ .goods-desc {
::v-deep .goods-desc {
color: $theme_color;
}
}

View File

@@ -1,30 +1,31 @@
<template>
<div class="invoice-modal">
<Modal v-model="invoiceAvailable" width="600" footer-hide>
<Modal v-model="invoiceAvailable" width="640" footer-hide>
<p slot="header">
<span>发票信息</span>
</p>
<!-- 普通发票 -->
<div class="nav-content">
<Form :model="invoiceForm" ref="form" label-position="left" :rules="ruleInline" :label-width="110">
<Form :model="invoiceForm" ref="form" label-position="left" :rules="formRules" :label-width="118">
<FormItem label="发票类型">
<RadioGroup v-model="invoice" type="button" button-style="solid">
<Radio @on-change="changeInvoice" :label="1">电子普通发票</Radio>
<Radio :label="2" :disabled="true">增值税专用发票</Radio>
<RadioGroup v-model="invoice" type="button" button-style="solid" @on-change="onInvoiceKindChange">
<Radio :label="1">电子普通发票</Radio>
<Radio :label="2">增值税专用发票</Radio>
</RadioGroup>
</FormItem>
<!-- 电子普通发票 -->
<div v-if="invoice === 1" key="invoice-normal">
<FormItem label="发票抬头">
<RadioGroup v-model="type" @on-change="changeInvoice" type="button" button-style="solid">
<RadioGroup v-model="type" type="button" button-style="solid" @on-change="onHeaderTypeChange">
<Radio :label="1">个人</Radio>
<Radio :label="2">单位</Radio>
</RadioGroup>
</FormItem>
<FormItem label="个人名称" v-if="type === 1" prop="receiptTitle">
<i-input v-model="invoiceForm.receiptTitle"></i-input>
</FormItem>
<FormItem label="单位名称" v-if="type === 2" prop="receiptTitle">
<i-input v-model="invoiceForm.receiptTitle"></i-input>
<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>
<FormItem label="纳税人识别号" v-if="type === 2" prop="taxpayerId">
<i-input v-model="invoiceForm.taxpayerId"></i-input>
</FormItem>
@@ -34,6 +35,49 @@
<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>
</div>
<!-- 增值税专用发票固定为单位 -->
<div v-else key="invoice-vat">
<FormItem 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>
</div>
</Form>
<div style="text-align: center">
<Button type="primary" :loading="loading" @click="submit">保存发票信息</Button>
@@ -45,106 +89,209 @@
</template>
<script>
import { receiptSelect } from '@/api/cart.js';
import { TINumber } from '@/plugins/RegExp.js';
import { mobile, email } from '@/plugins/RegExp.js';
export default {
name: 'invoiceModal',
data () {
return {
invoice: 1, // 发票类型
invoiceAvailable: false, // 模态框显隐
loading: false, // 提交状态
invoice: 1, // 1 电子普通发票 2 增值税专用发票
invoiceAvailable: false,
loading: false,
invoiceForm: {
// 普票表单
receiptTitle: '', // 发票抬头
taxpayerId: '', // 纳税人识别号
receiptContent: '商品明细' // 发票内容
receiptTitle: '',
personalName: '',
companyName: '',
taxpayerId: '',
receiptContent: '商品明细',
receiptPhone: '',
receiptEmail: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: ''
},
type: 1, // 1 个人 2 单位
ruleInline: {
taxpayerId: [
{ required: true, message: '请填写纳税人识别号' },
{ pattern: TINumber, message: '请填写正确的纳税人识别号' }
]
}
type: 1
};
},
props: ['invoiceData'],
computed: {
formRules () {
const receiptPhoneRules = [
{ required: true, message: '请填写收票人手机', trigger: 'blur' },
{ pattern: mobile, message: '请填写正确的手机号码', trigger: 'blur' }
];
// 收票人邮箱可选:仅校验格式;提示文案须与手机区分,避免误用「请填写收票人手机」
const receiptEmailRules = [
{
validator (rule, val, cb) {
const s = val != null ? String(val).trim() : '';
if (!s) {
cb();
return;
}
if (!email.test(s)) {
cb(new Error('请填写正确的收票人邮箱'));
return;
}
cb();
},
trigger: 'blur'
}
];
if (this.invoice === 2) {
return {
companyName: [{ required: true, message: '请填写单位名称', trigger: 'blur' }],
taxpayerId: [{ required: true, message: '请填写纳税人识别号', trigger: 'blur' }],
companyAddress: [{ required: true, message: '请填写单位地址', trigger: 'blur' }],
companyPhone: [{ required: true, message: '请填写单位电话', trigger: 'blur' }],
bankName: [{ required: true, message: '请填写开户银行', trigger: 'blur' }],
bankAccount: [{ required: true, message: '请填写银行账号', trigger: 'blur' }],
receiptPhone: receiptPhoneRules,
receiptEmail: receiptEmailRules
};
}
const rules = {
receiptPhone: receiptPhoneRules,
receiptEmail: receiptEmailRules
};
if (this.type === 1) {
rules.personalName = [{ required: true, message: '请填写个人名称', trigger: 'blur' }];
} else {
rules.companyName = [{ required: true, message: '请填写单位名称', trigger: 'blur' }];
rules.taxpayerId = [{ required: true, message: '请填写纳税人识别号', trigger: 'blur' }];
}
return rules;
}
},
watch: {
// 回显的发票信息
invoiceData: {
handler (val) {
this.invoiceForm = { ...val };
if (!val) return;
this.invoiceForm = { ...this.invoiceForm, ...val };
if (!this.invoiceForm.receiptPhone) this.invoiceForm.receiptPhone = '';
if (!this.invoiceForm.receiptEmail) this.invoiceForm.receiptEmail = '';
if (!this.invoiceForm.receiptContent) this.invoiceForm.receiptContent = '商品明细';
if (!this.invoiceForm.companyAddress) this.invoiceForm.companyAddress = '';
if (!this.invoiceForm.companyPhone) this.invoiceForm.companyPhone = '';
if (!this.invoiceForm.bankName) this.invoiceForm.bankName = '';
if (!this.invoiceForm.bankAccount) this.invoiceForm.bankAccount = '';
if (!this.invoiceForm.personalName) this.invoiceForm.personalName = '';
if (!this.invoiceForm.companyName) this.invoiceForm.companyName = '';
if (val.taxpayerId) {
const rt = val.receiptType;
if (rt === 2 || rt === '2' || val.invoiceKind === 'VAT_SPECIAL') {
this.invoice = 2;
this.type = 2;
} else {
this.type = 1;
this.invoice = 1;
if (val.taxpayerId) this.type = 2;
else this.type = 1;
}
// 兼容后端 receiptTitle 存「个人/单位」,实际名称在 personalName/companyName旧数据名称可能只在 receiptTitle
const titleAsName = (t) => (t && t !== '个人' && t !== '单位' ? t : '');
if (this.invoice === 2) {
this.invoiceForm.companyName = val.companyName || titleAsName(val.receiptTitle) || '';
} else if (this.type === 2) {
this.invoiceForm.companyName = val.companyName || titleAsName(val.receiptTitle) || '';
this.invoiceForm.personalName = val.personalName || '';
} else {
this.invoiceForm.personalName = val.personalName || titleAsName(val.receiptTitle) || '';
this.invoiceForm.companyName = val.companyName || '';
}
if (this.invoice === 1) {
this.invoiceForm.companyAddress = '';
this.invoiceForm.companyPhone = '';
this.invoiceForm.bankName = '';
this.invoiceForm.bankAccount = '';
}
if (this.invoice === 2) {
this.invoiceForm.personalName = '';
}
},
deep: true,
immeadite: true
immediate: true
}
},
methods: {
/**
* 选择发票抬头
*/
changeInvoice (val) {
onHeaderTypeChange (val) {
if (this.invoice === 1 && val === 1 && this.invoiceForm.personalName === '个人') {
this.invoiceForm.personalName = '';
}
this.$nextTick(() => {
this.type = val;
if (this.$refs.form) this.$refs.form.clearValidate();
});
},
onInvoiceKindChange (val) {
this.type = val === 2 ? 2 : 1;
this.invoiceForm = {
receiptTitle: '',
personalName: '',
companyName: '',
taxpayerId: '',
receiptContent: '商品明细',
receiptPhone: '',
receiptEmail: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: ''
};
this.$nextTick(() => {
if (this.$refs.form) this.$refs.form.clearValidate();
});
},
/**
* 保存判断
*/
save () {
let flage = true;
// 保存分为两种类型,个人以及企业
const { receiptTitle } = JSON.parse(
JSON.stringify(this.invoiceForm)
);
// 判断是否填写发票抬头
if (!receiptTitle) {
this.$Message.error('请填写发票抬头!');
flage = false;
return false;
async save () {
if (this.invoice === 2) {
this.invoiceForm.receiptContent = '商品明细';
}
if (this.type === 2) {
this.$refs.form.validate((valid) => {
if (!valid) {
flage = false;
}
return await new Promise(resolve => {
this.$refs.form.validate(valid => resolve(valid));
});
} else {
delete this.invoiceForm.taxpayerId;
}
return flage;
},
// 保存发票信息
async submit () {
if (this.save()) {
if (!await this.save()) return;
this.loading = true;
let submit = {
const raw = JSON.parse(JSON.stringify(this.invoiceForm));
if (this.invoice === 1) {
delete raw.companyAddress;
delete raw.companyPhone;
delete raw.bankName;
delete raw.bankAccount;
delete raw.invoiceAddress;
if (this.type === 1) {
delete raw.taxpayerId;
}
} else {
delete raw.personalName;
}
const isCompanyTitle = this.invoice === 2 || this.type === 2;
const titleName = isCompanyTitle
? (raw.companyName != null ? String(raw.companyName).trim() : '')
: (raw.personalName != null ? String(raw.personalName).trim() : '');
const submit = {
way: this.$route.query.way,
...this.invoiceForm
...raw,
receiptType: this.invoice,
receiptTitle: isCompanyTitle ? '单位' : '个人',
personalName: isCompanyTitle ? '' : titleName,
companyName: isCompanyTitle ? titleName : ''
};
let receipt = await receiptSelect(submit);
if (receipt.success) {
this.$emit('change', true);
}
this.loading = false;
}
}
}
};
</script>
<style lang="scss" scoped>
/** 普票 */
.inv-type {
text-align: center;
}
@@ -158,7 +305,12 @@ export default {
}
.nav-content {
width: 500px;
width: 520px;
margin: 10px auto;
}
.inv-title-fixed {
line-height: 32px;
color: #515a6e;
}
</style>

View File

@@ -25,11 +25,11 @@ export default {
//携带商品Id,在IM可以发送商品信息
if(goodsId && skuId){
window.open(
this.IMLink + "?token=" + accessToken + "&id=" + id || this.storeMsg.storeId + "&goodsId=" + goodsId + "&skuId=" + skuId
this.IMLink + "?token=" + accessToken + "&id=" + (id || this.storeMsg.storeId) + "&goodsId=" + goodsId + "&skuId=" + skuId
);
}else{
window.open(
this.IMLink + "?token=" + accessToken + "&id=" + id || this.storeMsg.storeId
this.IMLink + "?token=" + accessToken + "&id=" + (id || this.storeMsg.storeId)
);
}

View File

@@ -15,21 +15,112 @@
<Button @click="modifyPwd">修改密码</Button>
</Col>
</Row>
<Row class="safeItem">
<Col :span="2">
<Icon size="40" type="md-link" />
</Col>
<Col :span="16">
<div class="setDivItem">第三方账户绑定</div>
<div class="setDivItem theme">绑定后可使用第三方账号快捷登录</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>
</div>
</div>
<Spin size="large" fix v-if="connectLoading"></Spin>
</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>
<Modal
v-model="bindModalVisible"
title="绑定第三方账户"
:mask-closable="false"
:footer-hide="bindForm.type === 'WECHAT'"
:ok-text="bindModalOkText"
:loading="bindSubmitLoading"
@on-ok="submitBind"
@on-cancel="resetBindForm"
>
<Form :label-width="110">
<FormItem label="类型">
<Input v-model="bindForm.typeLabel" disabled />
</FormItem>
<FormItem v-if="bindForm.type === 'WECHAT'" label="公众号二维码">
<div class="wechatQrBox">
<div class="wechatQrImgBox">
<Spin fix v-if="qrCodeLoading"></Spin>
<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>
</div>
<div class="wechatQrTip">请使用微信扫码关注/授权完成后点击我已完成绑定检测绑定状态</div>
</div>
</FormItem>
<FormItem v-if="bindForm.type !== 'WECHAT'" label="OpenID/UnionID">
<Input v-model="bindForm.unionID" placeholder="请输入 OpenID/UnionID" />
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
import { getPwdStatus } from '@/api/account';
// import { getPwdStatus } from '@/api/account';
import storage from '@/plugins/storage'
import { bindThirdAccount, getThirdAccountBindList, getWechatH5QrCode, unbindThirdAccount } from '@/api/login.js'
export default {
name: 'AccountSafe',
data () {
return {
pwdStatus: '' // 密码状态
pwdStatus: '',
connectLoading: false,
bindModalVisible: false,
bindSubmitLoading: false,
connectBoundTypes: [],
thirdAccountTypes: [
{ type: 'WECHAT', label: '微信' }
],
qrCodeLoading: false,
qrCodeBase64: '',
bindForm: {
type: '',
typeLabel: '',
unionID: ''
}
}
},
computed: {
wechatConnect () {
return this.thirdAccountTypes[0]
},
qrCodeSrc () {
if (!this.qrCodeBase64) return ''
if (this.qrCodeBase64.startsWith('data:image')) return this.qrCodeBase64
return `data:image/png;base64,${this.qrCodeBase64}`
},
bindModalOkText () {
if (this.bindForm.type === 'WECHAT') return '我已完成绑定'
return '确定'
}
},
mounted () {
this.getPwdStatus()
this.refreshConnectBinds()
},
methods: {
// 设置支付密码
@@ -40,21 +131,140 @@ export default {
this.$router.push({name: 'ModifyPwd', query: { status: 1 }})
},
// 获取密码状态
getPwdStatus () {
getPwdStatus().then(res => {
if (res) {
this.pwdStatus = '修改密码'
} else {
this.pwdStatus = '设置密码'
getPwdStatus () {},
// getPwdStatus () {
// getPwdStatus().then(res => {
// if (res) {
// this.pwdStatus = '修改密码'
// } else {
// this.pwdStatus = '设置密码'
// }
// });
// }
isBound (type) {
return this.connectBoundTypes.includes(type)
},
refreshConnectBinds () {
this.connectLoading = true
return getThirdAccountBindList()
.then(res => {
if (res && res.success) {
this.connectBoundTypes = Array.isArray(res.result) ? res.result : []
}
});
})
.finally(() => {
this.connectLoading = false
})
},
openBindModal (item) {
this.bindForm.type = item.type
this.bindForm.typeLabel = item.label
this.bindForm.unionID = ''
this.qrCodeBase64 = ''
this.bindModalVisible = true
if (item.type === 'WECHAT') {
this.fetchWechatQrCode()
}
},
resetBindForm () {
this.bindForm.type = ''
this.bindForm.typeLabel = ''
this.bindForm.unionID = ''
this.qrCodeLoading = false
this.qrCodeBase64 = ''
this.bindSubmitLoading = false
},
fetchWechatQrCode () {
if (this.bindForm.type !== 'WECHAT') return
const rawUserInfo = storage.getItem('userInfo')
const userInfo = rawUserInfo ? JSON.parse(rawUserInfo) : null
const userId = userInfo && userInfo.id ? String(userInfo.id) : ''
if (!userId) {
this.qrCodeBase64 = ''
this.$Message.error('未获取到当前用户ID请重新登录后重试')
return
}
this.qrCodeLoading = true
getWechatH5QrCode({ scene: userId })
.then(res => {
if (res && res.success) {
this.qrCodeBase64 = res.result || ''
return
}
this.qrCodeBase64 = ''
this.$Message.error((res && res.message) || '二维码获取失败')
})
.catch(() => {
this.qrCodeBase64 = ''
})
.finally(() => {
this.qrCodeLoading = false
})
},
submitBind () {
if (this.bindSubmitLoading) return
if (this.bindForm.type === 'WECHAT' && !this.bindForm.unionID) {
this.bindSubmitLoading = true
this.refreshConnectBinds()
.then(() => {
if (this.isBound(this.bindForm.type)) {
this.$Message.success('绑定成功')
this.bindModalVisible = false
this.resetBindForm()
return
}
this.$Message.warning('暂未检测到绑定,请扫码后重试')
this.$nextTick(() => {
this.bindModalVisible = true
})
})
.finally(() => {
this.bindSubmitLoading = false
})
return
}
if (!this.bindForm.type) {
this.$Message.error('绑定类型不能为空')
return
}
if (!this.bindForm.unionID) {
this.$Message.error('OpenID/UnionID 不能为空')
this.$nextTick(() => {
this.bindModalVisible = true
})
return
}
this.bindSubmitLoading = true
bindThirdAccount({ unionID: this.bindForm.unionID, type: this.bindForm.type })
.then(() => {
this.$Message.success('绑定成功')
this.bindModalVisible = false
this.resetBindForm()
this.refreshConnectBinds()
})
.finally(() => {
this.bindSubmitLoading = false
})
},
confirmUnbind (item) {
this.$Modal.confirm({
title: '解绑确认',
content: `<p>确定解绑 ${item.label} 吗?</p>`,
onOk: () => {
unbindThirdAccount({ type: item.type }).then(() => {
this.$Message.success('解绑成功')
this.refreshConnectBinds()
})
}
})
}
}
}
</script>
<style scoped lang="scss">
/deep/ .ivu-col-span-2, .ivu-col-span-4 {
::v-deep .ivu-col-span-2, .ivu-col-span-4 {
text-align: center;
color: $theme_color;
}
@@ -71,9 +281,72 @@ export default {
border-bottom: 1px solid $border_color;
padding: 16px 0;
/deep/ .ivu-col {
::v-deep .ivu-col {
padding: 8px 0;
}
}
.connectList {
position: relative;
margin-top: 8px;
}
.connectRow {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 0;
}
.connectRowLeft {
display: flex;
align-items: center;
gap: 10px;
}
.connectName {
color: $title_color;
}
.wechatQrBox {
width: 100%;
}
.wechatQrImgBox {
position: relative;
width: 180px;
height: 180px;
border: 1px solid $border_color;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
}
.wechatQrImg {
width: 168px;
height: 168px;
object-fit: contain;
}
.wechatQrEmpty {
color: $light_sub_color;
font-size: 12px;
text-align: center;
padding: 0 8px;
}
.wechatQrActions {
margin-top: 10px;
display: flex;
gap: 10px;
}
.wechatQrTip {
margin-top: 10px;
color: $light_sub_color;
font-size: 12px;
line-height: 1.6;
}
</style>

View File

@@ -584,7 +584,7 @@ export default {
margin-left: 25px;
margin-top: 5px
}
/deep/ .ivu-alert-message {
::v-deep .ivu-alert-message {
p {
margin: 4px 0;
}

View File

@@ -0,0 +1,431 @@
<template>
<div class="memberGrade">
<card _Title="我的等级"/>
<div class="summary">
<div class="gradeCard">
<div class="gradeLeft">
<img v-if="currentGrade.gradeImage" class="gradeIcon" :src="currentGrade.gradeImage" alt="等级图标" />
<div class="gradeInfo">
<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>
</span>
</div>
<div class="gradeExp">
当前经验值<span class="num">{{ currentExperience || 0 }}</span>
</div>
<div class="gradeNext" v-if="nextGrade">
{{ nextGrade.gradeName }} 还差 <span class="num">{{ diffExperience }}</span> 经验值
</div>
<div class="gradeNext" v-else>已达最高等级</div>
</div>
</div>
</div>
</div>
<Tabs value="rules">
<TabPane label="获取经验值方式" name="rules">
<Table :columns="ruleColumns" :data="ruleList" :loading="rulesLoading">
<template slot-scope="{ row }" slot="ruleItem">
<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>
</div>
<div class="ruleDesc">{{ getRuleDesc(row.ruleKey) }}</div>
<div class="ruleMeta">
<span class="metaItem">单次获得<b>+{{ row.value == null ? 0 : row.value }}</b></span>
<span v-if="row.maxValue != null && Number(row.maxValue) > 0" class="metaItem">限额<b>{{ row.maxValue }}</b></span>
<span class="metaItem">已获得<b>{{ row.gainedExperience == null ? 0 : row.gainedExperience }}</b></span>
</div>
</div>
</template>
</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">
<span>{{ getRuleName(row.ruleKey) }}</span>
</template>
<template slot-scope="{ row }" slot="delta">
<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"
: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>
</div>
</template>
<script>
import { memberGradeCurrent, memberGradeList, memberGradeRules } from '@/api/member.js'
export default {
name: 'MemberGrade',
data () {
return {
ruleList: [],
rulesLoading: false,
rulesDescription: '',
/** 接口返回 List MemberGradeDetailVO每项 { grade, benefits } */
gradeDetails: [],
gradeList: [],
currentGrade: {},
currentExperience: 0,
currentGradeId: '',
selectedRuleKey: '',
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
}
]
}
},
computed: {
rulesMap () {
const map = {}
this.ruleList.forEach(item => {
map[item.ruleKey] = item.ruleName
})
return map
},
sortedGradeList () {
const list = Array.isArray(this.gradeList) ? this.gradeList.slice() : []
list.sort((a, b) => {
const sa = a && a.gradeSort != null ? Number(a.gradeSort) : 0
const sb = b && b.gradeSort != null ? Number(b.gradeSort) : 0
if (sa !== sb) return sa - sb
const ea = a && a.requiredExperience != null ? Number(a.requiredExperience) : 0
const eb = b && b.requiredExperience != null ? Number(b.requiredExperience) : 0
return ea - eb
})
return list
},
currentGradeIndex () {
if (!this.currentGradeId) return -1
return this.sortedGradeList.findIndex(g => g && g.id === this.currentGradeId)
},
nextGrade () {
const idx = this.currentGradeIndex
if (idx < 0) return null
return this.sortedGradeList[idx + 1] || null
},
diffExperience () {
if (!this.nextGrade || this.nextGrade.requiredExperience == null) return 0
const diff = Number(this.nextGrade.requiredExperience) - Number(this.currentExperience || 0)
return diff > 0 ? diff : 0
},
/** 当前等级在列表详情中对应的启用权益(顺序与后台 benefits 一致) */
currentGradeBenefits () {
const id = this.currentGradeId
if (!id || !Array.isArray(this.gradeDetails) || !this.gradeDetails.length) return []
const detail = this.gradeDetails.find(
(d) => d && d.grade && String(d.grade.id) === String(id)
)
const list = detail && Array.isArray(detail.benefits) ? detail.benefits : []
return list.filter((b) => b && b.benefitState === 'OPEN')
},
},
mounted () {
this.init()
},
methods: {
init () {
this.loadRules()
this.loadGradeList()
this.loadCurrent()
},
loadRules () {
this.rulesLoading = true
memberGradeRules()
.then(res => {
if (res && res.success) {
const items = res.result && Array.isArray(res.result.items) ? res.result.items : []
this.rulesDescription = (res.result && res.result.description) || ''
this.ruleList = items
}
})
.finally(() => {
this.rulesLoading = false
})
},
loadGradeList () {
this.gradeLoading = true
memberGradeList()
.then(res => {
if (res && res.success) {
const raw = Array.isArray(res.result) ? res.result : []
this.gradeDetails = raw
.map((item) => {
if (!item) return null
if (item.grade != null) {
return {
grade: item.grade,
benefits: Array.isArray(item.benefits) ? item.benefits : []
}
}
return { grade: item, benefits: [] }
})
.filter(Boolean)
this.gradeList = this.gradeDetails.map((d) => d.grade).filter(Boolean)
} else {
this.gradeDetails = []
this.gradeList = []
}
})
.finally(() => {
this.gradeLoading = false
})
},
loadCurrent () {
this.logLoading = true
const params = {
pageNumber: this.params.pageNumber,
pageSize: this.params.pageSize
}
if (this.selectedRuleKey) params.ruleKey = this.selectedRuleKey
memberGradeCurrent(params)
.then(res => {
if (res && res.success && res.result) {
this.currentGrade = res.result.grade || {}
this.currentExperience = res.result.experience || 0
this.currentGradeId = res.result.gradeId || (this.currentGrade && this.currentGrade.id) || ''
this.experienceLogs = res.result.experienceLogs || {}
}
})
.finally(() => {
this.logLoading = false
})
},
selectRule (ruleKey) {
this.selectedRuleKey = ruleKey || ''
this.params.pageNumber = 1
this.loadCurrent()
},
changePage (val) {
this.params.pageNumber = val
this.loadCurrent()
},
changePageSize (val) {
this.params.pageSize = val
this.params.pageNumber = 1
this.loadCurrent()
},
getRuleName (ruleKey) {
if (!ruleKey) return '-'
return this.rulesMap[ruleKey] || ruleKey
},
getRuleDesc (ruleKey) {
const map = {
CONSUME: '客户消费1元可获取经验值向下取整',
REGISTER: '会员注册成功后可获取经验值',
SIGN_IN: '客户每日签到后可获取的经验值',
COMMENT: '对已购买商品完成发表评价获取经验值仅针对评论字数大于30字的评论进行发放',
SHARE: '会员分享商城页面可获取的经验值',
PROFILE: '完善个人基本信息可获得经验值,每个会员仅可获得一次',
FOLLOW_STORE: '关注店铺可获得经验值每个客户ID同店铺仅第一次关注可进行获得',
BIND_WECHAT: '绑定微信成功后获得经验值,每个会员仅可获得一次',
ADD_ADDRESS: '添加收货地址可获得经验值,每个会员仅可获得一次',
SHARE_REGISTER: '仅被注册成功后可获得相应奖励经验值',
SHARE_BUY: '仅被购买成功后可获得相应奖励经验值'
}
if (!ruleKey) return '-'
return map[ruleKey] || '-'
},
getLogDeltaRaw (row) {
if (!row) return 0
const candidate = row.variableExperience != null ? row.variableExperience : (row.changeExperience != null ? row.changeExperience : (row.experience != null ? row.experience : row.value))
if (candidate == null) return 0
const num = Number(candidate)
if (Number.isNaN(num)) return 0
if (row.type === 'DECREASE') return -Math.abs(num)
if (row.type === 'INCREASE') return Math.abs(num)
return num
},
getLogDeltaSign (row) {
const num = this.getLogDeltaRaw(row)
return num >= 0 ? '+' : '-'
},
getLogDeltaAbsValue (row) {
const num = this.getLogDeltaRaw(row)
return Math.abs(num)
},
getLogDeltaColor (row) {
return this.getLogDeltaRaw(row) >= 0 ? 'green' : 'red'
}
}
}
</script>
<style scoped lang="scss">
h3 {
font-size: 16px;
margin: 20px 10px;
}
.summary {
margin-bottom: 20px;
}
.rulesDesc {
margin: 10px 0 0 0;
padding: 10px 12px;
border: 1px solid $border_color;
border-radius: 4px;
background: #fff;
color: $light_sub_color;
font-size: 12px;
line-height: 1.7;
}
.gradeCard {
padding: 16px;
border: 1px solid $border_color;
border-radius: 6px;
background: #fff;
background-repeat: no-repeat;
background-size: cover;
}
.gradeLeft {
display: flex;
align-items: flex-start;
gap: 12px;
}
.gradeIcon {
width: 48px;
height: 48px;
object-fit: contain;
border-radius: 4px;
background: #fff;
}
.gradeNameRow {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
line-height: 1.4;
}
.gradeNameText {
font-size: 16px;
color: $light_title_color;
font-weight: 600;
}
.benefitTagsInline {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 8px;
vertical-align: middle;
}
.benefitTag {
margin: 0 !important;
}
.gradeExp,
.gradeNext {
margin-top: 6px;
font-size: 12px;
color: #000;
}
.num {
color: $theme_color;
font-size: 16px;
}
.ruleItem {
padding: 10px 8px;
}
.ruleTop {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.ruleName {
display: flex;
align-items: center;
gap: 8px;
color: $light_title_color;
font-size: 14px;
}
.ruleDesc {
margin-top: 6px;
color: $light_sub_color;
font-size: 12px;
line-height: 1.6;
}
.ruleMeta {
margin-top: 8px;
display: flex;
flex-wrap: wrap;
gap: 16px;
color: $light_sub_color;
font-size: 12px;
}
.metaItem b {
color: $theme_color;
font-weight: 600;
}
</style>

View File

@@ -54,14 +54,40 @@ export default {
},
mounted () {
this.formItem = JSON.parse(storage.getItem('userInfo'))
this.formItem.birthday = this.normalizeBirthday(this.formItem.birthday)
this.accessToken.accessToken = storage.getItem('accessToken');
},
methods: {
normalizeBirthday (value) {
if (!value) return ''
if (value instanceof Date) return value
if (typeof value === 'number') return new Date(value)
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]))
const d = new Date(value)
return isNaN(d.getTime()) ? '' : d
}
return ''
},
formatBirthday (value) {
if (!value) return null
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
return m ? `${m[1]}-${m[2]}-${m[3]}` : null
}
const d = value instanceof Date ? value : new Date(value)
if (isNaN(d.getTime())) return null
const yyyy = d.getFullYear()
const mm = String(d.getMonth() + 1).padStart(2, '0')
const dd = String(d.getDate()).padStart(2, '0')
return `${yyyy}-${mm}-${dd}`
},
save () { // 保存
this.$refs.form.validate(valid => {
if (valid) {
let params = {
birthday: this.$options.filters.unixToDate(this.formItem.birthday / 1000, 'yyyy-MM-dd'),
birthday: this.formatBirthday(this.formItem.birthday),
face: this.formItem.face,
nickName: this.formItem.nickName,
sex: this.formItem.sex

View File

@@ -95,12 +95,43 @@
</div>
<div class="order-card" v-if="order.order.payStatus === 'PAID'">
<h3>发票信息</h3>
<template v-if="order.order.needReceipt && order.receipt">
<p>发票抬头:{{ order.receipt.receiptTitle }}</p>
<p>发票内容{{ order.receipt.receiptContent }}</p>
<!-- 以 li_receipt 为准needReceipt 可能为 null/false 导致有 receipt 仍显示「未开发票」 -->
<template v-if="hasValidReceipt()">
<p>发票类型{{ formatReceiptType(order.receipt) }}</p>
<p>发票抬头:{{ formatReceiptHeaderType(order.receipt) }}</p>
<p v-if="isPersonalReceipt(order.receipt) && order.receipt.personalName">
个人名称:{{ order.receipt.personalName }}
</p>
<p v-if="order.receipt.companyName">
单位名称:{{ order.receipt.companyName }}
</p>
<p v-if="order.receipt.taxpayerId">
纳税人识别号:{{ order.receipt.taxpayerId }}
</p>
<p v-if="order.receipt.companyAddress">
单位地址:{{ order.receipt.companyAddress }}
</p>
<p v-if="order.receipt.companyPhone">
单位电话:{{ order.receipt.companyPhone }}
</p>
<p v-if="order.receipt.bankName">
开户银行:{{ order.receipt.bankName }}
</p>
<p v-if="order.receipt.bankAccount">
银行账号:{{ order.receipt.bankAccount }}
</p>
<p>发票内容:{{ order.receipt.receiptContent }}</p>
<p v-if="order.receipt.receiptPhone">
收票人手机:{{ order.receipt.receiptPhone }}
</p>
<p v-if="order.receipt.receiptEmail">
收票人邮箱:{{ order.receipt.receiptEmail }}
</p>
<div v-if="Number(order.receipt.receiptStatus) === 1" class="receipt-action">
<Button size="small" type="primary" ghost @click="viewReceiptInvoice(order.receipt)">
查看发票
</Button>
</div>
</template>
<div v-else style="color: #999; margin-left: 5px">未开发票</div>
</div>
@@ -307,7 +338,7 @@ import {
cancelOrder,
getPackage
} from "@/api/order.js";
import { afterSaleReason } from "@/api/member";
import { afterSaleReason, receiptDetail } from "@/api/member";
export default {
name: "order-detail",
data() {
@@ -328,6 +359,51 @@ export default {
};
},
methods: {
isVatSpecialReceipt (receipt) {
if (!receipt) return false;
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "2" || rt === "增值税专用发票" || receipt.invoiceKind === "VAT_SPECIAL") {
return true;
}
return false;
},
isPersonalReceipt (receipt) {
return !this.isVatSpecialReceipt(receipt);
},
formatReceiptType (receipt) {
if (!receipt) return "";
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "电子普通发票" || rt === "增值税专用发票") return rt;
return this.isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
},
formatReceiptHeaderType (receipt) {
if (!receipt) return "";
if (this.isVatSpecialReceipt(receipt)) return "单位";
if (receipt.taxpayerId) return "单位";
return "个人";
},
hasValidReceipt () {
const receipt = this.order && this.order.receipt;
if (!receipt) return false;
const content = receipt.receiptContent ? String(receipt.receiptContent).trim() : '';
const title = receipt.receiptTitle ? String(receipt.receiptTitle).trim() : '';
const taxpayerId = receipt.taxpayerId ? String(receipt.taxpayerId).trim() : '';
// 后端有时会返回占位 receipt如 receiptContent=不开发票),这里过滤掉
if (content === '不开发票') return false;
return Boolean(content || title || taxpayerId);
},
getInvoiceAddress (receipt) {
if (!receipt) return "";
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
},
viewReceiptInvoice (receipt) {
const invoiceAddress = this.getInvoiceAddress(receipt);
if (!invoiceAddress) {
this.$Message.warning("暂无发票附件");
return;
}
window.open(invoiceAddress, "_blank");
},
// 退款状态枚举
refundPriceList(status) {
switch (status) {
@@ -359,11 +435,21 @@ export default {
});
window.open(routeUrl.href, "_blank");
},
getDetail() {
async getDetail() {
// 获取订单详情
orderDetail(this.$route.query.sn).then((res) => {
orderDetail(this.$route.query.sn).then(async (res) => {
if (res.success) {
this.order = res.result;
const receiptId =
(res.result.order && res.result.order.receiptId) ||
res.result.receiptId ||
(res.result.receipt && res.result.receipt.id);
if (receiptId) {
const receiptRes = await receiptDetail(receiptId);
if (receiptRes && receiptRes.success && receiptRes.result) {
this.$set(this.order, "receipt", receiptRes.result);
}
}
this.progressList = res.result.orderLogs;
if (this.order.order.deliveryMethod === 'LOGISTICS') {
this.getOrderPackage(this.order.order.sn);
@@ -604,15 +690,15 @@ table {
.layui-layer-wrap > .div-express-log {
max-height: 300px;
}
/deep/ .layui-layer-wrap > .div-express-log::-webkit-scrollbar{
::v-deep .layui-layer-wrap > .div-express-log::-webkit-scrollbar{
width: 1px;
height: 5px;
}
/deep/ .layui-layer-wrap > .div-express-log::-webkit-scrollbar-thumb{
::v-deep .layui-layer-wrap > .div-express-log::-webkit-scrollbar-thumb{
border-radius: 1em;
background-color: rgba(50,50,50,.3);
}
/deep/ .layui-layer-wrap > .div-express-log::-webkit-scrollbar-track{
::v-deep .layui-layer-wrap > .div-express-log::-webkit-scrollbar-track{
border-radius: 1em;
background-color: rgba(50,50,50,.1);
}
@@ -625,6 +711,10 @@ table {
overflow-x: auto;
}
.receipt-action {
margin-top: 12px;
}
.express-log {
/*margin: 5px -10px 5px 5px;*/
padding: 10px;

View File

@@ -157,8 +157,11 @@
</span></span>
</div>
<div class="inovice-content">
<span>{{ invoiceData.receiptTitle }}</span>
<template v-if="hasInvoiceInfo">
<span>{{ invoiceDisplayTitle }}</span>
<span>{{ invoiceData.receiptContent }}</span>
</template>
<span v-else>不开发票</span>
<span @click="editInvoice">编辑</span>
</div>
</div>
@@ -258,6 +261,28 @@ import { canUseCouponList } from "@/api/member.js";
export default {
name: "Pay",
components: { invoiceModal, addressManage },
computed: {
hasInvoiceInfo() {
const title =
this.invoiceData.personalName ||
this.invoiceData.companyName ||
this.invoiceData.receiptTitle;
const content = this.invoiceData.receiptContent;
if (content && String(content).trim() === "不开发票") return false;
return Boolean(
(title && String(title).trim()) ||
(content && String(content).trim())
);
},
invoiceDisplayTitle() {
return (
this.invoiceData.personalName ||
this.invoiceData.companyName ||
this.invoiceData.receiptTitle ||
""
);
},
},
data() {
return {
selectedStoreAddress: 'm',
@@ -270,8 +295,7 @@ export default {
storeMoreAddr: false,
invoiceData: {
// 发票数据
receiptTitle: "个人",
receiptContent: "不开发票",
//receiptContent: "不开发票",
},
searchForm: {
pageNumber: 1,

View File

@@ -76,6 +76,11 @@ const member = [{
icon: '',
title: '我的积分',
path: 'Point'
},
{
icon: '',
title: '我的等级',
path: 'MemberGrade'
}
],
display: true

View File

@@ -68,6 +68,8 @@ const ComplainDetail = (resolve) =>
require(["@/pages/home/memberCenter/ComplainDetail"], resolve);
const Point = (resolve) =>
require(["@/pages/home/memberCenter/Point"], resolve);
const MemberGrade = (resolve) =>
require(["@/pages/home/memberCenter/MemberGrade"], resolve);
const MsgList = (resolve) =>
require(["@/pages/home/memberCenter/memberMsg/MsgList"], resolve);
const MsgDetail = (resolve) =>
@@ -326,6 +328,12 @@ export default new Router({
component: Point,
meta: { title: "我的积分" },
},
{
path: "MemberGrade",
name: "MemberGrade",
component: MemberGrade,
meta: { title: "我的等级" },
},
{
path: "Profile",
name: "Profile",

View File

@@ -70,7 +70,7 @@ module.exports = {
loaderOptions: {
sass: {
data: `@import "@/assets/styles/global.scss";` //全局加载scss
additionalData: `@import "@/assets/styles/global.scss";` //全局加载scss
},
// 向 CSS 相关的 loader 传递选项
less: {

View File

@@ -24,6 +24,7 @@
"vue-cropper": "^0.5.5",
"vue-prism-editor": "^0.5.1",
"vue-router": "^3.4.9",
"vue-virtual-scroller": "^1.1.2",
"vuex": "^3.5.1"
},
"devDependencies": {

View File

@@ -121,13 +121,23 @@ export default {
// })
// },
getGoodsDetail () {
if(this.toUser.storeFlag){
// 检查必要参数是否存在
if (!this.toUser.storeFlag) {
return
}
if (!this.goodsParams || !this.goodsParams.goodsId) {
console.warn('getGoodsDetail: goodsParams 或 goodsId 参数缺失')
return
}
ServeGetGoodsDetail(this.goodsParams).then(res => {
if (res.success) {
this.goodsDetail = res.result.data
}
}).catch(error => {
console.error('获取商品详情失败:', error)
})
}
},
getFootPrint() {
if (this.toUser.storeFlag) {

View File

@@ -33,6 +33,11 @@ Object.keys(filters).forEach((key) => {
Vue.filter(key, filters[key]);
});
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
Vue.component('RecycleScroller', VueVirtualScroller.RecycleScroller)
// 引入自定义全局css
import '@/assets/css/global.less'

View File

@@ -65,9 +65,14 @@
> -->
</p>
<!-- 对话列表 -->
<template v-if="loadStatus === 1">
<div v-for="(item, index) in userTalkItem" :key="item.id" class="talk-item pointer"
:class="{ active: activeIndex == index }" @click="clickTab(item.userId, item, index)">
<RecycleScroller
:item-size="64"
:items="userTalkItem"
:prerender="10"
v-slot="{ item, index }"
>
<div v-bind:key="item.id" class="talk-item pointer" :class="{ active: activeIndex == index }" @click="clickTab(item.userId, item, index)">
<div class="avatar-box">
<face :text="item.face" v-if="item.face"></face>
<face-null :text="item.name" v-else></face-null>
@@ -79,9 +84,7 @@
<div class="title">
<div class="card-name">
<p class="nickname">
{{
item.remark_name ? item.remark_name : item.name
}}
{{ item.remark_name ? item.remark_name : item.name }}
</p>
<div v-show="item.unread" class="larkc-tag">
{{ item.unread }}条未读
@@ -89,11 +92,9 @@
<div v-show="item.is_top" class="larkc-tag top">
TOP
</div>
<div v-show="item.is_robot" class="larkc-tag top">
BOT
</div>
<div v-show="item.talk_type == 2" class="larkc-tag group">
群组
</div>
@@ -111,9 +112,7 @@
<span v-if="item.lastMessageType === 'ORDER'">[订单链接]</span>
</div>
<div class="content">
<template v-if="
index_name != item.index_name && item.draft_text
">
<template v-if="index_name != item.index_name && item.draft_text">
<span class="draft-color">[草稿]</span>
<span>{{ item.draft_text }}</span>
</template>
@@ -124,13 +123,12 @@
</span>
<span v-else>[群消息]</span>
</template>
<span>{{ item.msg_text }}</span>
</template>
</div>
</div>
</div>
</template>
</RecycleScroller>
</el-main>
</el-scrollbar>
</el-container>
@@ -151,6 +149,8 @@
</div>
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { mapGetters, mapState } from "vuex";
import MainLayout from "@/views/layout/MainLayout";
import WelcomeModule from "@/components/layout/WelcomeModule";
@@ -179,6 +179,7 @@ export default {
UserSearch,
OtherLink,
WelcomeModule,
RecycleScroller
},
data () {
return {
@@ -288,6 +289,8 @@ export default {
beforeRouteUpdate (to, from, next) {
let index_name = getCacheIndexName();
if (index_name) this.clickTab(index_name);
// 更新商品参数
this.initGoodsParams(to.query);
next();
},
beforeCreate () {
@@ -296,6 +299,8 @@ export default {
async created () {
await this.initialize();
await this.loadUserSetting();
// 初始化商品参数
this.initGoodsParams(this.$route.query);
/**
* 如果说有id 说明是用户点击 “联系客服” 进入的该页面
* 所以创建会话 并请求用户列表
@@ -316,6 +321,16 @@ export default {
// 美化时间格式
beautifyTime,
// 初始化商品参数
initGoodsParams (query) {
this.goodsParams = {
goodsId: query.goodsId || '',
skuId: query.skuId || '',
};
console.log('初始化商品参数:', this.goodsParams);
},
//创建会话
async createTalk (id) {
await ServeCreateTalkList(id);

1
manager/.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=false

1
manager/.yarnrc Normal file
View File

@@ -0,0 +1 @@
--ignore-engines true

View File

@@ -1,29 +1,5 @@
# LILISHOP-UI
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
详情点击 [https://cli.vuejs.org/zn/config/](https://cli.vuejs.org/zn/config/).

View File

@@ -9,6 +9,9 @@
"build": "vue-cli-service build",
"dev": "vue-cli-service serve"
},
"engines": {
"node": ">=14"
},
"dependencies": {
"@amap/amap-jsapi-loader": "0.0.7",
"@antv/g2": "^4.1.12",
@@ -16,8 +19,9 @@
"core-js": "^3.6.5",
"dplayer": "^1.26.0",
"js-cookie": "^2.2.1",
"node-sass": "^4.14.1",
"sass-loader": "^8.0.2",
"price-color": "1.0.2",
"sass": "^1.63.6",
"sass-loader": "^10.4.1",
"sockjs-client": "^1.4.0",
"swiper": "^6.3.5",
"uuid": "^8.3.2",
@@ -31,8 +35,7 @@
"vue-router": "^3.1.3",
"vuedraggable": "^2.23.2",
"vuex": "^3.4.0",
"xss": "^1.0.7",
"price-color":"1.0.2"
"xss": "^1.0.7"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.4.4",
@@ -48,5 +51,10 @@
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-cli-plugin-style-resources-loader": "^0.1.4",
"vue-template-compiler": "^2.6.10"
},
"resolutions": {
"minimatch": "^3.1.2",
"node-sass": "npm:sass@^1.63.6",
"@achrinza/node-ipc": "9.2.2"
}
}

View File

@@ -32,6 +32,26 @@ export const getCategoryBrandListData = (category_id, params) => {
export const saveCategoryBrand = (category_id, params) => {
return postRequest(`/goods/categoryBrand/${category_id}`, params)
}
// 根据品牌id获取关联分类
export const getBrandCategoryListData = (brand_id, params) => {
return getRequest(`/goods/categoryBrand/${brand_id}`, params)
}
// 保存品牌分类关联
export const saveBrandCategory = (brand_id, categoryIds) => {
return postRequest(`/goods/categoryBrand/${brand_id}`, categoryIds, {
"Content-Type": "application/json"
})
}
export const getParameterCategoryListData = (parameter_id, params) => {
return getRequest(`/goods/parameters/category/${parameter_id}`, params)
}
export const saveParameterCategory = (parameter_id, categoryIds) => {
return postRequest(`/goods/parameters/category/${parameter_id}`, categoryIds, {
"Content-Type": "application/json"
})
}
//保存获取关联规格
export const saveCategorySpec = (category_id, params) => {
return postRequest(`/goods/categorySpec/${category_id}`, params)
@@ -101,31 +121,76 @@ export const getGoodsCategory = (parent_id) => {
}
// 上架商品
export const upGoods = (id, params) => {
return putRequest(`/goods/goods/${id}/up`, params)
}
// 下架商品
export const lowGoods = (id, params) => {
return putRequest(`/goods/goods/${id}/under`, params)
}
export const upGoods = (params) => {
return putRequest(`/goods/goods/up`, params)
}
// 下架商品
export const lowGoods = (params) => {
return putRequest(`/goods/goods/under`, params)
}
// 获取商品sku分页列表
export const getGoodsSkuData = (params) => {
return getRequest('/goods/goods/sku/list', params)
}
// 设置单个商品规格虚拟销量
export const updateGoodsSkuVirtualSales = (skuId, params) => {
return putRequest(`/goods/goods/virtualSales/${skuId}`, params)
}
// 批量设置商品规格虚拟销量
export const batchUpdateGoodsSkuVirtualSales = (params) => {
return putRequest('/goods/goods/virtualSales', params, {
"Content-Type": "application/json"
})
}
// 获取商品数量
export const getGoodsNumerData = (params) => {
return getRequest('/goods/goods/goodsNumber', params)
}
// 获取商品分页列表
export const getGoodsListData = (params) => {
return getRequest('/goods/goods/list', params)
}
// 商品分组分页
export const getGoodsGroupByPage = (params) => {
return getRequest('/goods/goodsGroup/getByPage', params)
}
// 商品分组详情
export const getGoodsGroup = (id) => {
return getRequest(`/goods/goodsGroup/get/${id}`)
}
// 新增商品分组
export const addGoodsGroup = (params) => {
return postRequest('/goods/goodsGroup', params)
}
// 修改商品分组
export const updateGoodsGroup = (id, params) => {
return putRequest(`/goods/goodsGroup/update/${id}`, params)
}
// 删除商品分组
export const deleteGoodsGroup = (id) => {
return deleteRequest(`/goods/goodsGroup/delete/${id}`)
}
// 设定商品分组(批量)
export const addGoodsGroupItems = (groupId, goodsIds) => {
return postRequest(`/goods/goodsGroup/${groupId}/goods`, {
goodsIds: Array.isArray(goodsIds) ? goodsIds.join(",") : goodsIds,
})
}
// 获取待审核商品分页列表
export const getAuthGoodsListData = (params) => {
return getRequest('/goods/goods/auth/list', params)
}
// 审核商品
export const authGoods = (id, params) => {
return putRequest(`/goods/goods/${id}/auth`, params)
export const authGoods = (params) => {
return putRequest(`/goods/goods/auth`, params,{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
}
//查询分类绑定参数信息
@@ -133,17 +198,28 @@ export const getCategoryParamsListData = (id, params) => {
return getRequest(`/goods/categoryParameters/${id}`, params)
}
// 参数组分页列表
export const getCategoryParametersGroupPage = (params) => {
return getRequest(`/goods/categoryParameters`, params)
}
//查询商品绑定参数信息
export const getCategoryParamsByGoodsId = (goodsId, categoryId) => {
return getRequest(`/goods/parameters/${goodsId}/${categoryId}`)
}
export const getGoodsParamsPage = (params) => {
return getRequest(`/goods/parameters`, params)
}
export const getGoodsParamsDetail = (id, params) => {
return getRequest(`/goods/parameters/${id}`, params)
}
//保存参数
export const insertGoodsParams = (params) => {
return postRequest('/goods/parameters', params)
export const insertGoodsParams = (params, headers) => {
return postRequest('/goods/parameters', params, headers)
}
//更新参数
export const updateGoodsParams = (params) => {
return putRequest('/goods/parameters', params)
export const updateGoodsParams = (params, headers) => {
return putRequest('/goods/parameters', params, headers)
}
//删除参数
export const deleteParams = (id, params) => {

View File

@@ -111,7 +111,7 @@ export const changeMobile = (params) => {
};
// 获取用户数据 多条件
export const getUserListData = (params) => {
return getRequest("/passport/user", params);
return getRequest("/passport/user/getByCondition", params);
};
// 通过用户名搜索
export const searchUserByName = (username, params) => {
@@ -368,6 +368,31 @@ export const setSetting = (key, params) => {
return putRequestWithNoForm(`/setting/setting/put/${key}`, params);
};
// 微信视频号小店类目(三级)
export const getWxChannelsThirdCategory = (params) => {
return getRequest(`/wxchannels/category/third`, params);
};
// 微信视频号商品分页
export const getWxChannelsGoodsPage = (params) => {
return getRequest(`/wxchannels/goods`, params);
};
// 微信视频号订单分页
export const getWxChannelsOrderPage = (params) => {
return getRequest(`/wxchannels/order`, params);
};
// 微信视频号概况
export const getWxChannelsOverviewSummary = (params) => {
return getRequest(`/wxchannels/overview/summary`, params);
};
// 微信视频号退单分页
export const getWxChannelsRefundPage = (params) => {
return getRequest(`/wxchannels/refund`, params);
};
// 分页查询敏感词
export const getSensitiveWordsPage = (params) => {
@@ -445,6 +470,21 @@ export const getProgress = () => {
return getRequest(`/other/elasticsearch/progress`);
};
// 删除ES中下架的商品
export const deleteGoodsDown = () => {
return getRequest(`/other/elasticsearch/deleteGoodsDown`);
};
// 删除不存在的索引
export const delSkuIndex = () => {
return getRequest(`/other/elasticsearch/delSkuIndex`);
};
// 生成所有商品的缓存
export const generateGoodsCache = () => {
return getRequest(`/other/elasticsearch/cache`);
};
// 分页查询自定义分词
export const getCustomWordsPage = (params) => {
return getRequest(`/other/customWords/page`, params);

View File

@@ -30,6 +30,11 @@ export const updateMemberReview = (id, params) => {
return getRequest(`/member/evaluation/updateStatus/${id}`, params);
};
// 修改评价置顶状态
export const updateMemberReviewTop = (id, params) => {
return putRequest(`/member/evaluation/updateTop/${id}`, params);
};
// 添加或修改
export const insertOrUpdateSpec = (params) => {
return postRequest("/memberNoticeSenter/insertOrUpdate", params);
@@ -117,6 +122,9 @@ export const getMemberNum = (params) => {
export const getHistoryPointData = (params) => {
return getRequest(`/member/memberPointsHistory/getByPage`, params);
};
export const queryMemberPointsStatistics = () => {
return getRequest(`/member/memberPointsHistory/queryMemberPointsStatistics`);
};
//查询会员的收货地址
export const getMemberAddressData = (id, params) => {
return getRequest(`/member/address/${id}`, params);
@@ -137,3 +145,86 @@ export const editMemberAddress = (params) => {
export const getMemberWallet = (params) => {
return getRequest(`/wallet/wallet`, params);
};
export const increaseMemberWallet = (params) => {
return putRequest(`/wallet/wallet/increase`, params);
};
export const updateMemberPoint = (params) => {
return putRequest(`/passport/member/updateMemberPoint`, params);
};
export const getMemberGroupByPage = (params) => {
return getRequest("/member/memberGroup/getByPage", params);
};
export const getMemberGroup = (id) => {
return getRequest(`/member/memberGroup/get/${id}`);
};
export const addMemberGroup = (params) => {
return postRequest(`/member/memberGroup`, params);
};
export const updateMemberGroup = (id, params) => {
return putRequest(`/member/memberGroup/update/${id}`, params);
};
export const deleteMemberGroup = (id) => {
return deleteRequest(`/member/memberGroup/delete/${id}`);
};
export const addMemberGroupUsers = (groupId, memberIds) => {
return postRequest(`/member/memberGroup/${groupId}/users`, {
memberIds: Array.isArray(memberIds) ? memberIds.join(",") : memberIds,
});
};
// 客户等级
export const getMemberGradeByPage = (params) => {
return getRequest("/member/memberGrade/getByPage");
};
export const getMemberGrade = (id) => {
return getRequest(`/member/memberGrade/get/${id}`);
};
export const addMemberGrade = (params) => {
return postRequest(`/member/memberGrade`, params);
};
export const updateMemberGrade = (id, params) => {
return putRequest(`/member/memberGrade/update/${id}`, params);
};
export const updateMemberGradeState = (id, state) => {
return putRequest(`/member/memberGrade/state/${id}?state=${state}`);
};
export const deleteMemberGrade = (id) => {
return deleteRequest(`/member/memberGrade/delete/${id}`);
};
export const getMemberExperienceByPage = (params) => {
return getRequest(`/member/memberGrade/experience/getByPage`, params);
};
// 客户权益
export const getMemberBenefitByPage = (params) => {
return getRequest("/member/benefit/getByPage", params);
};
export const getMemberBenefit = (id) => {
return getRequest(`/member/benefit/get/${id}`);
};
export const addMemberBenefit = (params) => {
return postRequest("/member/benefit", params);
};
export const updateMemberBenefit = (id, params) => {
return putRequest(`/member/benefit/update/${id}`, params);
};
export const updateMemberBenefitState = (id, state) => {
return putRequest(`/member/benefit/state/${id}?state=${state}`);
};
export const deleteMemberBenefit = (id) => {
return deleteRequest(`/member/benefit/delete/${id}`);
};
/** 客户权益类型枚举(管理端) */
export const getMemberBenefitTypes = () => {
return getRequest("/member/benefit/types");
};

View File

@@ -160,3 +160,13 @@ export const refundLog = (params) => {
export const storeAddress = (sn) => {
return getRequest(`/order/afterSale/getStoreAfterSaleAddress/${sn}`)
}
// 获取订单数量统计
export const getOrderNum = (params) => {
return getRequest(`/order/order/orderNum`, params)
}
// 获取售后数量统计
export const getAfterSaleNumVO = (params) => {
return getRequest(`/order/afterSale/afterSaleNumVO`, params)
}

View File

@@ -114,5 +114,5 @@ export const getPrivacy = (type) => {
}
//修改隐私协议数据
export const updatePrivacy = (id,type,params) => {
return putRequest(`/other/article/updateArticle/${type}?id=${id}`, params, {"Content-Type": "application/json"})
return putRequest(`/other/article/updateArticle/${type}/${id}`, params, {"Content-Type": "application/json"})
}

View File

@@ -40,7 +40,7 @@ export default {
params: {
// 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
storeName: "",
},
dateList: [

View File

@@ -57,11 +57,11 @@ export default {
};
</script>
<style scoped lang="scss">
/deep/ .ivu-modal {
::v-deep .ivu-modal {
overflow: hidden;
height: 650px !important;
}
/deep/ .ivu-modal-body {
::v-deep .ivu-modal-body {
width: 100%;
height: 500px;
overflow: hidden;

View File

@@ -86,11 +86,11 @@ export default {
};
</script>
<style scoped lang="scss">
/deep/ .ivu-modal {
::v-deep .ivu-modal {
overflow: hidden;
height: 650px !important;
}
/deep/ .ivu-modal-body {
::v-deep .ivu-modal-body {
width: 100%;
height: 500px;
overflow: hidden;

View File

@@ -122,11 +122,11 @@ export default {
width: 100%;
}
/deep/ .ivu-modal {
::v-deep .ivu-modal {
overflow: hidden;
height: 650px !important;
}
/deep/ .ivu-modal-body {
::v-deep .ivu-modal-body {
width: 100%;
height: 500px;
overflow: hidden;

View File

@@ -42,12 +42,12 @@
}
}
/deep/ .ivu-scroll-container {
::v-deep .ivu-scroll-container {
width: 100% !important;
height: 400px !important;
}
/deep/ .ivu-scroll-content {
::v-deep .ivu-scroll-content {
/* */
display: flex;
flex-wrap: wrap;
@@ -81,7 +81,7 @@
align-items: center;
margin: 10px;
/deep/ img {
::v-deep img {
width: 60px;
height: 60px;
text-align: center;

View File

@@ -70,7 +70,7 @@ export default {
params: {
// 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
},
pintuanColumns: [
{
@@ -101,12 +101,12 @@ export default {
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
// type: this.index == params.index ? "primary" : "",
type: 'default',
size: "small",
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -194,12 +194,12 @@ export default {
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
// type: this.index == params.index ? "primary" : "",
type: 'default',
size: "small",
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -246,12 +246,12 @@ export default {
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
// type: this.index == params.index ? "primary" : "",
type: 'default',
size: "small",
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -420,7 +420,7 @@ img {
overflow: auto;
width: 100%;
}
/deep/ .ivu-table-wrapper {
::v-deep .ivu-table-wrapper {
width: 100%;
}
.list {

View File

@@ -157,7 +157,7 @@ export default {
text-align: center;
transition: 0.35s;
cursor: pointer;
/deep/ p {
::v-deep p {
margin: 10px 0;
}
border: 1px solid #ededed;

View File

@@ -33,7 +33,7 @@ export default {
};
</script>
<style lang="scss" scoped>
/deep/ .ivu-card-body {
::v-deep .ivu-card-body {
height: 414px;
overflow: auto;
}
@@ -69,11 +69,11 @@ export default {
height: 416px;
overflow: hidden;
}
/deep/ .ivu-table {
::v-deep .ivu-table {
height: 300px !important;
overflow: auto;
}
/deep/ .ivu-card-body {
::v-deep .ivu-card-body {
padding: 0;
height: auto;
}

View File

@@ -45,7 +45,7 @@ export default {
order: "desc",
pageClientType: "H5",
pageNumber: 1,
pageSize: 10,
pageSize: 20,
pageType: "SPECIAL",
},
total: 0, // 表单数据总数
@@ -137,7 +137,7 @@ img {
overflow: auto;
width: 100%;
}
/deep/ .ivu-table-wrapper {
::v-deep .ivu-table-wrapper {
width: 100%;
}
.list {

View File

@@ -39,14 +39,20 @@
<!--</Upload>-->
</div>
<Modal title="图片预览" v-model="viewImage" :styles="{top: '30px'}" draggable>
<Modal
title="图片预览"
v-model="viewImage"
:styles="{ top: '30px' }"
:z-index="3500"
draggable
>
<img :src="currentValue" alt="该资源不存在" style="max-width: 300px;margin: 0 auto;display: block;" />
<div slot="footer">
<Button @click="viewImage=false">关闭</Button>
</div>
</Modal>
<Modal width="1200px" v-model="picModalFlag">
<Modal width="1200px" v-model="picModalFlag" :z-index="3500">
<ossManage @callback="callbackSelected" ref="ossManage" :isComponent="true" :initialize="picModalFlag" />
</Modal>

View File

@@ -39,7 +39,7 @@ export const otherRouter = {
},
{
path: "category",
title: "类列表",
title: "类列表",
name: "category",
component: () => import("@/views/goods/goods-manage/category.vue")
},
@@ -91,6 +91,12 @@ export const otherRouter = {
name: "goods-category",
component: () => import("@/views/goods/goods-manage/category.vue")
},
{
path: "goods-group",
title: "商品分组",
name: "goods-group",
component: () => import("@/views/goods/group/index.vue")
},
{
path: "goods-parameter",
title: "商品参数",
@@ -98,10 +104,10 @@ export const otherRouter = {
component: () => import("@/views/goods/goods-manage/parameter.vue")
},
{
path: "goods-spec",
title: "商品参数",
name: "goods-spec",
component: () => import("@/views/goods/goods-manage/spec.vue")
path: "goods-parameter-edit",
title: "编辑商品参数",
name: "goods-parameter-edit",
component: () => import("@/views/goods/goods-manage/parameter-edit.vue")
},
{
path: "order-complaint-detail",
@@ -141,6 +147,37 @@ export const otherRouter = {
name: "member-detail",
component: () => import("@/views/member/list/memberDetail.vue")
},
{
path: "member-group",
title: "会员分组",
name: "member-group",
component: () => import("@/views/member/group/index.vue")
},
{
path: "member-grade",
title: "客户等级",
name: "member-grade",
component: () => import("@/views/member/grade/index.vue")
},
{
path: "member-benefit",
title: "客户权益管理",
name: "member-benefit",
component: () => import("@/views/member/benefit/index.vue")
},
{
path: "member-grade-experience",
title: "等级权益设置",
name: "member-grade-experience",
component: () => import("@/views/member/grade/experience-setting.vue")
},
{
path: "member-grade-experience-log",
title: "客户经验值记录",
name: "member-grade-experience-log",
component: () => import("@/views/member/grade/experience-log.vue")
},
{
path: "goods/goods-info/goodsDetail",

View File

@@ -19,10 +19,9 @@
width: 100% !important;
display: flex;
align-items: center;
background-color: #f0f0f0;
// background-color: #f0f0f0;
border-radius: 0.4em;
padding: 10px;
margin: 0;
flex-wrap: wrap;
> .ivu-form-item {
margin: 8px 10px !important;
@@ -42,3 +41,8 @@
-webkit-line-clamp: 4;
overflow: hidden;
}
// 为Card组件之间增加间距
.ivu-card + .ivu-card {
margin-top: 10px;
}

View File

@@ -85,3 +85,36 @@ export function memberPromotionsStatusRender(h, status) {
),
]);
}
/**
* 平台优惠券「活动时间 / 有效期」文案活动获取券effectiveDays 为领取后有效天数)
*/
export function formatPromotionCouponValidityHtml(row) {
if (!row) return "-";
if (row.getType === "ACTIVITY") {
const days = row.effectiveDays;
if (days !== undefined && days !== null && days !== "") {
const n = Number(days);
if (!Number.isNaN(n) && n > 0) {
return `领取后${n}天有效`;
}
}
}
if (row.startTime && row.endTime) {
return `${row.startTime}<br/>${row.endTime}`;
}
if (row.getType === "ACTIVITY" && row.rangeDayType === "DYNAMICTIME") {
return "长期有效";
}
return "-";
}
/** Table 列 render活动时间与 formatPromotionCouponValidityHtml 一致) */
export function promotionsCouponActivityTimeRender(h, params) {
const html = formatPromotionCouponValidityHtml(params.row);
if (html === "-") return h("div", "-");
if (html.indexOf("<br/>") !== -1) {
return h("div", { domProps: { innerHTML: html } });
}
return h("div", html);
}

View File

@@ -20,7 +20,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -69,7 +69,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
words: "",
@@ -124,14 +124,12 @@ export default {
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -142,14 +140,17 @@ export default {
"修改"
),
h(
"Button",
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
props: {
type: "error",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {

View File

@@ -15,13 +15,13 @@
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="状态">
<Select
v-model="searchForm.distributionStatus"
style="width: 200px"
style="width: 240px"
>
<Option
v-for="item in distributionStatusList"
@@ -42,6 +42,8 @@
>
</Form>
</Row>
</Card>
<Card>
<Table
:loading="loading"
border
@@ -57,7 +59,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -138,7 +140,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
},
columns: [
{
@@ -344,7 +346,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 获取列表数据

View File

@@ -15,7 +15,7 @@
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Button
@@ -26,6 +26,8 @@
>
</Form>
</Row>
</Card>
<Card>
<Table
class="mt_10"
:loading="loading"
@@ -41,7 +43,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -63,7 +65,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
@@ -113,17 +115,10 @@ export default {
fixed: "right",
width: 200,
render: (h, params) => {
return h("div", [
return h("div", { class: "ops" }, [
h(
"Button",
"a",
{
props: {
type: "success",
size: "small",
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.audit(params.row, "PASS");
@@ -132,13 +127,10 @@ export default {
},
"通过"
),
h("span", {}, "|"),
h(
"Button",
"a",
{
props: {
type: "error",
size: "small",
},
on: {
click: () => {
this.audit(params.row, "REFUSE");
@@ -174,7 +166,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 获取列表数据
@@ -222,3 +214,15 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -5,15 +5,15 @@
<Form-item label="会员名称" class="flex" prop="memberName">
<Input
type="text" placeholder="请输入会员名称" v-model="searchForm.memberName" clearable
style="width: 200px"></Input>
style="width: 240px"></Input>
</Form-item>
<Form-item label="编号" class="flex">
<Input
type="text" placeholder="请输入编号" v-model="searchForm.sn" clearable
style="width: 200px"></Input>
style="width: 240px"></Input>
</Form-item>
<Form-item label="状态"
style="width: 200px">
style="width: 240px">
<Select v-model="searchForm.distributionCashStatus" clearable style="width: 150px">
<Option v-for="item in cashStatusList" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select>
@@ -22,9 +22,11 @@
<Button @click="handleSearch" type="primary">搜索</Button>
</Form-item>
</Form>
</Card>
<Card>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" class="mt_10"></Table>
<Row type="flex" justify="end" class="page padding-row">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10,20,50]" size="small" show-total show-elevator show-sizer></Page>
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[20,50,100]" size="small" show-total show-elevator show-sizer></Page>
</Row>
</Card>
<Modal :title="modalTitle" v-model="modalVisible" :mask-closable='false' :width="500">
@@ -70,7 +72,7 @@ export default {
result: 'FAIL_AUDITING', // 是否通过
searchForm: { // 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
},
@@ -158,18 +160,11 @@ export default {
fixed: "right",
width: 130,
render: (h, params) => {
if(params.row.distributionCashStatus != 'APPLY'){
return h("div", [
if (params.row.distributionCashStatus != 'APPLY') {
return h("div", { class: "ops" }, [
h(
"Button",
"a",
{
props: {
type: "primary",
size: "small",
},
style: {
marginRight: "5px"
},
on: {
click: () => {
this.view(params.row);
@@ -178,20 +173,12 @@ export default {
},
"查看"
),
]);
}else {
return h("div", [
} else {
return h("div", { class: "ops" }, [
h(
"Button",
"a",
{
props: {
type: "primary",
size: "small",
},
style: {
marginRight: "5px"
},
on: {
click: () => {
this.edit(params.row);
@@ -200,7 +187,6 @@ export default {
},
"审核"
),
]);
}
}
@@ -228,7 +214,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 获取列表数据
@@ -310,3 +296,15 @@ export default {
}
};
</script>
<style lang="scss" scoped>
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -4,10 +4,12 @@
<Form @keydown.enter.native.prevent="handleSearch" ref="searchForm" :model="searchForm" inline :label-width="70"
class="search-form">
<Form-item label="商品名称" prop="goodsName">
<Input type="text" v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 240px" />
</Form-item>
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
</Form>
</Card>
<Card>
<Row class="operation" style="margin:10px 0;">
<Button @click="delAll" type="primary">批量下架</Button>
</Row>
@@ -30,7 +32,7 @@
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage"
@on-page-size-change="changePageSize" :page-size-opts="[10,20,50]" size="small" show-total show-elevator
@on-page-size-change="changePageSize" :page-size-opts="[20,50,100]" size="small" show-total show-elevator
show-sizer></Page>
</Row>
</Card>
@@ -51,7 +53,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
},
@@ -135,14 +137,10 @@ export default {
fixed: "right",
minWidth: 100,
render: (h, params) => {
return h("div", [
return h("div", { class: "ops" }, [
h(
"Button",
"a",
{
props: {
type: "error",
size: "small",
},
on: {
click: () => {
this.remove(params.row);
@@ -178,7 +176,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 清除选中状态
@@ -254,5 +252,17 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -8,7 +8,7 @@
v-model="searchForm.orderSn"
placeholder="请输入订单编号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="分销商" prop="distributionName">
@@ -17,21 +17,23 @@
v-model="searchForm.distributionName"
placeholder="请输入分销商名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="店铺名称">
<Select v-model="searchForm.storeId" placeholder="请选择" @on-query-change="searchChange" filterable
clearable style="width: 150px">
clearable style="width: 240px">
<Option v-for="item in shopList" :value="item.id" :key="item.id">{{ item.storeName }}</Option>
</Select>
</Form-item>
<Form-item label="订单时间">
<DatePicker type="daterange" v-model="timeRange" format="yyyy-MM-dd" placeholder="选择时间"
style="width: 210px"></DatePicker>
style="width: 240px"></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
</Form>
</Card>
<Card>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" class="mt_10">
<template slot-scope="{row}" slot="goodsMsg">
<div class="goods-msg">
@@ -56,7 +58,7 @@
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize"
@on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10,20,50]"
@on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[20,50,100]"
size="small" show-total show-elevator show-sizer></Page>
</Row>
</Card>
@@ -85,7 +87,7 @@
loading: true, // 表单加载状态
searchForm: { // 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort:"create_time",
order:"desc"
},
@@ -162,7 +164,7 @@
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 获取列表数据
@@ -190,7 +192,7 @@
getShopList(val) { // 获取店铺列表 搜索用
const params = {
pageNumber: 1,
pageSize: 10,
pageSize: 20,
storeName: ''
}
if (val) {
@@ -208,7 +210,7 @@
},
filterStatus (status) { // 过滤订单状态
const arr = [
{status: 'WAIT_BILL', title: '待结算'},
{status: 'NO_COMPLETED', title: '未完成'},
{status: 'COMPLETE', title: '完成'},
{status: 'REFUND', title: '退款'},
]
@@ -217,21 +219,20 @@
return arr[i].title;
}
}
return '未完成'; // 默认返回未完成
},
filterStatusColor (status) { // 状态tag标签颜色
const arr = [
{status: 'WAIT_BILL', color: 'blue'},
{status: 'WAIT_CASH', color: 'orange'},
{status: 'COMPLETE_CASH', color: 'green'},
{status: 'CANCEL', color: 'red'},
{status: 'NO_COMPLETED', color: 'red'},
{status: 'REFUND', color: 'magenta'},
{status: 'NO_COMPLETED', color: 'orange'},
{status: 'COMPLETE', color: 'green'},
{status: 'REFUND', color: 'red'},
]
for (let i=0;i<arr.length;i++) {
if (arr[i].status === status) {
return arr[i].color;
}
}
return 'orange'; // 默认返回橙色
}
},
mounted() {

View File

@@ -3,7 +3,6 @@
<Card>
<Row class="operation">
<Button @click="add" type="primary">添加</Button>
<Button @click="refresh">刷新</Button>
<Button @click="delAll">批量删除</Button>
</Row>
<Table
@@ -22,7 +21,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -71,7 +70,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
name: "",
@@ -122,18 +121,17 @@ export default {
title: "操作",
key: "action",
align: "center",
fixed: "right",
width: 200,
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -144,14 +142,17 @@ export default {
"修改"
),
h(
"Button",
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
props: {
type: "error",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -188,7 +189,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 清除选中
@@ -285,15 +286,7 @@ export default {
},
});
},
// 刷新
refresh() {
this.loading = true;
setTimeout(() => {
this.getDataList();
this.loading = false;
this.$Message.success("刷新成功");
}, 500);
},
// 全部删除
delAll() {
if (this.selectCount <= 0) {

View File

@@ -15,7 +15,7 @@
v-model="searchForm.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="商品编号" prop="id">
@@ -24,7 +24,7 @@
v-model="searchForm.id"
placeholder="请输入商品编号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="店铺名称" prop="id">
@@ -33,26 +33,15 @@
v-model="searchForm.storeName"
placeholder="请输入店铺名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="状态" prop="status">
<Select
v-model="searchForm.marketEnable"
placeholder="请选择"
clearable
style="width: 200px"
>
<Option value="UPPER">上架</Option>
<Option value="DOWN">下架</Option>
</Select>
</Form-item>
<Form-item label="销售模式" prop="status">
<Select
v-model="searchForm.salesModel"
placeholder="请选择"
clearable
style="width: 200px"
style="width: 240px"
>
<Option value="RETAIL">零售</Option>
<Option value="WHOLESALE">批发</Option>
@@ -63,12 +52,23 @@
v-model="searchForm.goodsType"
placeholder="请选择"
clearable
style="width: 200px"
style="width: 240px"
>
<Option value="PHYSICAL_GOODS">实物商品</Option>
<Option value="VIRTUAL_GOODS">虚拟商品</Option>
</Select>
</Form-item>
<Form-item label="商品分组" prop="groupId">
<Select
v-model="searchForm.groupId"
placeholder="请选择商品分组"
clearable
filterable
style="width: 240px"
>
<Option v-for="item in goodsGroupList" :value="item.id" :key="item.id">{{ item.groupName }}</Option>
</Select>
</Form-item>
<Button
@click="handleSearch"
class="search-btn"
@@ -77,47 +77,78 @@
>搜索</Button
>
</Form>
</Card>
<Card>
<div class="goods-tab">
<Tabs v-model="currentStatus" @on-click="goodsStatusClick">
<TabPane v-for="(item,index) in goodsStatusWithCount" :key="index" :label="item.title" :name="item.value">
</TabPane>
</Tabs>
</div>
<!-- 批量操作按钮 -->
<div class="batch-operations" style="margin: 10px 0;">
<Button
type="primary"
:disabled="selectedRows.length === 0"
@click="openSetGoodsGroup"
style="margin-right: 10px;"
>
批量设置分组
</Button>
<Button
type="success"
:disabled="selectedRows.length === 0"
@click="batchUpper"
style="margin-right: 10px;"
>
批量上架
</Button>
<Button
type="warning"
:disabled="selectedRows.length === 0"
@click="batchLower"
style="margin-right: 10px;"
>
批量下架
</Button>
<Button
v-if="currentStatus === 'TOBEAUDITED'"
type="primary"
:disabled="selectedRows.length === 0"
@click="batchAudit"
>
批量审核
</Button>
</div>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
ref="table"
class="mt_10"
@on-select="onSelect"
@on-select-all="onSelectAll"
@on-selection-change="onSelectionChange"
>
<!-- 商品栏目格式化 -->
<template slot="goodsSlot" slot-scope="{ row }">
<div style="margin: 5px 0px; height: 80px; display: flex">
<div style="">
<!-- 商品图片格式化 -->
<template slot="imageSlot" slot-scope="{ row }">
<div style="margin-top: 5px;">
<img
:src="row.original"
style="height: 60px; margin-top: 1px; width: 60px"
style="height: 50px; width: 50px; object-fit: cover;"
/>
</div>
</template>
<div style="margin-left: 13px">
<!-- 商品栏目格式化 -->
<template slot="goodsSlot" slot-scope="{ row }">
<div style="margin: 5px 0px; padding: 10px 0px;">
<div class="div-zoom">
<a @click="linkTo(row.id, row.skuId)">{{ row.goodsName }}</a>
</div>
<Poptip trigger="hover" title="扫码在手机中查看" transfer>
<div slot="content">
<vue-qr
:text="wapLinkTo(row.id, row.skuId)"
:margin="0"
colorDark="#000"
colorLight="#fff"
:size="150"
></vue-qr>
</div>
<img
src="../../../assets/qrcode.svg"
class="hover-pointer"
width="20"
height="20"
alt=""
/>
</Poptip>
</div>
</div>
</template>
</Table>
@@ -128,7 +159,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -154,11 +185,82 @@
>
</div>
</Modal>
<Modal
title="商品审核"
v-model="auditModalVisible"
:mask-closable="false"
:width="500"
>
<Form ref="auditForm" :model="goodsAuditForm" :label-width="100">
<FormItem label="审核结果" prop="auth_flag">
<RadioGroup v-model="goodsAuditForm.auth_flag">
<Radio :label="1">审核通过</Radio>
<Radio :label="2">审核拒绝</Radio>
</RadioGroup>
</FormItem>
<!-- <FormItem label="审核备注" prop="reason" v-if="goodsAuditForm.auth_flag === 2">
<Input v-model="goodsAuditForm.reason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
</FormItem> -->
</Form>
<div slot="footer">
<Button type="text" @click="auditModalVisible = false">取消</Button>
<Button type="primary" @click="confirmAudit">提交审核</Button>
</div>
</Modal>
<!-- 批量审核弹框 -->
<Modal
title="批量商品审核"
v-model="batchAuditModalVisible"
:mask-closable="false"
:width="500"
>
<Form ref="batchAuditForm" :model="batchAuditForm" :label-width="100">
<FormItem label="审核结果" prop="auth_flag">
<RadioGroup v-model="batchAuditForm.auth_flag">
<Radio :label="1">审核通过</Radio>
<Radio :label="2">审核拒绝</Radio>
</RadioGroup>
</FormItem>
<FormItem label="审核备注" prop="reason" v-if="batchAuditForm.auth_flag === 2">
<Input v-model="batchAuditForm.reason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
</FormItem>
<FormItem label="选中商品">
<div style="max-height: 200px; overflow-y: auto;">
<Tag v-for="item in selectedRows" :key="item.id" style="margin: 2px;">{{item.goodsName}}</Tag>
</div>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="batchAuditModalVisible = false">取消</Button>
<Button type="primary" @click="submitBatchAudit">提交审核</Button>
</div>
</Modal>
<Modal v-model="goodsGroupFlag" title="批量设置商品分组" width="420">
<Form ref="goodsGroupForm" :model="goodsGroupForm" :rules="goodsGroupRule" :label-width="90">
<FormItem label="商品分组" prop="groupId">
<Select v-model="goodsGroupForm.groupId" clearable filterable style="width: 240px">
<Option v-for="item in goodsGroupList" :value="item.id" :key="item.id">{{ item.groupName }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer">
<Button @click="goodsGroupFlag = false">取消</Button>
<Button type="primary" :loading="goodsGroupLoading" @click="submitSetGoodsGroup">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import { getGoodsListData, upGoods, lowGoods } from "@/api/goods";
import {
getGoodsListData,
getGoodsNumerData,
upGoods,
lowGoods,
authGoods,
getGoodsGroupByPage,
addGoodsGroupItems
} from "@/api/goods";
import vueQr from "vue-qr";
export default {
components: {
@@ -173,36 +275,70 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "create_time", // 默认排序字段
order: "desc", // 默认排序方式
groupId: "",
},
underForm: {
// 下架原因
reason: "",
},
goodsAuditForm: {
// 商品审核表单
auth_flag: 1,
},
auditModalVisible: false, // 审核弹框显示状态
currentAuditGoods: null, // 当前审核的商品
submitLoading: false, // 添加或编辑提交状态
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: "商品ID",
key: "id",
width: 180,
tooltip: true,
},
{
title: "商品图片",
key: "original",
width: 180,
slot: "imageSlot",
},
{
title: "商品名称",
key: "goodsName",
minWidth: 180,
slot: "goodsSlot",
},
{
title: "商品编号",
key: "id",
minWidth: 150,
tooltip: true,
},
{
title: "价格",
key: "price",
width: 130,
width: 100,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.price,color:this.$mainColor}} );
},
},
{
title: "销量",
key: "buyCount",
width: 100,
render: (h, params) => {
return h("span", params.row.buyCount || 0);
},
},
{
title: "库存",
key: "quantity",
width: 100,
render: (h, params) => {
return h("span", params.row.quantity || 0);
},
},
{
title: "销售模式",
key: "salesModel",
@@ -220,7 +356,7 @@ export default {
{
title: "商品类型",
key: "goodsType",
width: 130,
width: 120,
render: (h, params) => {
if (params.row.goodsType === "PHYSICAL_GOODS") {
return h("Tag", { props: { color: "green" } }, "实物商品");
@@ -234,7 +370,7 @@ export default {
{
title: "状态",
key: "marketEnable",
width: 100,
width: 120,
render: (h, params) => {
if (params.row.marketEnable == "DOWN") {
return h("Tag", { props: { color: "volcano" } }, "下架");
@@ -246,7 +382,7 @@ export default {
{
title: "审核状态",
key: "authFlag",
width: 130,
width: 180,
render: (h, params) => {
if (params.row.authFlag == "TOBEAUDITED") {
return h("Tag", { props: { color: "volcano" } }, "待审核");
@@ -260,7 +396,7 @@ export default {
{
title: "店铺名称",
key: "storeName",
minWidth: 100,
width: 180, // 使用minWidth替代width
tooltip: true,
},
{
@@ -268,19 +404,61 @@ export default {
key: "action",
align: "center",
fixed: "right",
width: 150,
width: 200,
render: (h, params) => {
if (params.row.marketEnable == "DOWN") {
// 如果是待审核状态,显示审核按钮
if (params.row.authFlag === "TOBEAUDITED") {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
this.openAuditModal(params.row);
},
},
},
"审核"
),
h("span", {
style: {
margin: "0 8px",
color: "#dcdee2"
}
}, "|"),
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
this.showDetail(params.row);
},
},
},
"查看"
),
]);
}
// 原有的上架/下架逻辑
else if (params.row.marketEnable == "DOWN") {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -290,11 +468,19 @@ export default {
},
"上架"
),
h("span", {
style: {
margin: "0 8px",
color: "#dcdee2"
}
}, "|"),
h(
"Button",
"a",
{
props: {
size: "small",
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -308,14 +494,12 @@ export default {
} else {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "error",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -325,11 +509,19 @@ export default {
},
"下架"
),
h("span", {
style: {
margin: "0 8px",
color: "#dcdee2"
}
}, "|"),
h(
"Button",
"a",
{
props: {
size: "small",
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -346,12 +538,47 @@ export default {
],
data: [], // 表单数据
total: 0, // 表单数据总数
currentStatus: '',
goodsNumerData: {},
goodsAuditForm: {
// 商品编辑表单
auth_flag: 1,
},
selectedRows: [], // 选中的行数据
selectAll: false, // 全选状态
batchAuditModalVisible: false, // 批量审核弹框显示状态
batchAuditForm: {
auth_flag: 1,
reason: ''
},
goodsGroupFlag: false,
goodsGroupLoading: false,
goodsGroupList: [],
goodsGroupForm: {
groupId: ""
},
goodsGroupRule: {
groupId: [{ required: true, message: "请选择商品分组", trigger: "change" }]
}
};
},
computed: {
goodsStatusWithCount() {
return [
{title: '全部', value: ''},
{title: `出售中${this.goodsNumerData.upperGoodsNum ? '(' + this.goodsNumerData.upperGoodsNum + ')' : ''}`, value: 'UPPER'},
{title: `仓库中${this.goodsNumerData.downGoodsNum ? '(' + this.goodsNumerData.downGoodsNum + ')' : ''}`, value: 'DOWN'},
{title: `待审核${this.goodsNumerData.auditGoodsNum ? '(' + this.goodsNumerData.auditGoodsNum + ')' : ''}`, value: 'TOBEAUDITED'},
{title: `审核未通过${this.goodsNumerData.refuseGoodsNum ? '(' + this.goodsNumerData.refuseGoodsNum + ')' : ''}`, value: 'REFUSE'}
];
}
},
methods: {
// 初始化数据
init() {
this.getDataList();
this.getNumberData();
this.loadGoodsGroupList();
},
// 分页 改变页码
changePage(v) {
@@ -366,8 +593,9 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
this.getNumberData();
},
// 获取数据
getDataList() {
@@ -380,6 +608,15 @@ export default {
}
});
},
getNumberData() {
// 创建一个不包含goodsStatus字段的搜索参数
const { goodsStatus, ...searchParams } = this.searchForm;
getGoodsNumerData(searchParams).then((res) => {
if (res.success) {
this.goodsNumerData = res.result;
}
})
},
// 编辑
edit(v) {
this.id = v.id;
@@ -392,27 +629,36 @@ export default {
},
// 下架
lower() {
lowGoods(this.id, this.underForm).then((res) => {
let params = {
goodsId: this.id,
reason:this.underForm.reason
};
lowGoods(params).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.modalVisible = false;
this.getDataList();
this.getNumberData(); // 添加这行
}
});
},
// 商家
// 上架
upper(v) {
this.$Modal.confirm({
title: "确认上架",
content: "您确认要上架 " + v.goodsName + " ?",
loading: true,
onOk: () => {
upGoods(v.id).then((res) => {
let params = {
goodsId: v.id
};
upGoods(params).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("上架成功");
this.getDataList();
this.getNumberData(); // 添加这行
}
});
},
@@ -427,9 +673,296 @@ export default {
query: { id: id },
})
},
// 商品状态筛选
goodsStatusClick(item) {
// 根据选择的状态设置搜索条件
if (item === 0) {
// 全部:清除状态筛选
delete this.searchForm.goodsStatus;
} else {
// 其他状态正常赋值
this.searchForm.goodsStatus = item;
}
this.currentStatus = item;
// tab切换时清除选中内容
this.selectedRows = [];
if (this.$refs.table) {
this.$refs.table.selectAll(false);
}
this.getDataList();
},
examine(v, authFlag) {
// 审核商品
let examine = "通过";
this.goodsAuditForm.authFlag = "PASS";
if (authFlag != 1) {
examine = "拒绝";
this.goodsAuditForm.authFlag = "REFUSE";
}
this.$Modal.confirm({
title: "确认审核",
content: "您确认要审核" + examine + " " + v.goodsName + " ?",
loading: true,
onOk: () => {
this.goodsAuditForm.goodsIds=v.id;
let formData = new FormData();
formData.append('goodsIds', v.id);
formData.append('authFlag', this.goodsAuditForm.authFlag);
authGoods(formData).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("审核成功");
this.getDataList();
this.getNumberData();
}
});
},
});
},
// 打开审核弹框
openAuditModal(goods) {
this.currentAuditGoods = goods;
this.goodsAuditForm.auth_flag = 1;
this.goodsAuditForm.reason = '';
this.auditModalVisible = true;
},
// 确认审核(二次确认)
confirmAudit() {
const auditText = this.goodsAuditForm.auth_flag === 1 ? '通过' : '拒绝';
this.$Modal.confirm({
title: '确认审核',
content: `您确认要审核${auditText} "${this.currentAuditGoods.goodsName}" 吗?`,
loading: true,
onOk: () => {
this.submitAudit();
},
});
},
// 提交审核
submitAudit() {
let formData = new FormData();
formData.append('goodsIds', this.currentAuditGoods.id);
formData.append('authFlag', this.goodsAuditForm.auth_flag === 1 ? 'PASS' : 'REFUSE');
authGoods(formData).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success('审核成功');
this.auditModalVisible = false;
this.getDataList();
this.getNumberData();
}
});
},
// 选择框事件处理
onSelect(selection, row) {
// 单行选择时触发
},
onSelectAll(selection) {
// 全选时触发
},
onSelectionChange(selection) {
this.selectedRows = selection;
},
// 批量上架
batchUpper() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请先选择要上架的商品');
return;
}
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
this.$Modal.confirm({
title: '确认批量上架',
content: `您确认要上架以下商品吗?\n${goodsNames}`,
loading: true,
onOk: () => {
// 提取所有选中商品的ID
const goodsIds = this.selectedRows.map(item => item.id);
const params = {
goodsId: goodsIds // 传递ID数组
};
upGoods(params).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success('批量上架成功');
this.selectedRows = [];
this.selectAll = false;
this.getDataList();
this.getNumberData();
}
}).catch(() => {
this.$Modal.remove();
});
}
});
},
// 批量下架
batchLower() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请先选择要下架的商品');
return;
}
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
this.$Modal.confirm({
title: '确认批量下架',
content: `您确认要下架以下商品吗?\n${goodsNames}`,
loading: true,
onOk: () => {
// 提取所有选中商品的ID
const goodsIds = this.selectedRows.map(item => item.id);
const params = {
goodsId: goodsIds, // 传递ID数组
reason: '批量下架操作' // 可以设置默认下架原因
};
lowGoods(params).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success('批量下架成功');
this.selectedRows = [];
this.selectAll = false;
this.getDataList();
this.getNumberData();
}
}).catch(() => {
this.$Modal.remove();
});
}
});
},
// 批量审核
batchAudit() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请先选择要审核的商品');
return;
}
// 重置批量审核表单
this.batchAuditForm = {
auth_flag: 1,
reason: ''
};
this.batchAuditModalVisible = true;
},
// 提交批量审核
submitBatchAudit() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请先选择要审核的商品');
return;
}
// 如果是拒绝审核,必须填写原因
if (this.batchAuditForm.auth_flag === 2 && !this.batchAuditForm.reason.trim()) {
this.$Message.warning('审核拒绝时必须填写拒绝原因');
return;
}
const actionText = this.batchAuditForm.auth_flag === 1 ? '通过' : '拒绝';
const goodsNames = this.selectedRows.map(item => item.goodsName).join('、');
this.$Modal.confirm({
title: `确认批量审核${actionText}`,
content: `您确认要${actionText}以下商品的审核吗?\n${goodsNames}`,
loading: true,
onOk: () => {
// 提取所有选中商品的ID
const goodsIds = this.selectedRows.map(item => item.id);
let formData = new FormData();
formData.append('goodsId', goodsIds);
formData.append('authFlag', this.batchAuditForm.auth_flag === 1 ? 'PASS' : 'REFUSE');
formData.append('reason', this.batchAuditForm.reason || '');
// 修正直接调用authGoods不传递'batch'参数
authGoods(formData).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success(`批量审核${actionText}成功`);
this.selectedRows = [];
this.selectAll = false;
this.batchAuditModalVisible = false;
this.getDataList();
this.getNumberData();
}
}).catch(() => {
this.$Modal.remove();
});
}
});
},
loadGoodsGroupList() {
getGoodsGroupByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
this.goodsGroupList = res.result.records || [];
}
});
},
openSetGoodsGroup() {
if (this.selectedRows.length === 0) {
this.$Message.warning("请先选择商品");
return;
}
this.goodsGroupFlag = true;
this.goodsGroupLoading = false;
this.goodsGroupForm = { groupId: "" };
this.$nextTick(() => {
if (this.$refs.goodsGroupForm) this.$refs.goodsGroupForm.resetFields();
});
this.loadGoodsGroupList();
},
submitSetGoodsGroup() {
this.$refs.goodsGroupForm.validate((valid) => {
if (!valid) return;
const goodsIds = this.selectedRows.map((item) => item.id);
this.goodsGroupLoading = true;
addGoodsGroupItems(this.goodsGroupForm.groupId, goodsIds).then((res) => {
this.goodsGroupLoading = false;
if (res && res.success) {
this.$Message.success("设置成功");
this.goodsGroupFlag = false;
this.selectedRows = [];
this.selectAll = false;
if (this.$refs.table && this.$refs.table.selectAll) {
this.$refs.table.selectAll(false);
}
this.getDataList();
} else if (res && res.message) {
this.$Message.error(res.message);
}
}).catch(() => {
this.goodsGroupLoading = false;
this.$Message.error("设置失败,请检查权限或后端接口");
});
});
}
},
mounted() {
this.init();
},
};
</script>
<style lang="scss" scoped>
// Tab组件样式
.goods-tab {
::v-deep .ivu-tabs-tab {
font-size: 14px;
}
}
</style>

View File

@@ -15,7 +15,7 @@
v-model="searchForm.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="商品编号" prop="id">
@@ -24,13 +24,15 @@
v-model="searchForm.id"
placeholder="请输入商品编号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Button @click="handleSearch" class="search-btn" type="primary" icon="ios-search"
>搜索</Button
>
</Form>
</Card>
<Card>
<Table
:loading="loading"
border
@@ -64,7 +66,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -88,7 +90,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "create_time", // 默认排序字段
order: "desc", // 默认排序方式
},
@@ -169,13 +171,12 @@ export default {
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "success",
size: "small",
},
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
marginRight: "5px",
},
on: {
@@ -187,13 +188,17 @@ export default {
"通过"
),
h(
"Button",
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
props: {
type: "error",
size: "small",
},
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
marginRight: "5px",
},
on: {
@@ -205,11 +210,17 @@ export default {
"拒绝"
),
h(
"Button",
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
props: {
type: 'default',
size: "small",
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -245,7 +256,7 @@ export default {
handleSearch() {
// 搜索
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
getDataList() {
@@ -274,7 +285,11 @@ export default {
content: "您确认要审核" + examine + " " + v.goodsName + " ?",
loading: true,
onOk: () => {
authGoods(v.id, this.goodsAuditForm).then((res) => {
let formData = new FormData();
formData.append('goodsIds', v.id);
formData.append('authFlag', this.goodsAuditForm.authFlag);
authGoods(formData).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("审核成功");

View File

@@ -4,18 +4,19 @@
<Form ref="searchForm" @submit.native.prevent @keydown.enter.native="handleSearch" :model="searchForm" inline :label-width="70"
class="search-form">
<Form-item label="品牌名称">
<Input type="text" v-model="searchForm.name" placeholder="请输入品牌名称" clearable style="width: 200px"/>
<Input type="text" v-model="searchForm.name" placeholder="请输入品牌名称" clearable style="width: 240px"/>
</Form-item>
<Button @click="handleSearch" type="primary">搜索</Button>
</Form>
</Card>
<Card>
<Row class="operation padding-row">
<Button @click="add" type="primary">添加</Button>
<Button @click="refresh">刷新</Button>
</Row>
<Table :loading="loading" border :columns="columns" :data="data" ref="table"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage"
@on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small"
@on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]" size="small"
show-total show-elevator show-sizer></Page>
</Row>
</Card>
@@ -25,7 +26,14 @@
<Input v-model="form.name" clearable style="width: 100%"/>
</FormItem>
<FormItem label="品牌图标" prop="logo">
<upload-pic-input v-model="form.logo" style="width: 100%"></upload-pic-input>
<div style="display: flex; align-items: center; gap: 12px;">
<img
:src="form.logo || defaultPic"
alt="品牌图标"
style="width: 80px; height: 60px; object-fit: contain; border: 1px solid #dcdee2; border-radius: 4px; background: #fff;"
/>
<Button type="text" @click="openLogoPicker">修改</Button>
</div>
</FormItem>
</Form>
<div slot="footer">
@@ -33,6 +41,34 @@
<Button type="primary" :loading="submitLoading" @click="handleSubmit">提交</Button>
</div>
</Modal>
<Modal width="1200px" v-model="picModelFlag" footer-hide>
<ossManage @callback="callbackSelected" :isComponent="true" :initialize="picModelFlag" ref="ossManage" />
</Modal>
<Modal
:title="categoryModalTitle"
v-model="categoryModalVisible"
:mask-closable="false"
:width="700"
>
<div style="position: relative; max-height: 520px; overflow: auto;">
<Spin size="large" fix v-if="categoryTreeLoading"></Spin>
<Tree
ref="categoryTree"
:key="categoryTreeKey"
:data="categoryTreeData"
show-checkbox
@on-check-change="onCategoryTreeCheckChange"
></Tree>
</div>
<div slot="footer">
<Button type="text" @click="categoryModalVisible = false">取消</Button>
<Button type="primary" :loading="categorySubmitLoading" @click="submitBrandCategory"
>提交</Button
>
</div>
</Modal>
</div>
</template>
@@ -43,26 +79,39 @@ import {
updateBrand,
disableBrand,
delBrand,
getCategoryTree,
getBrandCategoryListData,
saveBrandCategory,
} from "@/api/goods";
import uploadPicInput from "@/components/lili/upload-pic-input";
import ossManage from "@/views/sys/oss-manage/ossManage";
import {regular} from "@/utils";
export default {
name: "brand",
components: {
uploadPicInput
ossManage
},
data() {
return {
defaultPic: require("@/assets/default.png"),
loading: true, // 表单加载状态
modalType: 0, // 添加或编辑标识
modalVisible: false, // 添加或编辑显示
modalTitle: "", // 添加或编辑标题
picModelFlag: false, // 图片选择器
categoryModalVisible: false,
categoryModalTitle: "关联分类",
categoryTreeLoading: false,
categoryTreeData: [],
categoryTreeKey: 0,
categorySubmitLoading: false,
currentBrandId: "",
selectedCategoryIds: [],
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "create_time", // 默认排序字段
order: "desc", // 默认排序方式
},
@@ -117,11 +166,24 @@ export default {
key: "deleteFlag",
align: "left",
render: (h, params) => {
if (params.row.deleteFlag == 0) {
return h("Tag", {props: {color: "green",},}, "启用");
} else if (params.row.deleteFlag == 1) {
return h("Tag", {props: {color: "volcano",},}, "禁用");
}
return h(
"i-switch",
{
props: {
value: params.row.deleteFlag == 0,
size: "large",
},
on: {
"on-change": (checked) => {
this.handleStatusSwitchChange(params.row, checked);
},
},
},
[
h("span", { slot: "open" }, "启用"),
h("span", { slot: "close" }, "禁用"),
]
);
},
filters: [
{
@@ -145,60 +207,18 @@ export default {
{
title: "操作",
key: "action",
width: 180,
width: 210,
align: "center",
fixed: "right",
render: (h, params) => {
let enableOrDisable = "";
if (params.row.deleteFlag == 0) {
enableOrDisable = h(
"Button",
{
props: {
size: "small",
type: "error",
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.disable(params.row);
},
},
},
"禁用"
);
} else {
enableOrDisable = h(
"Button",
{
props: {
type: "success",
size: "small",
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.enable(params.row);
},
},
},
"启用"
);
}
return h("div", [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -208,17 +228,39 @@ export default {
},
"编辑"
),
enableOrDisable,
h(
"Button",
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
props: {
type: 'default',
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.openCategoryModal(params.row);
},
},
},
"关联分类"
),
h(
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -237,6 +279,100 @@ export default {
};
},
methods: {
openLogoPicker() {
this.$refs.ossManage.selectImage = true;
this.picModelFlag = true;
},
callbackSelected(val) {
this.picModelFlag = false;
this.form.logo = val.url;
},
buildCategoryTreeNodes(list, selectedSet) {
if (!Array.isArray(list) || list.length === 0) return [];
return list.map((item) => {
const children = this.buildCategoryTreeNodes(item.children || [], selectedSet);
return {
id: item.id,
title: item.name,
expand: true,
checked: selectedSet.has(item.id),
children,
};
});
},
async openCategoryModal(row) {
this.currentBrandId = row.id;
this.categoryModalTitle = "关联分类 - " + (row.name || "");
this.categoryModalVisible = true;
this.categoryTreeLoading = true;
this.categoryTreeKey += 1;
this.categoryTreeData = [];
this.selectedCategoryIds = [];
try {
const [treeRes, bindRes] = await Promise.all([
getCategoryTree(),
getBrandCategoryListData(row.id),
]);
const selectedIds = Array.isArray(bindRes?.result)
? bindRes.result
.map((x) => (typeof x === "string" ? x : x && x.id))
.filter(Boolean)
: [];
this.selectedCategoryIds = selectedIds;
const selectedSet = new Set(selectedIds);
this.categoryTreeData = treeRes && treeRes.success
? this.buildCategoryTreeNodes(treeRes.result || [], selectedSet)
: [];
} finally {
this.categoryTreeLoading = false;
}
},
onCategoryTreeCheckChange(checkedNodes) {
if (!Array.isArray(checkedNodes)) {
this.selectedCategoryIds = [];
return;
}
this.selectedCategoryIds = checkedNodes
.map((node) => node && node.id)
.filter(Boolean);
},
submitBrandCategory() {
if (!this.currentBrandId) return;
this.categorySubmitLoading = true;
saveBrandCategory(
this.currentBrandId,
(this.selectedCategoryIds || []).map((id) => String(id))
).then((res) => {
this.categorySubmitLoading = false;
if (res && res.success) {
this.$Message.success("操作成功");
this.categoryModalVisible = false;
return;
}
}).catch(() => {
this.categorySubmitLoading = false;
});
},
handleStatusSwitchChange(row, checked) {
const disable = !checked;
this.$Modal.confirm({
title: disable ? "确认禁用" : "确认启用",
content: "您确认要" + (disable ? "禁用" : "启用") + "品牌 " + row.name + " ?",
loading: true,
onOk: () => {
disableBrand(row.id, { disable }).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
}
this.getDataList();
});
},
onCancel: () => {
this.$nextTick(() => this.$forceUpdate());
},
});
},
// 删除品牌
async delBrand(id) {
let res = await delBrand(id);
@@ -263,7 +399,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 获取数据
@@ -315,15 +451,7 @@ export default {
delete this.form.id;
this.modalVisible = true;
},
// 刷新
refresh() {
this.loading = true;
setTimeout(() => {
this.getDataList();
this.loading = false;
this.$Message.success("刷新成功");
}, 500);
},
// 编辑
edit(v) {
this.modalType = 1;
@@ -340,40 +468,6 @@ export default {
this.form = data;
this.modalVisible = true;
},
// 启用品牌
enable(v) {
this.$Modal.confirm({
title: "确认启用",
content: "您确认要启用品牌 " + v.name + " ?",
loading: true,
onOk: () => {
disableBrand(v.id, {disable: false}).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
// 禁用
disable(v) {
this.$Modal.confirm({
title: "确认禁用",
content: "您确认要禁用品牌 " + v.name + " ?",
loading: true,
onOk: () => {
disableBrand(v.id, {disable: true}).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
},
mounted() {
this.init();

View File

@@ -14,55 +14,11 @@
:columns="columns"
>
<template slot="action" slot-scope="scope">
<Dropdown v-show="scope.row.level == 2" trigger="click">
<Button size="small">
绑定
<Icon type="ios-arrow-down"></Icon>
</Button>
<DropdownMenu slot="list">
<DropdownItem @click.native="brandOperation(scope.row)"
>编辑绑定品牌</DropdownItem
>
<DropdownItem @click.native="specOperation(scope.row)"
>编辑绑定规格</DropdownItem
>
<DropdownItem @click.native="parameterOperation(scope.row)"
>编辑绑定参数</DropdownItem
>
</DropdownMenu>
</Dropdown>
&nbsp;
<Dropdown trigger="click">
<Button size="small">
操作
<Icon type="ios-arrow-down"></Icon>
</Button>
<DropdownMenu slot="list">
<DropdownItem @click.native="edit(scope.row)">编辑</DropdownItem>
<DropdownItem
v-if="scope.row.deleteFlag == 1"
@click.native="enable(scope.row)"
>启用</DropdownItem
>
<DropdownItem
v-if="scope.row.deleteFlag == 0"
@click.native="disable(scope.row)"
>禁用</DropdownItem
>
<DropdownItem @click.native="remove(scope.row)">删除</DropdownItem>
</DropdownMenu>
</Dropdown>
&nbsp;
<Button
v-show="scope.row.level != 2"
type="primary"
@click="addChildren(scope.row)"
size="small"
icon="md-add"
style="margin-right: 5px"
>添加子分类
</Button>
<div class="ops">
<a class="ops-link" @click="edit(scope.row)">编辑</a>
<a class="ops-link" @click="remove(scope.row)">删除</a>
<a v-show="scope.row.level != 2" class="ops-link" @click="addChildren(scope.row)">添加子分类</a>
</div>
</template>
<template slot="commissionRate" slot-scope="scope">
@@ -72,12 +28,17 @@
</template>
<template slot="deleteFlag" slot-scope="{ row }">
<Tag
:class="{ ml_10: row.deleteFlag }"
:color="row.deleteFlag == false ? 'success' : 'error'"
>
{{ row.deleteFlag == false ? "正常启用" : "禁用" }}</Tag
<i-switch
size="large"
v-model="row.deleteFlag"
:true-value="false"
:false-value="true"
:loading="row._statusLoading"
@on-change="onStatusSwitchChange(row, $event)"
>
<span slot="open">开启</span>
<span slot="close">关闭</span>
</i-switch>
</template>
</Table>
@@ -272,6 +233,52 @@ export default {
};
},
methods: {
normalizeCategoryTree(list) {
if (!Array.isArray(list) || list.length === 0) return;
list.forEach((item) => {
if (!item || typeof item !== "object") return;
if (item.deleteFlag === 0) item.deleteFlag = false;
else if (item.deleteFlag === 1) item.deleteFlag = true;
else item.deleteFlag = !!item.deleteFlag;
if (Array.isArray(item.children) && item.children.length) {
this.normalizeCategoryTree(item.children);
}
});
},
onStatusSwitchChange(row, nextDeleteFlag) {
const previousDeleteFlag = !nextDeleteFlag;
const isClosing = nextDeleteFlag === true;
this.$Modal.confirm({
title: isClosing ? "确认关闭" : "确认开启",
content:
"您是否要" +
(isClosing ? "关闭" : "开启") +
"当前分类 " +
row.name +
" 及其子分类?",
loading: true,
okText: "是",
cancelText: "否",
onOk: () => {
this.$set(row, "_statusLoading", true);
disableCategory(row.id, { enableOperations: isClosing ? true : 0 }).then(
(res) => {
this.$Modal.remove();
this.$set(row, "_statusLoading", false);
if (res && res.success) {
this.$Message.success("操作成功");
this.getAllList(0);
return;
}
row.deleteFlag = previousDeleteFlag;
}
);
},
onCancel: () => {
row.deleteFlag = previousDeleteFlag;
},
});
},
// 初始化数据
init() {
this.getAllList(0);
@@ -330,10 +337,6 @@ export default {
}
});
},
// 编辑绑定参数
parameterOperation(v) {
this.$router.push({ name: "parameter", query: { id: v.id } });
},
// 添加子分类
addChildren(v) {
this.modalType = 0;
@@ -434,6 +437,7 @@ export default {
child._loading = false;
child.children = [];
});
this.normalizeCategoryTree(val.children);
// 模拟加载
setTimeout(() => {
callback(val.children);
@@ -442,6 +446,7 @@ export default {
});
} else {
this.deepCategoryChildren(item.id, this.categoryList);
this.normalizeCategoryTree(this.checkedCategoryChildren);
setTimeout(() => {
callback(this.checkedCategoryChildren);
}, 100);
@@ -469,6 +474,7 @@ export default {
this.loading = false;
if (res.success) {
localStorage.setItem("category", JSON.stringify(res.result));
this.normalizeCategoryTree(res.result);
this.categoryList = JSON.parse(JSON.stringify(res.result));
this.tableData = res.result.map((item) => {
if(this.recordLevel[0] && item.id === this.recordLevel[0]) {
@@ -543,11 +549,30 @@ export default {
};
</script>
<style lang="scss" scoped>
/deep/ .ivu-table-wrapper {
::v-deep .ivu-table-wrapper {
overflow: auto;
}
.table {
min-height: 100vh;
height: auto;
}
.ops-link {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops {
display: flex;
flex-wrap: wrap;
}
.ops-link + .ops-link {
margin-left: 16px;
position: relative;
}
.ops-link + .ops-link::before {
content: "|";
position: absolute;
left: -10px;
color: #dcdee2;
}
</style>

View File

@@ -0,0 +1,442 @@
<template>
<div class="search">
<Card>
<Form ref="form" :model="form" :label-width="100" :rules="formValidate">
<FormItem label="参数名称" prop="paramName">
<Input v-model="form.paramName" clearable style="width: 520px" />
</FormItem>
<FormItem label="是否必填" prop="required">
<RadioGroup v-model="form.required">
<Radio :label="0"></Radio>
<Radio :label="1"></Radio>
</RadioGroup>
<span style="margin-left: 10px; color: #999; font-size: 12px"
>商品发布时参数是否必填</span
>
</FormItem>
<FormItem label="是否索引" prop="isIndex">
<RadioGroup v-model="form.isIndex">
<Radio :label="0"></Radio>
<Radio :label="1"></Radio>
</RadioGroup>
<span style="margin-left: 10px; color: #999; font-size: 12px"
>开启索引后用户将可以通过该参数筛选商品索引开关不影响商详页的参数展示</span
>
</FormItem>
<FormItem label="排序" prop="sort">
<InputNumber :min="0" type="number" v-model="form.sort" style="width: 520px" />
</FormItem>
<FormItem label="参数值" prop="options">
<Table :columns="optionColumns" :data="form.options" border size="small" style="width: 520px" />
<div style="margin-top: 10px">
<Button type="primary" @click="addOptionRow">新增</Button>
</div>
</FormItem>
<FormItem label="关联分类">
<Button type="default" @click="openCategoryModal">选择分类</Button>
<span v-if="selectedCategoryNamesText" style="margin-left: 10px; color: #999; font-size: 12px"
>{{ selectedCategoryNamesText }}</span
>
<span v-else style="margin-left: 10px; color: #999; font-size: 12px"
>已选择{{ selectedCategoryIds.length }}个分类</span
>
</FormItem>
<FormItem>
<Button type="default" @click="back">返回</Button>
<Button type="primary" :loading="submitLoading" @click="handleSubmit">保存</Button>
</FormItem>
</Form>
</Card>
<Modal
:title="categoryModalTitle"
v-model="categoryModalVisible"
:mask-closable="false"
:width="700"
>
<div style="position: relative; max-height: 520px; overflow: auto;">
<Spin size="large" fix v-if="categoryTreeLoading"></Spin>
<Tree
:key="categoryTreeKey"
:data="categoryTreeData"
show-checkbox
@on-check-change="onCategoryTreeCheckChange"
></Tree>
</div>
<div slot="footer">
<Button type="text" @click="categoryModalVisible = false">取消</Button>
<Button type="primary" style="margin-left: 8px" @click="categoryModalVisible = false">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import {
getCategoryTree,
getGoodsParamsDetail,
insertGoodsParams,
updateGoodsParams,
} from "@/api/goods";
import { regular } from "@/utils";
const getOptionText = (value) => {
if (value && typeof value === "object") return value.value;
return value;
};
const normalizeOptions = (value) => {
const arr = Array.isArray(value)
? value
: typeof value === "string"
? value.split(",")
: [];
const cleaned = arr
.map((x) => String(getOptionText(x) ?? "").trim())
.filter((x) => x.length > 0);
return Array.from(new Set(cleaned));
};
const validateOptions = (rule, value, callback) => {
const arr = Array.isArray(value)
? value
: typeof value === "string"
? value.split(",")
: [];
const options = normalizeOptions(arr);
if (options.length === 0) {
callback(new Error("请填写参数值"));
return;
}
const joined = options.join(",");
if (!/^.{1,255}$/.test(joined)) {
callback(new Error("超出最大长度限制"));
return;
}
callback();
};
const normalize01 = (value, fallback = 0) => {
const n = Number(value);
if (n === 0 || n === 1) return n;
return fallback;
};
const buildSpringFormPayload = (payload) => {
const out = {};
if (payload && payload.id !== undefined && payload.id !== null && String(payload.id)) {
out.id = String(payload.id);
}
out.paramName = payload && payload.paramName !== undefined && payload.paramName !== null ? String(payload.paramName) : "";
out.options = payload && payload.options !== undefined && payload.options !== null ? String(payload.options) : "";
out.required = payload ? Number(payload.required) : 0;
out.isIndex = payload ? Number(payload.isIndex) : 0;
out.sort = payload ? Number(payload.sort) : 0;
const categoryList = payload && Array.isArray(payload.categoryParameterList) ? payload.categoryParameterList : [];
const categoryIds = categoryList
.map((x) => (x && x.categoryId !== undefined && x.categoryId !== null ? String(x.categoryId) : ""))
.filter((x) => x.length > 0);
categoryIds.forEach((categoryId, index) => {
out[`categoryParameterList[${index}].categoryId`] = categoryId;
});
return out;
};
const validateRadioRequired = (message) => (rule, value, callback) => {
const n = normalize01(value, NaN);
if (!(n === 0 || n === 1)) {
callback(new Error(message));
return;
}
callback();
};
const toStringArray = (arr) => {
if (!Array.isArray(arr)) return [];
return arr.map((x) => String(x)).filter((x) => x.length > 0);
};
const cacheKey = (id) => `goods-parameter-edit:${id}`;
const buildCategoryIdNameMap = (list, map) => {
if (!Array.isArray(list) || list.length === 0) return;
list.forEach((item) => {
if (!item) return;
const id = item.id !== undefined && item.id !== null ? String(item.id) : "";
if (id) map[id] = item.name;
buildCategoryIdNameMap(item.children || [], map);
});
};
export default {
name: "parameterEdit",
data() {
return {
submitLoading: false,
modalType: 0,
categoryModalVisible: false,
categoryModalTitle: "关联分类",
categoryTreeLoading: false,
categoryTreeData: [],
categoryTreeSource: [],
categoryIdNameMap: {},
categoryTreeKey: 0,
selectedCategoryIds: [],
form: {
paramName: "",
options: [{ value: "" }],
required: 0,
isIndex: 0,
sort: 0,
},
optionColumns: [
{
title: "参数值",
key: "value",
minWidth: 420,
render: (h, params) => {
return h("Input", {
props: {
value: params.row.value,
clearable: true,
},
on: {
input: (val) => {
if (!Array.isArray(this.form.options)) this.form.options = [];
if (!this.form.options[params.index]) return;
this.form.options[params.index].value = val;
},
"on-blur": () => {
this.touchOptionsValidate();
},
},
});
},
},
{
title: "操作",
width: 90,
align: "center",
render: (h, params) => {
return h(
"Button",
{
props: {
type: "error",
size: "small",
},
on: {
click: () => this.removeOptionRow(params.index),
},
},
"删除",
);
},
},
],
formValidate: {
paramName: [regular.REQUIRED, regular.VARCHAR5],
options: [{ required: true, validator: validateOptions, trigger: "change" }],
required: [{ required: true, validator: validateRadioRequired("请选择是否必填"), trigger: "change" }],
isIndex: [{ required: true, validator: validateRadioRequired("请选择是否索引"), trigger: "change" }],
sort: [regular.REQUIRED, regular.INTEGER],
},
};
},
computed: {
id() {
return this.$route.query && this.$route.query.id ? String(this.$route.query.id) : "";
},
selectedCategoryNamesText() {
if (!Array.isArray(this.categoryTreeSource) || this.categoryTreeSource.length === 0) return "";
const selectedSet = new Set(toStringArray(this.selectedCategoryIds));
const isSelected = (node) => {
if (!node) return false;
const id = node.id !== undefined && node.id !== null ? String(node.id) : "";
if (id && selectedSet.has(id)) return true;
const children = Array.isArray(node.children) ? node.children : [];
if (children.length === 0) return false;
return children.every(isSelected);
};
const collect = (node, out) => {
if (!node) return;
if (isSelected(node)) {
if (node.name) out.push(node.name);
return;
}
const children = Array.isArray(node.children) ? node.children : [];
children.forEach((c) => collect(c, out));
};
const names = [];
this.categoryTreeSource.forEach((n) => collect(n, names));
return names.join("");
},
},
watch: {
"form.required"(val) {
const n = normalize01(val, 0);
if (val !== n) this.$set(this.form, "required", n);
},
"form.isIndex"(val) {
const n = normalize01(val, 0);
if (val !== n) this.$set(this.form, "isIndex", n);
},
},
methods: {
back() {
this.$router.push({ name: "goods-parameter" });
},
touchOptionsValidate() {
if (this.$refs.form && this.$refs.form.validateField) {
this.$refs.form.validateField("options");
}
},
addOptionRow() {
if (!Array.isArray(this.form.options)) this.form.options = [];
this.form.options.push({ value: "" });
this.touchOptionsValidate();
},
removeOptionRow(index) {
if (!Array.isArray(this.form.options)) this.form.options = [];
this.form.options.splice(index, 1);
this.touchOptionsValidate();
},
buildCategoryTreeNodes(list, selectedSet) {
if (!Array.isArray(list) || list.length === 0) return [];
return list.map((item) => {
const children = this.buildCategoryTreeNodes(item.children || [], selectedSet);
return {
id: item.id,
title: item.name,
expand: true,
checked: selectedSet.has(String(item.id)),
children,
};
});
},
async loadDetail(parameterId) {
if (!parameterId) return;
const res = await getGoodsParamsDetail(parameterId).catch(() => null);
if (!(res && res.success && res.result)) return;
const dto = res.result;
const opts = normalizeOptions(dto.options);
this.form = {
id: dto.id || parameterId,
paramName: dto.paramName || "",
options: opts.map((x) => ({ value: x })),
required: normalize01(dto.required, 0),
isIndex: normalize01(dto.isIndex, 0),
sort: Number(dto.sort ?? 0) || 0,
};
const list = Array.isArray(dto.categoryParameterList) ? dto.categoryParameterList : [];
this.selectedCategoryIds = toStringArray(list.map((x) => x && x.categoryId).filter(Boolean));
},
async openCategoryModal() {
this.categoryModalVisible = true;
this.categoryTreeLoading = true;
this.categoryTreeKey += 1;
try {
const selectedSet = new Set(toStringArray(this.selectedCategoryIds));
if (!Array.isArray(this.categoryTreeSource) || this.categoryTreeSource.length === 0) {
const treeRes = await getCategoryTree();
this.categoryTreeSource = treeRes && treeRes.success ? treeRes.result || [] : [];
const map = {};
buildCategoryIdNameMap(this.categoryTreeSource, map);
this.categoryIdNameMap = map;
}
this.categoryTreeData = this.buildCategoryTreeNodes(this.categoryTreeSource || [], selectedSet);
} finally {
this.categoryTreeLoading = false;
}
},
onCategoryTreeCheckChange(checkedNodes) {
if (!Array.isArray(checkedNodes)) {
this.selectedCategoryIds = [];
return;
}
this.selectedCategoryIds = toStringArray(checkedNodes.map((node) => node && node.id).filter(Boolean));
},
initForm() {
if (this.id) {
this.modalType = 1;
const cached = window.sessionStorage.getItem(cacheKey(this.id));
if (cached) {
try {
const row = JSON.parse(cached);
const opts = normalizeOptions(row.options);
this.form = {
id: row.id,
paramName: row.paramName || "",
options: opts.map((x) => ({ value: x })),
required: normalize01(row.required, 0),
isIndex: normalize01(row.isIndex, 0),
sort: Number(row.sort ?? 0) || 0,
};
} catch (e) {
this.modalType = 0;
}
}
this.loadDetail(String(this.id));
} else {
this.modalType = 0;
if (!Array.isArray(this.form.options) || this.form.options.length === 0) {
this.form.options = [{ value: "" }];
}
this.selectedCategoryIds = [];
}
},
handleSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
const options = normalizeOptions(this.form.options);
const categoryIds = toStringArray(this.selectedCategoryIds);
const payload = {
...this.form,
options: options.join(","),
required: Number(this.form.required),
isIndex: Number(this.form.isIndex),
sort: Number(this.form.sort || 0),
categoryParameterList: categoryIds.map((categoryId) => ({ categoryId })),
};
if (this.modalType === 0) {
delete payload.id;
insertGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
this.$Message.success("操作成功");
this.back();
})
.finally(() => {
this.submitLoading = false;
});
} else {
updateGoodsParams(buildSpringFormPayload(payload))
.then((res) => {
if (!(res && res.success)) return;
this.$Message.success("操作成功");
this.back();
})
.finally(() => {
this.submitLoading = false;
});
}
});
},
},
mounted() {
getCategoryTree().then((res) => {
if (res && res.success) {
this.categoryTreeSource = res.result || [];
const map = {};
buildCategoryIdNameMap(this.categoryTreeSource, map);
this.categoryIdNameMap = map;
}
});
this.initForm();
},
};
</script>
<style lang="scss"></style>

View File

@@ -1,385 +1,202 @@
<template>
<div style="width: 100%">
<div class="search">
<Card>
<Button @click="handleAddParamsGroup" type="primary">添加</Button>
</Card>
<div class="row">
<Card v-if="paramsGroup.length == 0"> 暂无参数绑定信息 </Card>
<div class="paramsGroup" v-else>
<Card
style="width: 350px; margin: 7px"
v-for="(group, index) in paramsGroup"
:key="index"
:bordered="false"
>
<p slot="title">
<Icon type="ios-film-outline"></Icon>&nbsp;{{ group.groupName }}
</p>
<p slot="extra">
<Dropdown slot="extra">
<a href="javascript:void(0)">
操作
<Icon type="ios-arrow-down"></Icon>
</a>
<Dropdown-menu slot="list">
<Dropdown-item @click.native="handleEditParamsGroup(group)"
>编辑</Dropdown-item
>
<Dropdown-item @click.native="handleDeleteParamGroup(group)"
>删除</Dropdown-item
>
</Dropdown-menu>
</Dropdown>
<Icon type="arrow-down-b"></Icon>
</p>
<template v-if="group.params && group.params.length > 0">
<div
v-for="(param, paramId) in group.params"
:key="paramId"
class="params"
>
<span>{{ param.paramName }}</span>
<span>
<i-button type="text" @click="handleEditParams(group, param)"
>编辑</i-button
>
<i-button
type="text"
size="small"
style="color: #f56c6c"
@click="handleDeleteParam(group, param)"
>删除</i-button
>
</span>
</div>
</template>
<div v-else style="align-content: center">暂无数据...</div>
<div style="text-align: center">
<i-button type="text" @click="handleAddParams(group)"
>添加</i-button
>
</div>
</Card>
</div>
</div>
<div>
<Modal
:title="modalTitle"
v-model="dialogParamsVisible"
:mask-closable="false"
:width="500"
>
<Form
ref="paramForm"
:model="paramForm"
:label-width="100"
:rules="formValidate"
>
<FormItem label="参数名称" prop="paramName">
<Input v-model="paramForm.paramName" style="width: 100%" />
</FormItem>
<FormItem label="可选值" prop="options">
<Select
v-model="paramForm.options"
placeholder="输入后回车添加"
multiple
filterable
allow-create
:popper-append-to-body="false"
popper-class="spec-values-popper"
style="width: 100%; text-align: left; margin-right: 10px"
>
<Option
v-for="(item, itemIndex) in ops.options"
:value="item"
:key="itemIndex"
:label="item"
>
{{ item }}
</Option>
</Select>
</FormItem>
<FormItem label="选项" prop="specName3">
<Checkbox label="1" v-model="paramForm.required">必填</Checkbox>
<Checkbox label="1" v-model="paramForm.isIndex">可索引</Checkbox>
</FormItem>
<FormItem label="排序" prop="sort">
<InputNumber
:min="0"
type="number"
v-model="paramForm.sort"
style="width: 100%"
/>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="dialogParamsVisible = false">取消</Button>
<Button
type="primary"
:loading="submitLoading"
@click="submitParamForm"
>提交</Button
>
</div>
</Modal>
</div>
<div>
<Modal
:title="modalTitle"
v-model="dialogParamsGroupVisible"
:mask-closable="false"
:width="500"
>
<Form
ref="searchForm"
@submit.native.prevent
@keydown.enter.native="submitParamGroupForm"
ref="paramGroupForm"
:model="paramGroupForm"
:label-width="100"
:rules="paramGroupValidate"
@keydown.enter.native="handleSearch"
:model="searchForm"
inline
:label-width="70"
class="search-form"
>
<FormItem label="参数名称" prop="groupName">
<Input v-model="paramGroupForm.groupName" style="width: 100%" />
</FormItem>
<Form-item label="参数名称">
<Input
type="text"
v-model="searchForm.paramName"
placeholder="请输入参数名称"
clearable
style="width: 240px"
/>
</Form-item>
<Button @click="handleSearch" type="primary">搜索</Button>
</Form>
</Card>
<div slot="footer">
<Button type="text" @click="dialogParamsGroupVisible = false"
>取消</Button
>
<Button
type="primary"
:loading="submitLoading"
@click="submitParamGroupForm"
>提交</Button
>
</div>
</Modal>
</div>
<Card>
<Row class="operation padding-row">
<Button @click="goAdd" type="primary">添加</Button>
</Row>
<Table :loading="loading" border :columns="columns" :data="data" ref="table"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
</div>
</template>
<script>
import {
getCategoryParamsListData,
insertGoodsParams,
updateGoodsParams,
deleteParams,
insertParamsGroup,
updateParamsGroup,
deleteParamsGroup,
getGoodsParamsPage,
} from "@/api/goods";
import { regular } from "@/utils";
export default {
name: "categoryParams",
data() {
return {
submitLoading: false,
/** 分类ID */
categoryId: this.$route.query.id,
/** 参数组 */
paramsGroup: [],
/** 添加或编辑标识 */
modalType: 0,
/** 添加或编辑标题 */
modalTitle: "",
/** 参数添加或编辑弹出框 */
dialogParamsVisible: false,
/** 参数组添加或编辑弹出框 */
dialogParamsGroupVisible: false,
//参数表单
paramForm: {
sort: 1,
loading: true,
total: 0,
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
paramName: "",
},
/** 参数值 **/
ops: {
options: [],
data: [],
columns: [
{
title: "参数名称",
key: "paramName",
width: 300,
resizable: true,
sortable: false,
},
// 参数表单
paramGroupForm: {},
/** 添加、编辑参数 规格 */
formValidate: {
paramName: [regular.REQUIRED, regular.VARCHAR5],
options: [regular.REQUIRED, regular.VARCHAR255],
sort: [regular.REQUIRED, regular.INTEGER],
{
title: "参数值",
key: "options",
minWidth: 260,
tooltip: true,
},
/** 参数组*/
paramGroupValidate: {
groupName: [regular.REQUIRED, regular.VARCHAR5],
{
title: "必填",
key: "required",
width: 300,
align: "center",
render: (h, params) => {
const val = params.row.required;
const on = val === 1 || val === "1" || val === true;
return h("span", on ? "是" : "否");
},
},
{
title: "可索引",
key: "isIndex",
width: 300,
align: "center",
render: (h, params) => {
const val = params.row.isIndex;
const on = val === 1 || val === "1" || val === true;
return h("span", on ? "是" : "否");
},
},
{
title: "操作",
key: "action",
width: 300,
align: "center",
fixed: "right",
render: (h, params) => {
return h("div", [
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.goEdit(params.row);
},
},
},
"编辑"
),
h("span", { style: { margin: "0 8px", color: "#dcdee2" } }, "|"),
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.remove(params.row);
},
},
},
"删除"
),
]);
},
},
],
};
},
filters: {
paramTypeFilter(val) {
return val === 1 ? "输入项" : "选择项";
},
},
methods: {
// 初始化数据
init() {
this.getDataList();
},
//弹出添加参数框
handleAddParams(group) {
this.paramForm = {
paramName: "",
paramType: 1,
options: "",
required: false,
isIndex: false,
sort: 0,
groupId: group.groupId,
categoryId: this.categoryId,
};
this.modalTitle = "添加参数";
this.modalType = 0;
this.dialogParamsVisible = true;
},
//弹出修改参数框
handleEditParams(group, param) {
console.log(group, param);
this.paramForm = {
paramName: param.paramName,
options: param.options.split(","),
required: param.required == 1 ? true : false,
isIndex: param.isIndex == 1 ? true : false,
groupId: param.groupId || "",
categoryId: param.categoryId || "",
sort: param.sort || 1,
id: param.id,
};
this.ops.options = this.paramForm.options;
this.modalType = 1;
this.modalTitle = "修改参数";
this.dialogParamsVisible = true;
},
//弹出修改参数组框
handleEditParamsGroup(group) {
this.paramGroupForm = {
groupName: group.groupName,
categoryId: this.categoryId,
id: group.groupId,
};
this.modalType = 1;
this.modalTitle = "修改参数组";
this.dialogParamsGroupVisible = true;
},
// 添加参数
handleAddParamsGroup() {
this.paramGroupForm = {};
this.ops = {};
(this.paramGroupForm.categoryId = this.categoryId), (this.modalType = 0);
this.modalTitle = "添加参数组";
this.dialogParamsGroupVisible = true;
},
//保存参数组
submitParamGroupForm() {
this.$refs.paramGroupForm.validate((valid) => {
if (valid) {
if (this.modalType === 0) {
insertParamsGroup(this.paramGroupForm).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("参数组修改成功");
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.dialogParamsVisible = false;
}
});
} else {
console.warn(this.paramGroupForm);
updateParamsGroup(this.paramGroupForm).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("参数组修改成功");
this.getDataList();
this.dialogParamsVisible = false;
}
});
}
this.dialogParamsGroupVisible = false;
}
});
},
//保存参数
submitParamForm() {
this.$refs.paramForm.validate((valid) => {
if (valid) {
this.submitLoading = true;
let data = JSON.parse(JSON.stringify(this.paramForm));
data.isIndex = Number(data.isIndex);
data.required = Number(data.required);
if (this.modalType === 0) {
insertGoodsParams(data).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("参数添加成功");
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 20;
this.getDataList();
this.dialogParamsVisible = false;
}
});
} else {
console.warn(data.isIndex);
data.isIndex = Number(data.isIndex);
data.required = Number(data.required);
updateGoodsParams(data).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("参数修改成功");
this.getDataList();
this.dialogParamsVisible = false;
}
});
}
}
});
},
// 获取分类列表
getDataList() {
getCategoryParamsListData(this.categoryId).then((res) => {
if (res.success) {
this.paramsGroup = res.result;
this.loading = true;
getGoodsParamsPage(this.searchForm).then((res) => {
this.loading = false;
if (res && res.success) {
this.data = (res.result && res.result.records) || [];
this.total = (res.result && res.result.total) || 0;
}
});
},
//删除参数方法
handleDeleteParam(group, param) {
goAdd() {
this.$router.push({ name: "goods-parameter-edit" });
},
goEdit(row) {
if (!row || !row.id) return;
try {
window.sessionStorage.setItem(`goods-parameter-edit:${row.id}`, JSON.stringify(row));
} catch (e) {}
this.$router.push({ name: "goods-parameter-edit", query: { id: row.id } });
},
remove(row) {
this.$Modal.confirm({
title: "确认删除",
// 记得确认修改此处
content: "您确认要删除 " + param.paramName + " ?",
content: "您确认要删除 " + (row.paramName || "") + " ?",
loading: true,
onOk: () => {
// 删除
deleteParams(param.id).then((res) => {
deleteParams(row.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("删除参数成功");
this.getDataList();
}
});
},
});
},
//删除参数组方法
handleDeleteParamGroup(group) {
this.$Modal.confirm({
title: "确认删除",
// 记得确认修改此处
content: "您确认要删除 " + group.groupName + " ?",
loading: true,
onOk: () => {
// 删除
deleteParamsGroup(group.groupId).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("删除参数成功");
if (res && res.success) {
this.$Message.success("删除成功");
this.getDataList();
}
});
@@ -392,33 +209,4 @@ export default {
},
};
</script>
<style lang="scss">
.row {
overflow: hidden;
margin: 20px 0;
}
.params {
align-items: center;
display: flex;
padding: 3px;
background-color: #f5f7fa;
font-size: 14px;
justify-content: space-between;
}
.ivu-card-head {
background-color: #f5f7fa;
}
.ivu-btn {
font-size: 13px;
}
.paramsGroup {
flex-wrap: wrap;
display: flex;
flex-direction: row;
width: 100%;
}
</style>
<style lang="scss"></style>

View File

@@ -1,334 +0,0 @@
<template>
<div class="search">
<Card>
<Form @submit.native.prevent @keydown.enter.native="handleSearch" ref="searchForm" :model="searchForm" inline :label-width="70"
class="search-form">
<Form-item label="规格名称" prop="specName">
<Input type="text" v-model="searchForm.specName" placeholder="请输入规格名称" clearable style="width: 200px" />
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn">搜索</Button>
</Form>
<Row class="operation padding-row">
<Button @click="add" type="primary">添加</Button>
<Button @click="delAll">批量删除</Button>
</Row>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" sortable="custom"
@on-sort-change="changeSort" @on-selection-change="changeSelect">
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage"
@on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small" show-total show-elevator
show-sizer></Page>
</Row>
</Card>
<Modal :title="modalTitle" v-model="modalVisible" :mask-closable="false" :width="500">
<Form ref="form" :model="form" :label-width="100" :rules="formValidate">
<FormItem label="规格名称" prop="specName">
<Input v-model="form.specName" maxlength="30" clearable style="width: 100%" />
</FormItem>
<FormItem label="规格值" prop="specValue">
<Select v-model="form.specValue" placeholder="输入后回车添加" multiple filterable allow-create
:popper-append-to-body="false" popper-class="spec-values-popper"
style="width: 100%; text-align: left; margin-right: 10px" @on-create="handleCreate2">
<Option v-for="item in specValue" :value="item" :label="item" :key="item">
</Option>
</Select>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="modalVisible = false">取消</Button>
<Button type="primary" :loading="submitLoading" @click="saveSpec">提交</Button>
</div>
</Modal>
</div>
</template>
<script>
import { getSpecListData, insertSpec, updateSpec, delSpec } from "@/api/goods";
import { regular } from "@/utils";
export default {
name: "spec",
components: {},
data () {
return {
loading: true, // 表单加载状态
modalType: 0, // 添加或编辑标识
modalVisible: false, // 添加或编辑显示
modalTitle: "", // 添加或编辑标题
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "asc", // 默认排序方式
},
// 表单验证规则
formValidate: {
specName: [
regular.REQUIRED,
// regular.VARCHAR20
],
specValue: [regular.REQUIRED, regular.VARCHAR255],
},
form: {
// 添加或编辑表单对象初始化数据
specName: "",
specValue: "",
},
/** 编辑规格值 */
specValue: [],
submitLoading: false, // 添加或编辑提交状态
selectList: [], // 多选数据
selectCount: 0, // 多选计数
columns: [
// 表头
{
type: "selection",
width: 60,
align: "center",
},
{
title: "规格名称",
key: "specName",
width: 200,
},
{
title: "规格值",
key: "specValue",
minWidth: 250,
tooltip: true,
},
{
title: "操作",
key: "action",
align: "center",
fixed: "right",
width: 250,
render: (h, params) => {
return h("div", [
h(
"Button",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.edit(params.row);
},
},
},
"编辑"
),
h(
"Button",
{
props: {
type: "error",
size: "small",
},
on: {
click: () => {
this.remove(params.row);
},
},
},
"删除"
),
]);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
};
},
methods: {
handleCreate2 (v) {
console.log(v);
},
//初始化,获取数据
init () {
this.getDataList();
},
//修改分页
changePage (v) {
this.searchForm.pageNumber = v;
this.getDataList();
this.clearSelectAll();
},
//修改页面大小
changePageSize (v) {
this.searchForm.pageSize = v;
this.getDataList();
},
//搜索参数
handleSearch () {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.getDataList();
},
//重置搜索参数
handleReset () {
this.$refs.searchForm.resetFields();
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
// 重新加载数据
this.getDataList();
},
//更改排序
changeSort (e) {
this.searchForm.sort = e.key;
this.searchForm.order = e.order;
if (e.order === "normal") {
this.searchForm.order = "";
}
this.getDataList();
},
//清除已选择
clearSelectAll () {
this.$refs.table.selectAll(false);
},
//修改已选择
changeSelect (e) {
this.selectList = e;
this.selectCount = e.length;
},
//获取数据
getDataList () {
this.loading = true;
// 带多条件搜索参数获取表单数据 请自行修改接口
getSpecListData(this.searchForm).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
this.total = res.result.total;
}
});
this.loading = false;
},
//新增规格
saveSpec () {
this.$refs.form.validate((valid) => {
if (valid) {
this.submitLoading = true;
if (this.modalType === 0) {
if (this.data.find((item) => item.specName == this.form.specName)) {
this.$Message.error("请勿添加重复规格名称!");
this.submitLoading = false;
return;
}
// 添加 避免编辑后传入id等数据
delete this.form.id;
insertSpec(this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
} else {
// 编辑
updateSpec(this.form.id, this.form).then((res) => {
this.submitLoading = false;
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
this.modalVisible = false;
}
});
}
}
});
},
//弹出添加框
add () {
this.modalType = 0;
this.modalTitle = "添加";
this.$refs.form.resetFields();
this.specValue = "";
delete this.form.id;
this.modalVisible = true;
},
//弹出编辑框
edit (v) {
this.modalType = 1;
this.modalTitle = "编辑";
// 转换null为""
for (let attr in v) {
if (v[attr] === null) {
v[attr] = "";
}
}
let localVal = v.specValue;
this.form.specName = v.specName;
this.form.id = v.id;
this.$nextTick(() => {
this.$set(this.form, 'specValue', v.specValue);
})
if (localVal && localVal.indexOf("," > 0)) {
this.$set(this.form, 'specValue', localVal.split(","));
this.specValue = this.form.specValue;
} else {
this.specValue = [];
}
this.modalVisible = true;
},
// 删除规格
remove (v) {
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除 " + v.specName + " ?",
loading: true,
onOk: () => {
delSpec(v.id).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("操作成功");
this.getDataList();
}
});
},
});
},
// 批量删除
delAll () {
if (this.selectCount <= 0) {
this.$Message.warning("您还未选择要删除的数据");
return;
}
this.$Modal.confirm({
title: "确认删除",
content: "您确认要删除所选的 " + this.selectCount + " 条数据?",
loading: true,
onOk: () => {
let ids = "";
this.selectList.forEach(function (e) {
ids += e.id + ",";
});
ids = ids.substring(0, ids.length - 1);
delSpec(ids).then((res) => {
this.$Modal.remove();
if (res.success) {
this.$Message.success("删除成功");
this.clearSelectAll();
this.searchForm.pageNumber = 1;
this.getDataList();
}
});
},
});
},
},
mounted () {
this.init();
},
};
</script>

View File

@@ -4,11 +4,16 @@
<Row>
<Form ref="searchForm" :model="searchForm" @keydown.enter.native="handleSearch" @submit.native.prevent inline :label-width="70" class="search-form">
<Form-item label="会员名称" prop="memberName">
<Input type="text" v-model="searchForm.memberName" placeholder="请输入会员名称" clearable style="width: 200px"/>
<Input type="text" v-model="searchForm.memberName" placeholder="请输入会员名称" clearable style="width: 240px"/>
</Form-item>
<Form-item label="商品名称" prop="goodsName">
<Input type="text" v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable style="width: 240px"/>
</Form-item>
<Button @click="handleSearch" type="primary" class="search-btn" icon="ios-search">搜索</Button>
</Form>
</Row>
</Card>
<Card>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" class="mt_10">
<!-- 页面展示 -->
<template slot="shopDisableSlot" slot-scope="scope">
@@ -21,7 +26,7 @@
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage"
@on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]"
@on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]"
size="small" show-total show-elevator show-sizer></Page>
</Row>
</Card>
@@ -110,7 +115,8 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
goodsName: "",
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
@@ -132,6 +138,30 @@ export default {
align: "left",
tooltip: true,
},
{
title: "评论内容",
key: "content",
minWidth: 220,
align: "left",
tooltip: true,
},
{
title: "是否置顶",
key: "top",
align: "center",
width: 100,
render: (h, params) => {
return h(
"Tag",
{
props: {
color: params.row.top ? "red" : "default",
},
},
params.row.top ? "已置顶" : "未置顶"
);
},
},
{
title: "评价",
key: "grade",
@@ -147,27 +177,6 @@ export default {
}
}
},
{
title: "物流评分",
key: "deliveryScore",
render: (h, params) => {
return h('div',params.row.deliveryScore || 5 + '星')
},
},
{
title: "服务评分",
key: "deliveryScore",
render: (h, params) => {
return h('div',params.row.serviceScore || 5 + '星')
},
},
{
title: "描述评分",
key: "deliveryScore",
render: (h, params) => {
return h('div',params.row.descriptionScore || 5 + '星')
},
},
{
title: "评价时间",
key: "createTime",
@@ -184,21 +193,18 @@ export default {
{
title: "操作",
key: "action",
width: 150,
width: 220,
align: "center",
fixed: "right",
render: (h, params) => {
return h("div", [
return h("div", { class: "ops" }, [
h(
"Button",
"a",
{
props: {
size: "small",
type: "info",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -209,23 +215,65 @@ export default {
"查看"
),
h(
"Button",
"span",
{
props: {
type: "error",
size: "small",
},
style: {
marginRight: "5px",
margin: "0 8px",
color: "#dcdee2",
},
},
"|"
),
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.updateTop(params.row);
},
},
},
params.row.top ? "取消置顶" : "置顶评论"
),
h(
"span",
{
style: {
margin: "0 8px",
color: "#dcdee2",
},
},
"|"
),
h(
"Poptip",
{
props: { confirm: true, title: "确认删除" },
on: {
"on-ok": () => {
this.remove(params.row);
},
},
},
[
h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
},
"删除"
),
]
),
]);
},
},
@@ -263,7 +311,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
//列表直接选择页面是否展示
@@ -273,6 +321,25 @@ export default {
this.init();
});
},
// 置顶评论
updateTop(v) {
const top = !v.top;
API_Member.updateMemberReviewTop(v.id, { top }).then((res) => {
if (res.success) {
this.data = this.data.map((item) => {
if (item.goodsId !== v.goodsId) {
return item;
}
return {
...item,
top: item.id === v.id ? top : false,
};
});
this.$Message.success(top ? "置顶成功!" : "取消置顶成功!");
this.init();
}
});
},
// 获取列表
getDataList() {
this.loading = true;
@@ -370,4 +437,14 @@ label {
margin: 5px 0;
span{margin-right: 20px;}
}
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<div class="search">
<Card>
<Row class="operation padding-row">
<Button type="primary" @click="openAdd">添加分组</Button>
</Row>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
ref="table"
class="mt_10"
></Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
<Modal v-model="addFlag" title="添加商品分组">
<Form ref="addForm" :model="formAdd" :rules="rulesAdd" :label-width="90">
<FormItem label="分组名称" prop="groupName" style="width: 90%;">
<Input v-model="formAdd.groupName" maxlength="30" placeholder="请输入分组名称" />
</FormItem>
<FormItem label="分组描述" prop="description" style="width: 90%;">
<Input v-model="formAdd.description" maxlength="200" placeholder="请输入分组描述" />
</FormItem>
</Form>
<div slot="footer">
<Button @click="addFlag = false">取消</Button>
<Button type="primary" :loading="submitAddLoading" @click="submitAdd">确定</Button>
</div>
</Modal>
<Modal v-model="editFlag" title="编辑商品分组">
<Form ref="editForm" :model="formEdit" :rules="rulesEdit" :label-width="90">
<Input v-model="formEdit.id" v-show="false" />
<FormItem label="分组名称" prop="groupName" style="width: 90%;">
<Input v-model="formEdit.groupName" maxlength="30" placeholder="请输入分组名称" />
</FormItem>
<FormItem label="分组描述" prop="description" style="width: 90%;">
<Input v-model="formEdit.description" maxlength="200" placeholder="请输入分组描述" />
</FormItem>
</Form>
<div slot="footer">
<Button @click="editFlag = false">取消</Button>
<Button type="primary" :loading="submitEditLoading" @click="submitEdit">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import * as API_Goods from "@/api/goods.js";
export default {
name: "goodsGroup",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
},
columns: [
{
title: "分组名称",
key: "groupName",
minWidth: 160,
tooltip: true,
},
{
title: "分组描述",
key: "description",
minWidth: 240,
tooltip: true,
},
{
title: "创建时间",
key: "createTime",
width: 180,
},
{
title: "更新时间",
key: "updateTime",
width: 180,
},
{
title: "操作",
key: "action",
align: "center",
width: 200,
fixed: "right",
render: (h, params) => {
const linkStyle = {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
};
const sep = h(
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
);
return h("div", { class: "ops", style: { display: "flex", justifyContent: "center" } }, [
h("a", { style: linkStyle, on: { click: () => this.openEdit(params.row) } }, "编辑"),
sep,
h("a", { style: linkStyle, on: { click: () => this.remove(params.row) } }, "删除"),
]);
},
},
],
data: [],
total: 0,
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: {
groupName: "",
description: "",
},
formEdit: {
id: "",
groupName: "",
description: "",
},
rulesAdd: {
groupName: [{ required: true, message: "请输入分组名称" }],
},
rulesEdit: {
groupName: [{ required: true, message: "请输入分组名称" }],
},
};
},
methods: {
init() {
this.getData();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getData();
},
getData() {
this.loading = true;
API_Goods.getGoodsGroupByPage(this.searchForm)
.then((res) => {
this.loading = false;
if (res && res.success && res.result) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
} else if (res && res.message) {
this.$Message.error(res.message);
}
})
.catch(() => {
this.loading = false;
this.$Message.error("加载商品分组失败");
});
},
openAdd() {
this.addFlag = true;
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = { groupName: "", description: "" };
});
},
submitAdd() {
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
API_Goods.addGoodsGroup(this.formAdd)
.then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
this.$Message.success("添加成功");
this.addFlag = false;
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
}
})
.catch(() => {
this.submitAddLoading = false;
this.$Message.error("添加失败,请检查权限或后端接口");
});
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
API_Goods.getGoodsGroup(row.id)
.then((res) => {
if (res && res.success && res.result) {
this.formEdit = {
id: res.result.id,
groupName: res.result.groupName || "",
description: res.result.description || "",
};
} else {
this.formEdit = {
id: row.id,
groupName: row.groupName || "",
description: row.description || "",
};
}
})
.catch(() => {
this.$Message.error("获取分组详情失败");
});
},
submitEdit() {
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, groupName, description } = this.formEdit;
API_Goods.updateGoodsGroup(id, { id, groupName, description })
.then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
this.$Message.success("修改成功");
this.editFlag = false;
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
}
})
.catch(() => {
this.submitEditLoading = false;
this.$Message.error("修改失败,请检查权限或后端接口");
});
});
},
remove(row) {
this.$Modal.confirm({
title: "提示",
content: "<p>确定删除该分组?</p>",
onOk: () => {
API_Goods.deleteGoodsGroup(row.id)
.then((res) => {
if (res && res.success) {
this.$Message.success("删除成功");
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
}
})
.catch(() => {
this.$Message.error("删除失败,请检查权限或后端接口");
});
},
});
},
},
mounted() {
this.init();
},
};
</script>

View File

@@ -0,0 +1,540 @@
<template>
<div class="search">
<Card>
<Form
ref="searchForm"
:model="searchForm"
inline
:label-width="70"
class="search-form"
@keydown.enter.native="handleSearch"
>
<Form-item label="商品名称" prop="goodsName">
<Input
v-model="searchForm.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 240px"
/>
</Form-item>
<Form-item label="商品ID" prop="goodsId">
<Input
v-model="searchForm.goodsId"
placeholder="请输入商品ID"
clearable
style="width: 240px"
/>
</Form-item>
<Form-item label="SKU货号" prop="sn">
<Input
v-model="searchForm.sn"
placeholder="请输入SKU货号"
clearable
style="width: 240px"
/>
</Form-item>
<Form-item label="店铺名称" prop="storeName">
<Input
v-model="searchForm.storeName"
placeholder="请输入店铺名称"
clearable
style="width: 240px"
/>
</Form-item>
<Button
@click="handleSearch"
class="search-btn"
type="primary"
icon="ios-search"
>
搜索
</Button>
<Button @click="handleReset" style="margin-left: 8px">重置</Button>
</Form>
</Card>
<Card class="mt_10">
<Alert show-icon>
支持按规格逐条设置虚拟销量列表总销量 = 真实销量 + 虚拟销量
</Alert>
<div class="batch-operations">
<Button
type="primary"
:disabled="selectedRows.length === 0"
@click="openBatchVirtualSalesModal"
>
批量设置虚拟销量
</Button>
</div>
<Table
:loading="loading"
:columns="columns"
:data="data"
ref="table"
class="mt_10"
sortable="custom"
@on-sort-change="changeSort"
@on-selection-change="handleSelectionChange"
>
<template slot="goodsSlot" slot-scope="{ row }">
<div class="goods-info">
<img
:src="row.thumbnail"
class="goods-thumbnail"
/>
<div class="goods-text">
<div class="div-zoom">{{ row.goodsName }}</div>
<div class="sub-title" v-if="row.simpleSpecs">
规格{{ row.simpleSpecs.trim() || "-" }}
</div>
</div>
</div>
</template>
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
:total="total"
:page-size="searchForm.pageSize"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
show-sizer
@on-change="changePage"
@on-page-size-change="changePageSize"
></Page>
</Row>
</Card>
<Modal
v-model="editModalVisible"
:title="modalMode === 'batch' ? '批量设置虚拟销量' : '设置虚拟销量'"
:mask-closable="false"
:closable="!modalSubmitting"
>
<div v-if="modalMode === 'single' && currentSku" class="virtual-sales-modal">
<div class="sku-info-item">
<span class="sku-info-label">商品名称</span>
<span>{{ currentSku.goodsName || "-" }}</span>
</div>
<div class="sku-info-item">
<span class="sku-info-label">规格信息</span>
<span>{{ currentSku.simpleSpecs || "-" }}</span>
</div>
<div class="sku-info-item">
<span class="sku-info-label">SKU ID</span>
<span>{{ currentSku.id || "-" }}</span>
</div>
<Form :label-width="90" class="mt_10">
<Form-item label="虚拟销量">
<InputNumber
v-model="editForm.virtualSales"
:min="0"
:max="99999999"
:precision="0"
style="width: 200px"
/>
</Form-item>
</Form>
</div>
<div v-else-if="modalMode === 'batch'" class="virtual-sales-modal">
<div class="sku-info-item">
<span class="sku-info-label">已选规格</span>
<span>{{ selectedRows.length }} </span>
</div>
<Form :label-width="90" class="mt_10">
<Form-item label="虚拟销量">
<InputNumber
v-model="editForm.virtualSales"
:min="0"
:max="99999999"
:precision="0"
style="width: 200px"
/>
</Form-item>
</Form>
</div>
<div slot="footer">
<Button @click="handleCancelVirtualSales" :disabled="modalSubmitting">
取消
</Button>
<Button
type="primary"
:loading="modalSubmitting"
@click="handleSubmitVirtualSales"
>
提交
</Button>
</div>
</Modal>
</div>
</template>
<script>
import {
batchUpdateGoodsSkuVirtualSales,
getGoodsSkuData,
updateGoodsSkuVirtualSales,
} from "@/api/goods";
const VIRTUAL_SALES_FIELDS = [
"virtualSales",
"fictitiousSales",
"fakeBuyCount",
"fictitiousBuyCount",
"mockBuyCount",
"salesVolume",
];
export default {
name: "goodsVirtualSales",
data() {
return {
loading: true,
editModalVisible: false,
modalSubmitting: false,
modalMode: "single",
currentSku: null,
currentIndex: -1,
selectedRows: [],
editForm: {
virtualSales: 0,
},
searchForm: {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
goodsName: "",
goodsId: "",
sn: "",
storeName: "",
},
columns: [
{
type: "selection",
width: 60,
align: "center",
},
{
title: "SKU ID",
key: "id",
width: 180,
tooltip: true,
},
{
title: "商品ID",
key: "goodsId",
width: 180,
tooltip: true,
},
{
title: "商品信息",
key: "goodsName",
minWidth: 360,
slot: "goodsSlot",
},
{
title: "真实销量",
key: "buyCount",
width: 110,
render: (h, params) => h("span", params.row.buyCount || 0),
},
{
title: "虚拟销量",
key: "virtualSalesInput",
width: 110,
render: (h, params) => h("span", params.row.virtualSalesInput || 0),
},
{
title: "总销量",
key: "totalSalesDisplay",
width: 110,
render: (h, params) => h("span", this.getTotalSales(params.row)),
},
{
title: "状态",
key: "marketEnable",
width: 100,
render: (h, params) => {
const isUpper = params.row.marketEnable === "UPPER";
return h(
"Tag",
{ props: { color: isUpper ? "green" : "volcano" } },
isUpper ? "上架" : "下架"
);
},
},
{
title: "操作",
key: "action",
width: 100,
align: "center",
render: (h, params) => {
return h(
"Button",
{
props: {
type: "primary",
size: "small",
loading: !!params.row.saving,
},
on: {
click: () => this.openVirtualSalesModal(params.row, params.index),
},
},
"设置"
);
},
},
],
data: [],
total: 0,
};
},
methods: {
init() {
this.getDataList();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getDataList();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getDataList();
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleReset() {
this.searchForm = {
pageNumber: 1,
pageSize: 20,
sort: "create_time",
order: "desc",
goodsName: "",
goodsId: "",
sn: "",
storeName: "",
};
this.getDataList();
},
changeSort(e) {
this.searchForm.sort = e.key;
this.searchForm.order = e.order;
if (e.order === "normal") {
this.searchForm.sort = "create_time";
this.searchForm.order = "desc";
}
this.getDataList();
},
handleSelectionChange(selection) {
this.selectedRows = selection;
},
getDataList() {
this.loading = true;
getGoodsSkuData(this.searchForm)
.then((res) => {
if (res && res.success && res.result) {
this.data = (res.result.records || []).map((item) => ({
...item,
virtualSalesInput: this.getVirtualSalesValue(item),
saving: false,
}));
this.total = res.result.total || 0;
this.selectedRows = [];
} else {
this.data = [];
this.total = 0;
this.selectedRows = [];
this.$Message.error((res && res.message) || "加载规格列表失败");
}
})
.catch(() => {
this.data = [];
this.total = 0;
this.selectedRows = [];
this.$Message.error("加载规格列表失败");
})
.finally(() => {
this.loading = false;
});
},
getVirtualSalesValue(row) {
const field = VIRTUAL_SALES_FIELDS.find(
(item) => row[item] !== undefined && row[item] !== null && row[item] !== ""
);
return field ? Number(row[field]) || 0 : 0;
},
openVirtualSalesModal(row, index) {
this.modalMode = "single";
this.currentSku = { ...row };
this.currentIndex = index;
this.editForm.virtualSales = this.getVirtualSalesValue(row);
this.editModalVisible = true;
},
openBatchVirtualSalesModal() {
if (!this.selectedRows.length) {
this.$Message.warning("请先选择要设置的商品规格");
return;
}
this.modalMode = "batch";
this.currentSku = null;
this.currentIndex = -1;
this.editForm.virtualSales = 0;
this.editModalVisible = true;
},
handleCancelVirtualSales() {
if (this.modalSubmitting) {
return;
}
this.resetVirtualSalesModal();
},
resetVirtualSalesModal() {
this.editModalVisible = false;
this.modalMode = "single";
this.currentSku = null;
this.currentIndex = -1;
this.editForm.virtualSales = 0;
},
handleSubmitVirtualSales() {
const virtualSales = Number(this.editForm.virtualSales);
if (!Number.isInteger(virtualSales) || virtualSales < 0) {
this.$Message.warning("请输入大于等于 0 的整数虚拟销量");
return;
}
if (this.modalMode === "batch") {
this.handleBatchSubmitVirtualSales(virtualSales);
return;
}
if (!this.currentSku || this.currentIndex < 0) {
return;
}
const currentIndex = this.currentIndex;
const currentSkuId = this.currentSku.id;
this.modalSubmitting = true;
this.$set(this.data[currentIndex], "saving", true);
updateGoodsSkuVirtualSales(currentSkuId, { virtualSales })
.then((res) => {
if (res && res.success) {
this.$set(this.data[currentIndex], "virtualSales", virtualSales);
this.$set(this.data[currentIndex], "virtualSalesInput", virtualSales);
this.$Message.success("虚拟销量设置成功");
this.resetVirtualSalesModal();
} else {
this.$Message.error((res && res.message) || "规格虚拟销量设置失败");
}
})
.catch(() => {
this.$Message.error("规格虚拟销量设置失败");
})
.finally(() => {
if (this.data[currentIndex]) {
this.$set(this.data[currentIndex], "saving", false);
}
this.modalSubmitting = false;
});
},
handleBatchSubmitVirtualSales(virtualSales) {
const skuIds = this.selectedRows.map((item) => item.id).filter(Boolean);
if (!skuIds.length) {
this.$Message.warning("请先选择要设置的商品规格");
return;
}
this.modalSubmitting = true;
batchUpdateGoodsSkuVirtualSales({ skuIds, virtualSales })
.then((res) => {
if (res && res.success) {
this.data = this.data.map((item) => {
if (!skuIds.includes(item.id)) {
return item;
}
return {
...item,
virtualSales,
virtualSalesInput: virtualSales,
};
});
this.selectedRows = [];
if (this.$refs.table) {
this.$refs.table.selectAll(false);
}
this.$Message.success("虚拟销量设置成功");
this.resetVirtualSalesModal();
} else {
this.$Message.error((res && res.message) || "虚拟销量设置失败");
}
})
.catch(() => {
this.$Message.error("虚拟销量设置失败");
})
.finally(() => {
this.modalSubmitting = false;
});
},
getTotalSales(row) {
return (Number(row.buyCount) || 0) + (Number(row.virtualSalesInput) || 0);
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss" scoped>
.sub-title {
margin-top: 6px;
color: #808695;
font-size: 12px;
}
.goods-info {
display: flex;
align-items: center;
margin: 5px 0;
padding: 10px 0;
}
.goods-thumbnail {
width: 50px;
height: 50px;
margin-right: 12px;
object-fit: cover;
flex-shrink: 0;
}
.goods-text {
min-width: 0;
}
.batch-operations {
margin-top: 10px;
}
.virtual-sales-modal {
padding-top: 8px;
}
.sku-info-item {
display: flex;
line-height: 24px;
margin-bottom: 8px;
word-break: break-all;
}
.sku-info-label {
width: 90px;
color: #808695;
flex-shrink: 0;
}
</style>

View File

@@ -10,7 +10,7 @@ h4 {
margin: 20px 0;
font-size: 18px;
}
/deep/ .ivu-icon {
::v-deep .ivu-icon {
margin-left: 40px;
margin-right: 40px;
}

View File

@@ -26,7 +26,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator

View File

@@ -66,16 +66,16 @@ export default {
};
</script>
<style scoped lang="scss">
/deep/ .ivu-select-dropdown {
::v-deep .ivu-select-dropdown {
text-align: left;
}
.message-con {
margin-right: 10px;
}
/deep/ .ivu-dropdown-item{
::v-deep .ivu-dropdown-item{
padding: 7px 20px !important;
}
/deep/ .ivu-badge-count{
::v-deep .ivu-badge-count{
right: -10px !important;
}
</style>

View File

@@ -10,7 +10,7 @@
<Menu
ref="childrenMenu"
:active-name="$route.name"
width="100px"
width="120px"
@on-select="changeMenu"
>
<template v-for="item in menuList">
@@ -71,7 +71,7 @@ export default {
<style lang="scss" scoped>
.ivu-shrinkable-menu{
height: calc(100% - 60px);
width: 180px;
width: 200px;
display: flex;
}
@@ -90,7 +90,7 @@ export default {
.ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu), .ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title-active:not(.ivu-menu-submenu){
color: $theme_color;
}
/deep/.ivu-menu-vertical .ivu-menu-item-group-title {
::v-deep.ivu-menu-vertical .ivu-menu-item-group-title {
height: 40px;
line-height: 40px;
padding-left: 20px;

View File

@@ -52,7 +52,7 @@
box-sizing: border-box;
position: fixed;
display: block;
padding-left: 180px;
padding-left: 200px;
width: 100%;
height: 100px;
z-index: 20;
@@ -218,12 +218,12 @@
.single-page-con {
min-width: 740px;
position: relative;
left: 180px;
left: 220px;
top: 100px;
right: 0;
bottom: 0;
height: calc(100% - 110px);
width: calc(100% - 180px);
width: calc(100% - 220px);
overflow: auto;
background-color: #f0f0f0;
z-index: 1;

View File

@@ -14,7 +14,7 @@
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="充值单号" prop="rechargeSn">
@@ -23,7 +23,7 @@
v-model="searchForm.rechargeSn"
placeholder="请输入充值单号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="支付时间">
@@ -34,11 +34,13 @@
clearable
@on-change="selectDateRange"
placeholder="选择起始时间"
style="width: 200px"
style="width: 240px"
></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
</Form>
</Card>
<Card>
<Table
:loading="loading"
border
@@ -54,7 +56,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -77,7 +79,7 @@
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
@@ -175,7 +177,7 @@
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 时间段赋值

View File

@@ -5,17 +5,19 @@
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="会员名称" prop="memberName">
<Input type="text" v-model="searchForm.memberName" placeholder="请输入会员名称" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.memberName" placeholder="请输入会员名称" clearable style="width: 240px" />
</Form-item>
<Form-item label="支付时间">
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd HH:mm:ss" clearable @on-change="selectDateRange" placeholder="选择起始时间" style="width: 200px"></DatePicker>
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd HH:mm:ss" clearable @on-change="selectDateRange" placeholder="选择起始时间" style="width: 240px"></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
</Form>
</Row>
</Card>
<Card>
<Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]"
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]"
size="small" show-total show-elevator show-sizer></Page>
</Row>
</Card>
@@ -32,7 +34,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
@@ -113,7 +115,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 时间段赋值

View File

@@ -4,10 +4,10 @@
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="会员名称" prop="memberName">
<Input type="text" v-model="searchForm.memberName" placeholder="请输入会员名称" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.memberName" placeholder="请输入会员名称" clearable style="width: 240px" />
</Form-item>
<Form-item label="审核状态" prop="applyStatus">
<Select v-model="searchForm.applyStatus" clearable style="width: 200px">
<Select v-model="searchForm.applyStatus" clearable style="width: 240px">
<Option value="APPLY">申请中</Option>
<Option value="VIA_AUDITING">审核通过</Option>
<Option value="FAIL_AUDITING">审核拒绝</Option>
@@ -16,7 +16,7 @@
</Select>
</Form-item>
<Form-item label="申请时间">
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd HH:mm:ss" clearable @on-change="selectDateRange" placeholder="选择起始时间" style="width: 200px"></DatePicker>
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd HH:mm:ss" clearable @on-change="selectDateRange" placeholder="选择起始时间" style="width: 240px"></DatePicker>
</Form-item>
<Form-item style="margin-left: -35px" class="br">
<Button @click="handleSearch" type="primary" icon="ios-search">搜索
@@ -24,9 +24,11 @@
</Form-item>
</Form>
</Row>
</Card>
<Card>
<Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table" sortable="custom" @on-sort-change="changeSort" @on-selection-change="changeSelect"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small"
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]" size="small"
show-total show-elevator show-sizer></Page>
</Row>
</Card>
@@ -115,7 +117,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
@@ -192,37 +194,36 @@ export default {
fixed: "right",
render: (h, params) => {
if (params.row.applyStatus == "APPLY") {
return h(
"Button",
return h("div", { class: "ops" }, [
h(
"a",
{
props: {
type: "primary",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.showList = {};
this.roleModalVisible = true;
this.showList = params.row;
this.audit =""
this.audit = "";
},
},
},
"审核"
);
),
]);
} else {
return h(
"Button",
return h("div", { class: "ops" }, [
h(
"a",
{
props: {
type: "default",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -234,7 +235,8 @@ export default {
},
},
"查看"
);
),
]);
}
},
},
@@ -291,13 +293,13 @@ export default {
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
handleReset() {
this.$refs.searchForm.resetFields();
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.selectDate = null;
this.searchForm.startDate = "";
this.searchForm.endDate = "";
@@ -345,4 +347,16 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -0,0 +1,992 @@
<template>
<div class="search member-benefit">
<Card>
<Row class="operation padding-row">
<Button type="primary" @click="openAdd">添加权益设置</Button>
</Row>
<Table
:loading="loading"
stripe
:columns="columns"
:data="data"
no-data-text="暂无数据"
class="mt_10 benefit-list-table benefit-list-table--no-vertical-borders"
></Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 30, 50]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
<!-- 平台优惠券选择添加优惠券活动coupon-publish 相同组件 -->
<Modal
v-model="couponPickerVisible"
title="选择优惠券"
width="80%"
:z-index="3000"
:mask-closable="false"
@on-ok="handleCouponPickerClose"
@on-cancel="handleCouponPickerClose"
>
<couponTemplate
v-if="couponPickerVisible"
:selectedList="couponPickerSelectedList"
getType="ACTIVITY"
promotionStatus="START"
@selected="onCouponTemplateSelected"
/>
</Modal>
<Drawer
v-model="addFlag"
title="添加权益设置"
width="1120"
placement="right"
:z-index="950"
:mask-closable="false"
:closable="true"
scrollable
class-name="benefit-form-drawer"
>
<Form ref="addForm" :model="formAdd" :rules="rulesAdd" :label-width="110">
<FormItem label="权益类型" prop="benefitType">
<Select
v-model="formAdd.benefitType"
clearable
placeholder="请选择权益类型"
style="width: 100%"
@on-change="onAddBenefitTypeChange"
>
<Option v-for="item in benefitTypeOptions" :key="item.value" :value="item.value">{{ item.description }}</Option>
</Select>
</FormItem>
<FormItem
v-show="formAdd.benefitType === 'GIFT_POINT'"
label="赠送积分"
prop="giftPoint"
:required="formAdd.benefitType === 'GIFT_POINT'"
>
<InputNumber
v-model="formAdd.giftPoint"
:min="0"
:max="99999"
:precision="0"
style="width: 220px"
placeholder="必填,范围 0-99999"
/>
</FormItem>
<FormItem
v-show="formAdd.benefitType === 'COUPON_PACKAGE'"
label="赠送优惠券"
prop="couponPackageRows"
:required="formAdd.benefitType === 'COUPON_PACKAGE'"
>
<div>
<Button type="dashed" icon="ios-add" class="mb_10" @click="openCouponPicker('add')">添加优惠券</Button>
<Table
border
size="small"
:columns="couponPackageTableColumns"
:data="formAdd.couponPackageRows"
no-data-text="请添加优惠券"
></Table>
</div>
</FormItem>
<FormItem label="权益名称" prop="benefitName">
<Input v-model="formAdd.benefitName" maxlength="50" placeholder="请输入权益名称" />
</FormItem>
<FormItem label="权益LOGO" prop="benefitLogo">
<div class="benefit-logo-field">
<div v-if="formAdd.benefitLogo" class="benefit-logo-thumb">
<img :src="formAdd.benefitLogo" alt="" />
</div>
<div class="benefit-logo-upload">
<upload-pic-input v-model="formAdd.benefitLogo" placeholder="请上传权益 LOGO 图片地址" style="width: 100%" />
</div>
</div>
</FormItem>
<FormItem label="权益介绍" prop="benefitDesc">
<Input v-model="formAdd.benefitDesc" type="textarea" :rows="4" maxlength="500" placeholder="请输入权益介绍" />
</FormItem>
<FormItem label="启用状态" prop="benefitState">
<RadioGroup v-model="formAdd.benefitState">
<Radio label="OPEN">开启</Radio>
<Radio label="CLOSE">关闭</Radio>
</RadioGroup>
</FormItem>
<FormItem label="排序" prop="benefitSort">
<InputNumber v-model="formAdd.benefitSort" :min="1" :max="9999" :precision="0" style="width: 220px" />
</FormItem>
</Form>
<div class="benefit-drawer-footer-btns">
<Button @click="addFlag = false">取消</Button>
<Button type="primary" :loading="submitAddLoading" @click="submitAdd">提交</Button>
</div>
</Drawer>
<Drawer
v-model="editFlag"
title="编辑权益设置"
width="1120"
placement="right"
:z-index="950"
:mask-closable="false"
:closable="true"
scrollable
class-name="benefit-form-drawer"
>
<Form ref="editForm" :model="formEdit" :rules="rulesEdit" :label-width="110">
<Input v-model="formEdit.id" v-show="false" />
<FormItem label="权益类型" prop="benefitType">
<Select
v-model="formEdit.benefitType"
clearable
placeholder="请选择权益类型"
style="width: 100%"
@on-change="onEditBenefitTypeChange"
>
<Option v-for="item in benefitTypeOptions" :key="item.value" :value="item.value">{{ item.description }}</Option>
</Select>
</FormItem>
<FormItem
v-show="formEdit.benefitType === 'GIFT_POINT'"
label="赠送积分"
prop="giftPoint"
:required="formEdit.benefitType === 'GIFT_POINT'"
>
<InputNumber
v-model="formEdit.giftPoint"
:min="0"
:max="99999"
:precision="0"
style="width: 220px"
placeholder="必填,范围 0-99999"
/>
</FormItem>
<FormItem
v-show="formEdit.benefitType === 'COUPON_PACKAGE'"
label="赠送优惠券"
prop="couponPackageRows"
:required="formEdit.benefitType === 'COUPON_PACKAGE'"
>
<div>
<Button type="dashed" icon="ios-add" class="mb_10" @click="openCouponPicker('edit')">添加优惠券</Button>
<Table
border
size="small"
:columns="couponPackageTableColumnsEdit"
:data="formEdit.couponPackageRows"
no-data-text="请添加优惠券"
></Table>
</div>
</FormItem>
<FormItem label="权益名称" prop="benefitName">
<Input v-model="formEdit.benefitName" maxlength="50" placeholder="请输入权益名称" />
</FormItem>
<FormItem label="权益LOGO" prop="benefitLogo">
<div class="benefit-logo-field">
<div v-if="formEdit.benefitLogo" class="benefit-logo-thumb">
<img :src="formEdit.benefitLogo" alt="" />
</div>
<div class="benefit-logo-upload">
<upload-pic-input v-model="formEdit.benefitLogo" placeholder="请上传权益 LOGO 图片地址" style="width: 100%" />
</div>
</div>
</FormItem>
<FormItem label="权益介绍" prop="benefitDesc">
<Input v-model="formEdit.benefitDesc" type="textarea" :rows="4" maxlength="500" placeholder="请输入权益介绍" />
</FormItem>
<FormItem label="启用状态" prop="benefitState">
<RadioGroup v-model="formEdit.benefitState">
<Radio label="OPEN">开启</Radio>
<Radio label="CLOSE">关闭</Radio>
</RadioGroup>
</FormItem>
<FormItem label="排序" prop="benefitSort">
<InputNumber v-model="formEdit.benefitSort" :min="1" :max="9999" :precision="0" style="width: 220px" />
</FormItem>
</Form>
<div class="benefit-drawer-footer-btns">
<Button @click="editFlag = false">取消</Button>
<Button type="primary" :loading="submitEditLoading" @click="submitEdit">提交</Button>
</div>
</Drawer>
</div>
</template>
<script>
import * as API_Member from "@/api/member.js";
import { getPlatformCoupon } from "@/api/promotion";
import { formatPromotionCouponValidityHtml } from "@/utils/promotions";
import couponTemplate from "@/views/promotions/coupon/coupon.vue";
import uploadPicInput from "@/components/lili/upload-pic-input.vue";
const GIFT_POINT = "GIFT_POINT";
const COUPON_PACKAGE = "COUPON_PACKAGE";
const buildDefaultForm = () => ({
id: "",
benefitName: "",
benefitCode: "",
benefitType: "",
benefitLogo: "",
benefitDesc: "",
benefitSort: 1,
benefitState: "OPEN",
benefitConfig: "",
/** 仅 UI赠送积分数量提交时写入 benefitConfig */
giftPoint: null,
/** 仅 UI券礼包已选优惠券含展示字段提交时写入 benefitConfig.coupons */
couponPackageRows: [],
});
export default {
name: "memberBenefit",
components: {
uploadPicInput,
couponTemplate,
},
data() {
return {
benefitTypeOptions: [],
loading: false,
total: 0,
searchForm: {
pageNumber: 1,
pageSize: 20,
},
columns: [
{
title: "权益类型",
key: "benefitType",
width: 132,
render: (h, params) => {
const v = params.row.benefitType;
let text = "-";
if (v) {
const opt = this.benefitTypeOptions.find((o) => o.value === v);
text = opt ? opt.description : v;
}
return h(
"span",
{
attrs: { title: text },
class: "benefit-list-cell-ellipsis benefit-list-cell-ellipsis--type",
},
text
);
},
},
{
title: "权益名称",
key: "benefitName",
width: 188,
render: (h, params) => {
const text = params.row.benefitName || "-";
return h(
"span",
{
attrs: { title: text },
class: "benefit-list-cell-ellipsis benefit-list-cell-ellipsis--name",
},
text
);
},
},
{
title: "权益LOGO",
key: "benefitLogo",
width: 116,
align: "center",
render: (h, params) => {
const src = params.row.benefitLogo;
if (!src) return h("span", "-");
return h(
"div",
{
class: "benefit-logo-thumb benefit-logo-thumb--table",
},
[
h("img", {
attrs: { src, alt: "" },
}),
]
);
},
},
{
title: "状态",
key: "benefitState",
width: 118,
align: "center",
render: (h, params) => {
const row = params.row;
return h("i-switch", {
props: {
value: row.benefitState === "OPEN",
size: "large",
loading: !!row._benefitStateLoading,
},
on: {
"on-change": (checked) => {
this.onBenefitStateSwitch(row, checked);
},
},
}, [
h("span", { slot: "open" }, "开启"),
h("span", { slot: "close" }, "关闭"),
]);
},
},
{
title: "操作",
key: "action",
align: "center",
width: 140,
render: (h, params) => {
const linkStyle = {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
};
const sep = h("span", { style: { margin: "0 8px", color: "#dcdee2" } }, "|");
return h("div", { style: { display: "flex", justifyContent: "center" } }, [
h("a", { style: linkStyle, on: { click: () => this.openEdit(params.row) } }, "编辑"),
sep,
h("a", { style: linkStyle, on: { click: () => this.remove(params.row) } }, "删除"),
]);
},
},
],
data: [],
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: buildDefaultForm(),
formEdit: buildDefaultForm(),
couponPickerVisible: false,
couponPickerWhich: "add",
};
},
computed: {
rulesAdd() {
return this.buildFormRules("add");
},
rulesEdit() {
return this.buildFormRules("edit");
},
couponPackageTableColumns() {
return this.buildCouponPackageTableColumns("add");
},
couponPackageTableColumnsEdit() {
return this.buildCouponPackageTableColumns("edit");
},
/** 传给优惠券列表组件,用于勾选回显(与 coupon-publish 一致用 id */
couponPickerSelectedList() {
const form = this.couponPickerWhich === "add" ? this.formAdd : this.formEdit;
return (form.couponPackageRows || []).map((r) => ({
id: r.couponId,
couponName: r.couponName || "",
}));
},
},
methods: {
buildFormRules(which) {
const formKey = which === "add" ? "formAdd" : "formEdit";
return {
benefitType: [{ required: true, message: "请选择权益类型", trigger: "change" }],
benefitName: [{ required: true, message: "请输入权益名称", trigger: "blur" }],
benefitLogo: [{ required: true, message: "请配置权益LOGO", trigger: "change" }],
benefitSort: [{ required: true, type: "number", message: "请输入排序", trigger: "change" }],
benefitState: [{ required: true, message: "请选择启用状态", trigger: "change" }],
giftPoint: [
{
validator: (rule, value, callback) => {
const form = this[formKey];
this.validateGiftPoint(form, value, callback);
},
trigger: ["change", "blur"],
},
],
couponPackageRows: [
{
validator: (rule, value, callback) => {
const form = this[formKey];
this.validateCouponPackage(form, callback);
},
trigger: "change",
},
],
};
},
validateGiftPoint(form, value, callback) {
if (form.benefitType !== GIFT_POINT) return callback();
if (value === null || value === undefined || value === "") {
return callback(new Error("请输入赠送积分"));
}
const n = Number(value);
if (Number.isNaN(n) || n < 0 || n > 99999) {
return callback(new Error("赠送积分范围为 0-99999"));
}
callback();
},
validateCouponPackage(form, callback) {
if (form.benefitType !== COUPON_PACKAGE) return callback();
const rows = form.couponPackageRows || [];
if (!rows.length) {
return callback(new Error("请添加至少一张优惠券"));
}
for (let i = 0; i < rows.length; i++) {
const q = Number(rows[i].quantity);
if (Number.isNaN(q) || q < 1 || q > 10) {
return callback(new Error("每张券赠送张数需在 110 之间"));
}
}
callback();
},
formatCouponFace(row) {
if (!row) return "-";
if (row.price !== undefined && row.price !== null && row.price !== "") {
return `¥${row.price}`;
}
if (row.couponDiscount !== undefined && row.couponDiscount !== null && row.couponDiscount !== "") {
return `${row.couponDiscount}`;
}
return "-";
},
formatCouponValidity(row) {
return formatPromotionCouponValidityHtml(row);
},
buildCouponDisplayRow(detail, quantity) {
const qty = Math.min(10, Math.max(1, Number(quantity) || 1));
return {
couponId: detail.id,
quantity: qty,
couponName: detail.couponName || "",
faceValueLabel: this.formatCouponFace(detail),
validRange: this.formatCouponValidity(detail),
};
},
buildCouponPackageTableColumns(which) {
const formKey = which === "add" ? "formAdd" : "formEdit";
const linkStyle = { color: "#2d8cf0", cursor: "pointer" };
return [
{ title: "优惠券名称", key: "couponName", minWidth: 100, tooltip: true },
{
title: "有效期",
minWidth: 100,
render: (h, p) =>
h("span", {
domProps: { innerHTML: p.row.validRange || "-" },
}),
},
{ title: "面额", key: "faceValueLabel", minWidth: 100 },
{
title: "赠送张数",
key: "quantity",
width: 150,
render: (h, params) =>
h("InputNumber", {
props: {
min: 1,
max: 10,
precision: 0,
value: params.row.quantity,
},
style: { width: "100px" },
on: {
"on-change": (val) => {
this.$set(this[formKey].couponPackageRows[params.index], "quantity", val);
this.$nextTick(() => {
const ref = which === "add" ? this.$refs.addForm : this.$refs.editForm;
if (ref) ref.validateField("couponPackageRows");
});
},
},
}),
},
{
title: "操作",
key: "action",
width: 90,
align: "center",
render: (h, params) =>
h(
"a",
{
style: linkStyle,
on: {
click: () => {
this.removeCouponRow(which, params.index);
},
},
},
"删除"
),
},
];
},
/** 构造 benefitConfig JSON 字符串 */
buildBenefitConfigString(slice) {
const { benefitType, giftPoint, couponPackageRows } = slice;
if (benefitType === GIFT_POINT) {
return JSON.stringify({ giftPoint: Number(giftPoint) });
}
if (benefitType === COUPON_PACKAGE) {
const coupons = (couponPackageRows || []).map((r) => ({
couponId: String(r.couponId),
quantity: Math.min(10, Math.max(1, Number(r.quantity) || 1)),
}));
return JSON.stringify({ coupons });
}
return "";
},
/** 从详情 benefitConfig 解析赠送积分,解析失败时返回 null */
parseGiftPointFromConfig(benefitType, benefitConfigStr) {
if (benefitType !== GIFT_POINT || !benefitConfigStr) return null;
try {
const o = JSON.parse(benefitConfigStr);
if (o && typeof o.giftPoint !== "undefined") {
const n = Number(o.giftPoint);
return Number.isNaN(n) ? null : n;
}
} catch (e) {
/* ignore */
}
return null;
},
onAddBenefitTypeChange(val) {
if (val !== GIFT_POINT) {
this.formAdd.giftPoint = null;
}
if (val !== COUPON_PACKAGE) {
this.formAdd.couponPackageRows = [];
}
this.$nextTick(() => {
if (this.$refs.addForm && val !== GIFT_POINT) {
this.$refs.addForm.validateField("giftPoint");
}
if (this.$refs.addForm && val !== COUPON_PACKAGE) {
this.$refs.addForm.validateField("couponPackageRows");
}
});
},
onEditBenefitTypeChange(val) {
if (val !== GIFT_POINT) {
this.formEdit.giftPoint = null;
}
if (val !== COUPON_PACKAGE) {
this.formEdit.couponPackageRows = [];
}
this.$nextTick(() => {
if (this.$refs.editForm && val !== GIFT_POINT) {
this.$refs.editForm.validateField("giftPoint");
}
if (this.$refs.editForm && val !== COUPON_PACKAGE) {
this.$refs.editForm.validateField("couponPackageRows");
}
});
},
parseCouponsFromConfig(str) {
if (!str) return [];
try {
const o = JSON.parse(str);
if (o && Array.isArray(o.coupons)) {
return o.coupons.map((c) => ({
couponId: c.couponId,
quantity: c.quantity,
}));
}
} catch (e) {
/* ignore */
}
return [];
},
/** 与添加时 formatCouponFace 一致;仅有 faceValueText 时去掉「减免」前缀 */
normalizeCouponFaceFromDetail(item) {
let face = this.formatCouponFace(item);
if (face !== "-") return face;
const raw = item.faceValueText;
if (raw == null || raw === "") return "-";
return String(raw)
.replace(/^减免现金\s*/, "")
.replace(/^减免\s*/, "")
.trim() || "-";
},
/** 管理端详情 couponItems → 表格行(面额展示与添加一致) */
mapCouponItemsToPackageRows(items) {
if (!Array.isArray(items)) return [];
return items.map((item) => {
const face = this.normalizeCouponFaceFromDetail(item);
const validHtml = item.validityText
? String(item.validityText).replace(/\n/g, "<br/>")
: "-";
return {
couponId: item.couponId,
quantity: Math.min(10, Math.max(1, Number(item.quantity) || 1)),
couponName: item.couponName || "",
faceValueLabel: face,
validRange: validHtml,
};
});
},
async hydrateCouponRowsForEdit(snippets) {
const rows = [];
for (const s of snippets) {
const cid = s.couponId;
const qty = s.quantity;
try {
const res = await getPlatformCoupon(cid);
if (res && res.success && res.result) {
rows.push(this.buildCouponDisplayRow(res.result, qty));
} else {
rows.push({
couponId: cid,
quantity: Math.min(10, Math.max(1, Number(qty) || 1)),
couponName: String(cid),
faceValueLabel: "-",
validRange: "-",
});
}
} catch (e) {
rows.push({
couponId: cid,
quantity: Math.min(10, Math.max(1, Number(qty) || 1)),
couponName: String(cid),
faceValueLabel: "-",
validRange: "-",
});
}
}
this.$set(this.formEdit, "couponPackageRows", rows);
},
handleCouponPickerClose() {
this.couponPickerVisible = false;
},
openCouponPicker(which) {
this.couponPickerWhich = which;
this.couponPickerVisible = true;
},
/** 与 coupon-publish `selectedCoupon`:表格多选变更时同步到券礼包配置 */
onCouponTemplateSelected(selectedRows) {
const which = this.couponPickerWhich;
const form = which === "add" ? this.formAdd : this.formEdit;
const refName = which === "add" ? "addForm" : "editForm";
const list = selectedRows || [];
const rows = list.map((row) => {
const existing = (form.couponPackageRows || []).find((r) => String(r.couponId) === String(row.id));
const qty = existing ? existing.quantity : 1;
return this.buildCouponDisplayRow(row, qty);
});
this.$set(form, "couponPackageRows", rows);
this.$nextTick(() => {
const ref = this.$refs[refName];
if (ref) ref.validateField("couponPackageRows");
});
},
removeCouponRow(which, index) {
const form = which === "add" ? this.formAdd : this.formEdit;
const refName = which === "add" ? "addForm" : "editForm";
form.couponPackageRows.splice(index, 1);
this.$nextTick(() => {
const ref = this.$refs[refName];
if (ref) ref.validateField("couponPackageRows");
});
},
init() {
this.loadBenefitTypes();
this.getData();
},
loadBenefitTypes() {
API_Member.getMemberBenefitTypes().then((res) => {
if (res && res.success && Array.isArray(res.result)) {
this.benefitTypeOptions = res.result;
} else {
this.benefitTypeOptions = [];
}
});
},
changePage(page) {
this.searchForm.pageNumber = page;
this.getData();
},
changePageSize(size) {
this.searchForm.pageSize = size;
this.searchForm.pageNumber = 1;
this.getData();
},
getData() {
this.loading = true;
const params = { ...this.searchForm };
API_Member.getMemberBenefitByPage(params).then((res) => {
this.loading = false;
if (res && res.success && res.result) {
this.data = Array.isArray(res.result.records) ? res.result.records : [];
this.total = Number(res.result.total) || 0;
} else {
this.data = [];
this.total = 0;
}
});
},
openAdd() {
this.addFlag = true;
this.submitAddLoading = false;
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = buildDefaultForm();
});
},
submitAdd() {
if (!this.$refs.addForm) {
this.$Message.warning("表单未就绪,请稍后重试");
return;
}
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
const { giftPoint, couponPackageRows, ...rest } = this.formAdd;
const payload = {
...rest,
benefitConfig: this.buildBenefitConfigString({
benefitType: this.formAdd.benefitType,
giftPoint,
couponPackageRows,
}),
};
API_Member.addMemberBenefit(payload).then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
this.$Message.success("添加成功");
this.addFlag = false;
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
} else {
this.$Message.error("添加失败");
}
}).catch(() => {
this.submitAddLoading = false;
this.$Message.error("网络异常,请稍后重试");
});
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
this.$nextTick(() => {
if (this.$refs.editForm) this.$refs.editForm.resetFields();
});
API_Member.getMemberBenefit(row.id).then((res) => {
if (res && res.success && res.result) {
const raw = res.result;
/** 新接口:{ benefit, couponItems };旧接口:扁平 MemberBenefit */
const detail = raw.benefit != null ? raw.benefit : raw;
const couponItems = raw.couponItems;
const bt = detail.benefitType || "";
const parsed = this.parseGiftPointFromConfig(bt, detail.benefitConfig || "");
const couponSnippets = this.parseCouponsFromConfig(detail.benefitConfig || "");
let couponRows = [];
if (bt === COUPON_PACKAGE && Array.isArray(couponItems) && couponItems.length) {
couponRows = this.mapCouponItemsToPackageRows(couponItems);
}
this.formEdit = {
id: detail.id || row.id || "",
benefitName: detail.benefitName || "",
benefitCode: detail.benefitCode || "",
benefitType: bt,
benefitLogo: detail.benefitLogo || "",
benefitDesc: detail.benefitDesc || "",
benefitSort: Number(detail.benefitSort) > 0 ? Number(detail.benefitSort) : 1,
benefitState: detail.benefitState || "OPEN",
benefitConfig: detail.benefitConfig || "",
giftPoint: bt === GIFT_POINT ? (parsed !== null ? parsed : null) : null,
couponPackageRows: couponRows,
};
if (bt === COUPON_PACKAGE && couponRows.length === 0 && couponSnippets.length) {
this.hydrateCouponRowsForEdit(couponSnippets);
}
}
});
},
submitEdit() {
if (!this.$refs.editForm) {
this.$Message.warning("表单未就绪,请稍后重试");
return;
}
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, giftPoint, couponPackageRows, ...rest } = this.formEdit;
const payload = {
...rest,
benefitConfig: this.buildBenefitConfigString({
benefitType: this.formEdit.benefitType,
giftPoint,
couponPackageRows,
}),
};
API_Member.updateMemberBenefit(id, payload).then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
this.$Message.success("修改成功");
this.editFlag = false;
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
} else {
this.$Message.error("修改失败");
}
}).catch(() => {
this.submitEditLoading = false;
this.$Message.error("网络异常,请稍后重试");
});
});
},
onBenefitStateSwitch(row, checked) {
const nextState = checked ? "OPEN" : "CLOSE";
const prevState = row.benefitState;
if (nextState === prevState) return;
const text = checked ? "开启" : "关闭";
this.$Modal.confirm({
title: "提示",
content: `<p>确定${text}该客户权益?</p>`,
onOk: () => {
this.$set(row, "_benefitStateLoading", true);
return API_Member.updateMemberBenefitState(row.id, nextState)
.then((res) => {
this.$set(row, "_benefitStateLoading", false);
if (res && res.success) {
this.$Message.success(`${text}成功`);
this.$set(row, "benefitState", nextState);
} else {
this.getData();
}
})
.catch(() => {
this.$set(row, "_benefitStateLoading", false);
});
},
});
},
remove(row) {
this.$Modal.confirm({
title: "提示",
content: "<p>确定删除该客户权益?</p>",
onOk: () => {
API_Member.deleteMemberBenefit(row.id).then((res) => {
if (res && res.success) {
this.$Message.success("删除成功");
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
}
});
},
});
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss" scoped>
.benefit-drawer-footer-btns {
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #e8eaec;
text-align: right;
}
.benefit-drawer-footer-btns .ivu-btn + .ivu-btn {
margin-left: 8px;
}
</style>
<!--
权益 LOGO 缩略图固定 100×100
不使用 .member-benefit 前缀Modal 默认 transfer body弹窗内节点不在 .member-benefit
Table render 生成的节点在子组件内同样用非 scoped
-->
<style lang="scss">
.benefit-logo-thumb {
width: 100px;
height: 100px;
min-width: 100px;
min-height: 100px;
max-width: 100px;
max-height: 100px;
flex-shrink: 0;
border: 1px solid #dcdee2;
border-radius: 4px;
background: #f8f8f9;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.benefit-logo-thumb img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.benefit-logo-thumb--table {
margin: 0 auto;
}
/* 权益列表:固定类型/名称列宽并省略过长文案(悬浮 title 可看全文) */
.member-benefit .benefit-list-table table {
table-layout: fixed;
}
.member-benefit .benefit-list-cell-ellipsis {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.member-benefit .benefit-list-cell-ellipsis--type {
max-width: 118px;
}
.member-benefit .benefit-list-cell-ellipsis--name {
max-width: 172px;
}
/* 列表不出现列与列之间的竖线(保留行间分隔) */
.member-benefit .benefit-list-table--no-vertical-borders .ivu-table th,
.member-benefit .benefit-list-table--no-vertical-borders .ivu-table td {
border-right: none !important;
}
</style>
<style lang="scss" scoped>
.benefit-logo-field {
display: flex;
align-items: flex-start;
gap: 12px;
flex-wrap: wrap;
}
.benefit-logo-upload {
flex: 1;
min-width: 200px;
}
</style>

View File

@@ -0,0 +1,197 @@
<template>
<div class="search">
<Card>
<Form
ref="searchForm"
:model="searchForm"
inline
:label-width="90"
@keydown.enter.native="handleSearch"
@submit.native.prevent
class="search-form"
>
<FormItem label="客户手机号" prop="mobile">
<Input
v-model="searchForm.memberMobile"
clearable
placeholder="请输入客户手机号"
style="width: 220px"
/>
</FormItem>
<FormItem label="规则" prop="ruleKey">
<Select v-model="searchForm.ruleKey" clearable filterable style="width: 220px">
<Option v-for="item in ruleOptions" :key="item.value" :value="item.value">
{{ item.label }}
</Option>
</Select>
</FormItem>
<Button type="primary" icon="ios-search" @click="handleSearch">搜索</Button>
</Form>
</Card>
<Card>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
class="mt_10"
></Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
</div>
</template>
<script>
import * as API_Member from "@/api/member";
const RULE_OPTIONS = [
{ value: "CONSUME", label: "消费" },
{ value: "REGISTER", label: "注册" },
{ value: "SIGN_IN", label: "签到" },
{ value: "COMMENT", label: "评价" },
{ value: "SHARE", label: "分享商城" },
{ value: "PROFILE", label: "完善信息" },
{ value: "FOLLOW_STORE", label: "关注店铺" },
{ value: "BIND_WECHAT", label: "绑定微信" },
{ value: "ADD_ADDRESS", label: "添加收货地址" },
{ value: "SHARE_REGISTER", label: "分享注册" },
{ value: "SHARE_BUY", label: "分享购买" },
];
export default {
name: "memberGradeExperienceLog",
data() {
return {
loading: false,
total: 0,
data: [],
ruleOptions: RULE_OPTIONS,
searchForm: {
pageNumber: 1,
pageSize: 20,
memberMobile: "",
ruleKey: "",
},
columns: [
{
title: "客户手机号",
key: "memberMobile",
width: 140,
tooltip: true,
render: (h, params) => {
const v = params.row.memberMobile || params.row.mobile || "-";
return h("span", v);
},
},
{
title: "规则名称",
key: "ruleName",
width: 150,
tooltip: true,
render: (h, params) => {
const text = params.row.ruleName || this.findRuleName(params.row.ruleKey) || "-";
return h("span", text);
},
},
{
title: "变化经验值",
key: "value",
width: 120,
render: (h, params) => {
const v =
params.row.value ??
params.row.variableExperience ??
params.row.experience ??
params.row.variableValue;
return h("span", v == null ? "-" : v);
},
},
{
title: "经验值限额",
key: "maxValue",
width: 120,
render: (h, params) => {
const v = params.row.maxValue ?? params.row.maxExperience ?? params.row.limitValue;
return h("span", v == null ? "-" : v);
},
},
{
title: "操作说明",
key: "content",
minWidth: 200,
tooltip: true,
render: (h, params) => {
const text = params.row.content || params.row.remark || params.row.description || "-";
return h("span", text);
},
},
{ title: "创建时间", key: "createTime", width: 170 },
],
};
},
mounted() {
this.init();
},
methods: {
findRuleName(ruleKey) {
const hit = this.ruleOptions.find((item) => item.value === ruleKey);
return hit ? hit.label : "";
},
init() {
this.getData();
},
buildParams() {
const params = {
pageNumber: this.searchForm.pageNumber,
pageSize: this.searchForm.pageSize,
};
if (this.searchForm.memberMobile) params.mobile = this.searchForm.memberMobile;
if (this.searchForm.ruleKey) params.ruleKey = this.searchForm.ruleKey;
return params;
},
getData() {
this.loading = true;
API_Member.getMemberExperienceByPage(this.buildParams())
.then((res) => {
if (res && res.success && res.result) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
} else {
this.data = [];
this.total = 0;
}
})
.finally(() => {
this.loading = false;
});
},
handleSearch() {
this.searchForm.pageNumber = 1;
this.getData();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getData();
},
},
};
</script>

View File

@@ -0,0 +1,556 @@
<template>
<div class="experience-setting">
<Card>
<Form :label-width="120" label-position="right">
<Table :loading="loading" :columns="columns" :data="form.items" class="mt_10 experience-table"></Table>
<FormItem label="经验值说明" style="margin-top: 16px" class="desc-item">
<Input
v-model="form.description"
type="textarea"
:rows="4"
maxlength="500"
show-word-limit
placeholder="请输入经验值说明"
/>
</FormItem>
<FormItem>
<Button type="primary" :loading="submitLoading" @click="submit">保存</Button>
</FormItem>
</Form>
</Card>
</div>
</template>
<script>
import { getSetting, setSetting } from "@/api/index";
const RULE_OPTIONS = [
{ ruleKey: "CONSUME", ruleName: "消费" },
{ ruleKey: "REGISTER", ruleName: "注册" },
{ ruleKey: "SIGN_IN", ruleName: "签到" },
{ ruleKey: "COMMENT", ruleName: "评价" },
{ ruleKey: "SHARE", ruleName: "分享商城" },
{ ruleKey: "PROFILE", ruleName: "完善信息" },
{ ruleKey: "FOLLOW_STORE", ruleName: "关注店铺" },
{ ruleKey: "BIND_WECHAT", ruleName: "绑定微信" },
{ ruleKey: "ADD_ADDRESS", ruleName: "添加收货地址" },
{ ruleKey: "SHARE_REGISTER", ruleName: "分享注册" },
{ ruleKey: "SHARE_BUY", ruleName: "分享购买" },
];
const defaultRuleItem = (rule) => ({
ruleKey: rule.ruleKey,
ruleName: rule.ruleName,
enabled: false,
value: 1,
maxValue: null,
});
const defaultForm = () => ({
items: RULE_OPTIONS.map((item) => defaultRuleItem(item)),
description: "",
});
export default {
name: "memberGradeExperienceSetting",
data() {
return {
loading: false,
submitLoading: false,
form: defaultForm(),
columns: [
{
title: "是否开启",
key: "enabled",
width: 90,
align: "center",
render: (h, params) => {
return h("Checkbox", {
props: { value: !!this.form.items[params.index].enabled },
on: {
"on-change": (checked) => {
this.updateRuleEnabled(params.index, checked);
},
},
});
},
},
{
title: "类型",
key: "ruleName",
width: 160,
},
{
title: "经验值(1-100)",
key: "value",
minWidth: 700,
render: (h, params) => {
const row = this.form.items[params.index] || {};
const inputNode = h("Input", {
style: { width: "120px" },
props: {
value: row.value == null ? "" : String(row.value),
number: true,
},
on: {
input: (val) => {
this.updateRuleValue(params.index, val);
},
"on-change": (v) => {
this.updateRuleValue(params.index, v);
},
"on-blur": () => {
this.commitRuleValue(params.index);
},
},
});
const maxInputNode = h("Input", {
style: { width: "120px" },
props: {
value: row.maxValue == null ? "" : String(row.maxValue),
number: true,
},
on: {
input: (val) => {
this.updateRuleMaxValue(params.index, val);
},
"on-change": (v) => {
this.updateRuleMaxValue(params.index, v);
},
"on-blur": () => {
this.commitRuleMaxValue(params.index);
},
},
});
if (params.row.ruleKey === "REGISTER") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center" } },
[h("span", { style: { marginRight: "8px" } }, "获得经验值:"), inputNode]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"会员注册成功后可获得经验值"
),
]
);
}
if (params.row.ruleKey === "SHARE") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", marginRight: "16px" } },
[h("span", { style: { marginRight: "8px" } }, "分享商品详情页获得经验值:"), inputNode]
),
h(
"div",
{ style: { display: "flex", alignItems: "center" } },
[h("span", { style: { marginRight: "8px" } }, "可获得经验值限额:"), maxInputNode]
),
]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"会员分享商城页面可获得的经验值"
),
]
);
}
if (params.row.ruleKey === "COMMENT") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[h("span", { style: { marginRight: "8px" } }, "对已购买商品完成提交评论获得经验值:"), inputNode]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"仅针对评论字数大于30字的评论进行发放"
),
]
);
}
if (params.row.ruleKey === "FOLLOW_STORE") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", marginRight: "16px" } },
[h("span", { style: { marginRight: "8px" } }, "获得经验值:"), inputNode]
),
h(
"div",
{ style: { display: "flex", alignItems: "center" } },
[h("span", { style: { marginRight: "8px" } }, "可获得经验值限额:"), maxInputNode]
),
]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"关注店铺可获得经验值每个客户D相同店铺仅第一次关注可进行获得"
),
]
);
}
if (params.row.ruleKey === "PROFILE") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[h("span", { style: { marginRight: "8px" } }, "获得经验值:"), inputNode]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"完善个人基本信息可获得经验值,每个会员仅可获得一次"
),
]
);
}
if (params.row.ruleKey === "BIND_WECHAT") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[h("span", { style: { marginRight: "8px" } }, "获取经验值:"), inputNode]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"绑定微信成功获得经验值,每个会员仅可获得一次"
),
]
);
}
if (params.row.ruleKey === "ADD_ADDRESS") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[h("span", { style: { marginRight: "8px" } }, "获取经验值:"), inputNode]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"添加收货地址后获得经验值,每个会员仅可获得一次"
),
]
);
}
if (params.row.ruleKey === "SHARE_REGISTER") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", marginRight: "16px" } },
[h("span", { style: { marginRight: "8px" } }, "获取的经验值:"), inputNode]
),
h(
"div",
{ style: { display: "flex", alignItems: "center" } },
[h("span", { style: { marginRight: "8px" } }, "可获得经验值限额:"), maxInputNode]
),
]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"仅被注册成功后才可获得相应奖励经验值"
),
]
);
}
if (params.row.ruleKey === "SHARE_BUY") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", marginRight: "16px" } },
[h("span", { style: { marginRight: "8px" } }, "获取的经验值:"), inputNode]
),
h(
"div",
{ style: { display: "flex", alignItems: "center" } },
[h("span", { style: { marginRight: "8px" } }, "可获得经验值限额:"), maxInputNode]
),
]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"仅被购买成功后才可获得相应奖励经验值"
),
]
);
}
if (params.row.ruleKey === "SIGN_IN") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[h("span", { style: { marginRight: "8px" } }, "获取的经验值:"), inputNode]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"客户每日签到后可获的经验值"
),
]
);
}
if (params.row.ruleKey === "CONSUME") {
return h(
"div",
{ style: { display: "flex", flexDirection: "column", alignItems: "flex-start" } },
[
h(
"div",
{ style: { display: "flex", alignItems: "center", flexWrap: "wrap" } },
[h("span", { style: { marginRight: "8px" } }, "1元获取经验值"), inputNode]
),
h(
"div",
{ style: { marginTop: "6px", color: "#808695", fontSize: "12px" } },
"客户消费1元可获取经验值向下取整"
),
]
);
}
return inputNode;
},
},
],
};
},
mounted() {
this.loadData();
},
methods: {
getRawInputValue(v) {
return v && v.target ? v.target.value : v;
},
updateRuleEnabled(index, enabled) {
const item = this.form.items[index] || {};
this.$set(this.form.items, index, {
...item,
enabled: !!enabled,
});
},
updateRuleValue(index, v) {
const raw = this.getRawInputValue(v);
const next = Number(raw);
const item = this.form.items[index] || {};
this.$set(this.form.items, index, {
...item,
value: Number.isFinite(next) ? next : null,
});
},
commitRuleValue(index) {
const item = this.form.items[index] || {};
const next = Number(item.value);
let value = 1;
if (Number.isFinite(next)) {
if (next < 1) value = 1;
else if (next > 100) value = 100;
else value = Math.floor(next);
}
this.$set(this.form.items, index, {
...item,
value,
});
},
updateRuleMaxValue(index, v) {
const raw = this.getRawInputValue(v);
const item = this.form.items[index] || {};
if (raw == null || raw === "") {
this.$set(this.form.items, index, {
...item,
maxValue: null,
});
return;
}
const next = Number(raw);
this.$set(this.form.items, index, {
...item,
maxValue: Number.isFinite(next) ? next : null,
});
},
commitRuleMaxValue(index) {
const item = this.form.items[index] || {};
if (item.maxValue == null || item.maxValue === "") {
this.$set(this.form.items, index, {
...item,
maxValue: null,
});
return;
}
const next = Number(item.maxValue);
this.$set(this.form.items, index, {
...item,
maxValue: Number.isFinite(next) && next >= 1 ? Math.floor(next) : null,
});
},
normalizeConfig(val) {
if (!val) return {};
if (typeof val === "string") {
try {
return JSON.parse(val);
} catch (e) {
return {};
}
}
if (typeof val === "object") return val;
return {};
},
normalizeItems(items) {
const map = {};
if (Array.isArray(items)) {
items.forEach((item) => {
if (!item || !item.ruleKey) return;
map[item.ruleKey] = item;
});
}
return RULE_OPTIONS.map((rule) => {
const hit = map[rule.ruleKey] || {};
return {
ruleKey: rule.ruleKey,
ruleName: hit.ruleName || rule.ruleName,
enabled: !!hit.enabled,
value: Number(hit.value) > 0 ? Number(hit.value) : 1,
maxValue: hit.maxValue == null || hit.maxValue === "" ? null : Number(hit.maxValue),
};
});
},
loadData() {
this.loading = true;
getSetting("EXPERIENCE_SETTING")
.then((res) => {
if (res && res.success) {
const cfg = this.normalizeConfig(res.result);
this.form = {
items: this.normalizeItems(cfg.items),
description: cfg.description || "",
};
} else {
this.form = defaultForm();
}
})
.finally(() => {
this.loading = false;
});
},
validateForm() {
const invalid = this.form.items.find((item) => {
const v = Number(item.value);
if (!Number.isInteger(v) || v < 1 || v > 100) return true;
if (item.maxValue != null && item.maxValue !== "") {
const m = Number(item.maxValue);
if (!Number.isInteger(m) || m < 1) return true;
}
return false;
});
if (invalid) {
this.$Message.error("请检查经验值配置经验值范围为1-100限额需为正整数");
return false;
}
return true;
},
submit() {
if (!this.validateForm()) return;
const payload = {
items: this.form.items.map((item) => ({
ruleKey: item.ruleKey,
ruleName: item.ruleName,
enabled: !!item.enabled,
value: Number(item.value),
maxValue: item.maxValue == null || item.maxValue === "" ? null : Number(item.maxValue),
})),
description: this.form.description || "",
};
this.submitLoading = true;
setSetting("EXPERIENCE_SETTING", payload)
.then((res) => {
if (res && res.success) {
this.$Message.success("保存成功");
}
})
.finally(() => {
this.submitLoading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.experience-setting {
padding: 2px 0;
}
::v-deep .experience-table .ivu-table th {
background: #fafbfc;
}
::v-deep .experience-table .ivu-table td {
padding-top: 12px;
padding-bottom: 12px;
}
::v-deep .experience-table .ivu-table-cell {
font-size: 13px;
line-height: 1.7;
}
::v-deep .desc-item .ivu-form-item-label {
font-size: 16px;
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,547 @@
<template>
<div class="search">
<Card>
<Row class="operation padding-row">
<Button type="primary" @click="openAdd">添加客户等级</Button>
</Row>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
class="mt_10"
></Table>
</Card>
<Modal v-model="addFlag" title="添加客户等级" width="720" :z-index="950" :mask-closable="false">
<Form ref="addForm" :model="formAdd" :rules="rules" :label-width="110">
<FormItem label="等级名称" prop="gradeName">
<Input v-model="formAdd.gradeName" maxlength="50" placeholder="请输入等级名称" />
</FormItem>
<FormItem label="是否默认" prop="isDefault">
<RadioGroup v-model="formAdd.isDefault">
<Radio :label="true"></Radio>
<Radio :label="false"></Radio>
</RadioGroup>
</FormItem>
<FormItem label="等级图标" prop="gradeImage">
<upload-pic-input v-model="formAdd.gradeImage"></upload-pic-input>
</FormItem>
<FormItem label="等级背景图" prop="gradeBackground">
<upload-pic-input v-model="formAdd.gradeBackground"></upload-pic-input>
</FormItem>
<FormItem label="字体颜色" prop="gradeFontColor">
<Input v-model="formAdd.gradeFontColor" maxlength="20" placeholder="如:#333333" />
</FormItem>
<FormItem label="所需经验值" prop="requiredExperience">
<InputNumber v-model="formAdd.requiredExperience" :min="1" :precision="0" style="width: 220px"></InputNumber>
</FormItem>
<FormItem label="等级排序" prop="gradeSort">
<InputNumber v-model="formAdd.gradeSort" :min="1" :max="9999" :precision="0" style="width: 220px"></InputNumber>
</FormItem>
<FormItem label="等级开关" prop="gradeState">
<RadioGroup v-model="formAdd.gradeState">
<Radio label="OPEN">开启</Radio>
<Radio label="CLOSE">关闭</Radio>
</RadioGroup>
</FormItem>
<FormItem label="关联权益" prop="benefitIds">
<Select
:value="addBenefitOrder"
multiple
filterable
placeholder="请选择客户权益"
style="width: 100%"
@on-change="onAddBenefitIdsChange"
>
<Option v-for="b in benefitOptions" :key="b.id" :value="String(b.id)">{{ benefitOptionLabel(b) }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer">
<Button @click="addFlag = false">取消</Button>
<Button type="primary" :loading="submitAddLoading" @click="submitAdd">确定</Button>
</div>
</Modal>
<Modal v-model="editFlag" title="编辑客户等级" width="720" :z-index="950" :mask-closable="false">
<Form ref="editForm" :model="formEdit" :rules="rules" :label-width="110">
<Input v-model="formEdit.id" v-show="false" />
<FormItem label="等级名称" prop="gradeName">
<Input v-model="formEdit.gradeName" maxlength="50" placeholder="请输入等级名称" />
</FormItem>
<FormItem label="是否默认" prop="isDefault">
<RadioGroup v-model="formEdit.isDefault">
<Radio :label="true"></Radio>
<Radio :label="false"></Radio>
</RadioGroup>
</FormItem>
<FormItem label="等级图标" prop="gradeImage">
<upload-pic-input v-model="formEdit.gradeImage"></upload-pic-input>
</FormItem>
<FormItem label="等级背景图" prop="gradeBackground">
<upload-pic-input v-model="formEdit.gradeBackground"></upload-pic-input>
</FormItem>
<FormItem label="字体颜色" prop="gradeFontColor">
<Input v-model="formEdit.gradeFontColor" maxlength="20" placeholder="如:#333333" />
</FormItem>
<FormItem label="所需经验值" prop="requiredExperience">
<InputNumber v-model="formEdit.requiredExperience" :min="1" :precision="0" style="width: 220px"></InputNumber>
</FormItem>
<FormItem label="等级排序" prop="gradeSort">
<InputNumber v-model="formEdit.gradeSort" :min="1" :max="9999" :precision="0" style="width: 220px"></InputNumber>
</FormItem>
<FormItem label="等级开关" prop="gradeState">
<RadioGroup v-model="formEdit.gradeState">
<Radio label="OPEN">开启</Radio>
<Radio label="CLOSE">关闭</Radio>
</RadioGroup>
</FormItem>
<FormItem label="关联权益" prop="benefitIds">
<Select
:value="editBenefitOrder"
multiple
filterable
placeholder="请选择客户权益"
style="width: 100%"
@on-change="onEditBenefitIdsChange"
>
<Option v-for="b in benefitOptions" :key="b.id" :value="String(b.id)">{{ benefitOptionLabel(b) }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer">
<Button @click="editFlag = false">取消</Button>
<Button type="primary" :loading="submitEditLoading" @click="submitEdit">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import * as API_Member from "@/api/member.js";
import uploadPicInput from "@/components/lili/upload-pic-input";
const buildDefaultForm = () => ({
id: "",
gradeName: "",
isDefault: false,
gradeImage: "",
gradeBackground: "",
gradeFontColor: "",
requiredExperience: 1,
gradeSort: 1,
gradeState: "OPEN",
benefitIds: "",
});
/** 权益多选:新勾选追加到末尾,取消勾选移除,保持已有顺序 */
function syncOrderedBenefitIds(prevOrder, selected) {
const sel = Array.isArray(selected) ? selected.map((id) => String(id)) : [];
const out = [];
(prevOrder || []).forEach((id) => {
const s = String(id);
if (sel.includes(s)) out.push(s);
});
sel.forEach((s) => {
if (!out.includes(s)) out.push(s);
});
return out;
}
export default {
name: "memberGrade",
components: {
uploadPicInput,
},
data() {
return {
loading: true,
columns: [
{
title: "等级名称",
key: "gradeName",
width: 130,
tooltip: true,
},
{
title: "默认等级",
key: "isDefault",
width: 95,
render: (h, params) => {
const yes = params.row.isDefault === true;
return h(
"Tag",
{
props: {
color: yes ? "success" : "default",
},
},
yes ? "是" : "否"
);
},
},
{
title: "等级图标",
key: "gradeImage",
width: 100,
render: (h, params) => {
if (!params.row.gradeImage) return h("span", "-");
return h("img", {
attrs: {
src: params.row.gradeImage,
alt: "等级图标",
},
style: {
width: "48px",
height: "48px",
objectFit: "contain",
border: "1px solid #dcdee2",
borderRadius: "4px",
background: "#fff",
},
});
},
},
{
title: "等级背景图",
key: "gradeBackground",
width: 120,
render: (h, params) => {
if (!params.row.gradeBackground) return h("span", "-");
return h("img", {
attrs: {
src: params.row.gradeBackground,
alt: "等级背景图",
},
style: {
width: "64px",
height: "40px",
objectFit: "cover",
border: "1px solid #dcdee2",
borderRadius: "4px",
background: "#fff",
},
});
},
},
{
title: "所需经验值",
key: "requiredExperience",
width: 110,
},
{
title: "等级排序",
key: "gradeSort",
width: 95,
},
{
title: "状态",
key: "gradeState",
width: 118,
align: "center",
render: (h, params) => {
const row = params.row;
return h("i-switch", {
props: {
value: row.gradeState === "OPEN",
size: "large",
loading: !!row._gradeStateLoading,
},
on: {
"on-change": (checked) => {
this.onGradeStateSwitch(row, checked);
},
},
}, [
h("span", { slot: "open" }, "开启"),
h("span", { slot: "close" }, "关闭"),
]);
},
},
{
title: "操作",
key: "action",
align: "center",
width: 130,
render: (h, params) => {
const linkStyle = {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
};
const sep = h(
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
);
return h(
"div",
{ class: "ops", style: { display: "flex", justifyContent: "center" } },
[
h(
"a",
{ style: linkStyle, on: { click: () => this.openEdit(params.row) } },
"编辑"
),
sep,
h(
"a",
{ style: linkStyle, on: { click: () => this.remove(params.row) } },
"删除"
),
]
);
},
},
],
data: [],
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: buildDefaultForm(),
formEdit: buildDefaultForm(),
/** 关联权益 id 顺序(与 benefitIds 一致) */
addBenefitOrder: [],
editBenefitOrder: [],
benefitOptions: [],
benefitTypeOptions: [],
benefitOptionsLoading: false,
editDetailLoading: false,
rules: {
gradeName: [{ required: true, message: "请输入等级名称", trigger: "blur" }],
gradeImage: [{ required: true, message: "请上传等级图标", trigger: "change" }],
requiredExperience: [{ required: true, type: "number", message: "请输入所需经验值", trigger: "change" }],
gradeSort: [{ required: true, type: "number", message: "请输入等级排序", trigger: "change" }],
gradeState: [{ required: true, message: "请选择等级开关", trigger: "change" }],
},
};
},
methods: {
benefitOptionLabel(b) {
if (!b) return "";
const name = b.benefitName || String(b.id);
const opt = this.benefitTypeOptions.find((o) => o.value === b.benefitType);
const typeText = opt ? opt.description : b.benefitType || "";
return typeText ? `${name}${typeText}` : name;
},
onAddBenefitIdsChange(val) {
this.addBenefitOrder = syncOrderedBenefitIds(this.addBenefitOrder, val);
},
onEditBenefitIdsChange(val) {
this.editBenefitOrder = syncOrderedBenefitIds(this.editBenefitOrder, val);
},
loadBenefitTypes() {
API_Member.getMemberBenefitTypes().then((res) => {
if (res && res.success && Array.isArray(res.result)) {
this.benefitTypeOptions = res.result;
} else {
this.benefitTypeOptions = [];
}
});
},
loadBenefitOptions() {
this.benefitOptionsLoading = true;
const pageSize = 500;
const fetchPage = (pageNumber) =>
API_Member.getMemberBenefitByPage({ pageNumber, pageSize, sort: "benefitSort", order: "asc" });
return fetchPage(1)
.then((res) => {
if (!(res && res.success && res.result)) {
this.benefitOptions = [];
return;
}
const records = Array.isArray(res.result.records) ? res.result.records : [];
const total = Number(res.result.total) || records.length;
let all = records.slice();
if (total > pageSize) {
const pages = Math.ceil(total / pageSize);
const rest = [];
for (let p = 2; p <= pages; p++) {
rest.push(fetchPage(p));
}
return Promise.all(rest).then((results) => {
results.forEach((r) => {
if (r && r.success && r.result && Array.isArray(r.result.records)) {
all = all.concat(r.result.records);
}
});
this.benefitOptions = all;
});
}
this.benefitOptions = all;
})
.catch(() => {
this.benefitOptions = [];
})
.finally(() => {
this.benefitOptionsLoading = false;
});
},
parseBenefitIdsFromString(str) {
return String(str || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
},
fillEditFormFromGrade(grade, benefitsOrderedIds) {
this.formEdit = {
id: grade.id || "",
gradeName: grade.gradeName || "",
isDefault: grade.isDefault === true,
gradeImage: grade.gradeImage || "",
gradeBackground: grade.gradeBackground || "",
gradeFontColor: grade.gradeFontColor || "",
requiredExperience: Number(grade.requiredExperience) > 0 ? Number(grade.requiredExperience) : 1,
gradeSort: Number(grade.gradeSort) > 0 ? Number(grade.gradeSort) : 1,
gradeState: grade.gradeState || "OPEN",
benefitIds: grade.benefitIds || "",
};
this.editBenefitOrder = (benefitsOrderedIds || []).map((id) => String(id));
},
init() {
this.getData();
this.loadBenefitTypes();
this.loadBenefitOptions();
},
getData() {
this.loading = true;
API_Member.getMemberGradeByPage().then((res) => {
this.loading = false;
if (res && res.success) {
this.data = Array.isArray(res.result) ? res.result : [];
}
});
},
openAdd() {
this.addFlag = true;
this.submitAddLoading = false;
if (!this.benefitOptions.length && !this.benefitOptionsLoading) {
this.loadBenefitOptions();
}
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = buildDefaultForm();
this.addBenefitOrder = [];
});
},
submitAdd() {
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
const { benefitIds: _omit, ...rest } = this.formAdd;
const payload = {
...rest,
benefitIds: (this.addBenefitOrder || []).join(","),
};
API_Member.addMemberGrade(payload).then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
this.$Message.success("添加成功");
this.addFlag = false;
this.getData();
}
});
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
this.editDetailLoading = true;
if (!this.benefitOptions.length && !this.benefitOptionsLoading) {
this.loadBenefitOptions();
}
this.$nextTick(() => {
if (this.$refs.editForm) this.$refs.editForm.resetFields();
});
API_Member.getMemberGrade(row.id)
.then((res) => {
this.editDetailLoading = false;
if (res && res.success && res.result) {
const raw = res.result;
const grade = raw.grade != null ? raw.grade : raw;
const benefits = Array.isArray(raw.benefits) ? raw.benefits : [];
const orderedIds = benefits.length
? benefits.map((b) => b.id).filter((id) => id != null && id !== "")
: String(grade.benefitIds || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
this.fillEditFormFromGrade(grade, orderedIds);
} else {
this.fillEditFormFromGrade(row, this.parseBenefitIdsFromString(row.benefitIds));
}
})
.catch(() => {
this.editDetailLoading = false;
this.fillEditFormFromGrade(row, this.parseBenefitIdsFromString(row.benefitIds));
});
},
submitEdit() {
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, benefitIds: _omit, ...rest } = this.formEdit;
const payload = {
...rest,
benefitIds: (this.editBenefitOrder || []).join(","),
};
API_Member.updateMemberGrade(id, payload).then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
this.$Message.success("修改成功");
this.editFlag = false;
this.getData();
}
});
});
},
onGradeStateSwitch(row, checked) {
const nextState = checked ? "OPEN" : "CLOSE";
const prevState = row.gradeState;
if (nextState === prevState) return;
const text = checked ? "开启" : "关闭";
this.$Modal.confirm({
title: "提示",
content: `<p>确定${text}该客户等级?</p>`,
onOk: () => {
this.$set(row, "_gradeStateLoading", true);
return API_Member.updateMemberGradeState(row.id, nextState)
.then((res) => {
this.$set(row, "_gradeStateLoading", false);
if (res && res.success) {
this.$Message.success(`${text}成功`);
this.$set(row, "gradeState", nextState);
} else {
this.getData();
}
})
.catch(() => {
this.$set(row, "_gradeStateLoading", false);
});
},
});
},
remove(row) {
this.$Modal.confirm({
title: "提示",
content: "<p>确定删除该客户等级?</p>",
onOk: () => {
API_Member.deleteMemberGrade(row.id).then((res) => {
if (res && res.success) {
this.$Message.success("删除成功");
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
}
});
},
});
},
},
mounted() {
this.init();
},
};
</script>

View File

@@ -0,0 +1,257 @@
<template>
<div class="search">
<Card>
<Row class="operation padding-row">
<Button type="primary" @click="openAdd">添加分组</Button>
</Row>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
ref="table"
class="mt_10"
></Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="searchForm.pageNumber"
:total="total"
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
<Modal v-model="addFlag" title="添加分组">
<Form ref="addForm" :model="formAdd" :rules="rulesAdd" :label-width="90">
<FormItem label="分组名称" prop="groupName" style="width: 90%;">
<Input v-model="formAdd.groupName" maxlength="30" placeholder="请输入分组名称" />
</FormItem>
<FormItem label="分组描述" prop="description" style="width: 90%;">
<Input v-model="formAdd.description" maxlength="200" placeholder="请输入分组描述" />
</FormItem>
</Form>
<div slot="footer">
<Button @click="addFlag = false">取消</Button>
<Button type="primary" :loading="submitAddLoading" @click="submitAdd">确定</Button>
</div>
</Modal>
<Modal v-model="editFlag" title="编辑分组">
<Form ref="editForm" :model="formEdit" :rules="rulesEdit" :label-width="90">
<Input v-model="formEdit.id" v-show="false" />
<FormItem label="分组名称" prop="groupName" style="width: 90%;">
<Input v-model="formEdit.groupName" maxlength="30" placeholder="请输入分组名称" />
</FormItem>
<FormItem label="分组描述" prop="description" style="width: 90%;">
<Input v-model="formEdit.description" maxlength="200" placeholder="请输入分组描述" />
</FormItem>
</Form>
<div slot="footer">
<Button @click="editFlag = false">取消</Button>
<Button type="primary" :loading="submitEditLoading" @click="submitEdit">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import * as API_Member from "@/api/member.js";
export default {
name: "memberGroup",
data() {
return {
loading: true,
searchForm: {
pageNumber: 1,
pageSize: 20,
},
columns: [
{
title: "分组名称",
key: "groupName",
minWidth: 160,
tooltip: true,
},
{
title: "分组描述",
key: "description",
minWidth: 240,
tooltip: true,
},
{
title: "创建时间",
key: "createTime",
width: 180,
},
{
title: "更新时间",
key: "updateTime",
width: 180,
},
{
title: "操作",
key: "action",
align: "center",
width: 200,
fixed: "right",
render: (h, params) => {
const linkStyle = {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
};
const sep = h(
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
);
const children = [
h(
"a",
{ style: linkStyle, on: { click: () => this.openEdit(params.row) } },
"编辑"
),
sep,
h(
"a",
{ style: linkStyle, on: { click: () => this.remove(params.row) } },
"删除"
),
];
return h(
"div",
{ class: "ops", style: { display: "flex", justifyContent: "center" } },
children
);
},
},
],
data: [],
total: 0,
addFlag: false,
editFlag: false,
submitAddLoading: false,
submitEditLoading: false,
formAdd: {
groupName: "",
description: "",
},
formEdit: {
id: "",
groupName: "",
description: "",
},
rulesAdd: {
groupName: [{ required: true, message: "请输入分组名称" }],
},
rulesEdit: {
groupName: [{ required: true, message: "请输入分组名称" }],
},
};
},
methods: {
init() {
this.getData();
},
changePage(v) {
this.searchForm.pageNumber = v;
this.getData();
},
changePageSize(v) {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = v;
this.getData();
},
getData() {
this.loading = true;
API_Member.getMemberGroupByPage(this.searchForm).then((res) => {
this.loading = false;
if (res && res.success && res.result) {
this.data = res.result.records || [];
this.total = res.result.total || 0;
}
});
},
openAdd() {
this.addFlag = true;
this.$nextTick(() => {
if (this.$refs.addForm) this.$refs.addForm.resetFields();
this.formAdd = { groupName: "", description: "" };
});
},
submitAdd() {
this.$refs.addForm.validate((valid) => {
if (!valid) return;
this.submitAddLoading = true;
API_Member.addMemberGroup(this.formAdd).then((res) => {
this.submitAddLoading = false;
if (res && res.success) {
this.$Message.success("添加成功");
this.addFlag = false;
this.getData();
}
});
});
},
openEdit(row) {
this.editFlag = true;
this.submitEditLoading = false;
API_Member.getMemberGroup(row.id).then((res) => {
if (res && res.success && res.result) {
this.formEdit = {
id: res.result.id,
groupName: res.result.groupName || "",
description: res.result.description || "",
};
} else {
this.formEdit = {
id: row.id,
groupName: row.groupName || "",
description: row.description || "",
};
}
});
},
submitEdit() {
this.$refs.editForm.validate((valid) => {
if (!valid) return;
this.submitEditLoading = true;
const { id, groupName, description } = this.formEdit;
API_Member.updateMemberGroup(id, { groupName, description }).then((res) => {
this.submitEditLoading = false;
if (res && res.success) {
this.$Message.success("修改成功");
this.editFlag = false;
this.getData();
}
});
});
},
remove(row) {
this.$Modal.confirm({
title: "提示",
content: "<p>确定删除该分组?</p>",
onOk: () => {
API_Member.deleteMemberGroup(row.id).then((res) => {
if (res && res.success) {
this.$Message.success("删除成功");
this.getData();
} else if (res && res.message) {
this.$Message.error(res.message);
}
});
},
});
},
},
mounted() {
this.init();
},
};
</script>

View File

@@ -3,28 +3,57 @@
<Card>
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="会员ID" prop="id">
<Input type="text" v-model="searchForm.id" placeholder="请输入会员ID" clearable style="width: 240px" />
</Form-item>
<Form-item label="会员名称" prop="username">
<Input type="text" v-model="searchForm.username" placeholder="请输入会员名称" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.username" placeholder="请输入会员名称" clearable style="width: 240px" />
</Form-item>
<Form-item label="会员昵称" prop="nickName">
<Input type="text" v-model="searchForm.nickName" placeholder="请输入会员昵称" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.nickName" placeholder="请输入会员昵称" clearable style="width: 240px" />
</Form-item>
<Form-item label="联系方式" prop="mobile">
<Input type="text" v-model="searchForm.mobile" placeholder="请输入会员联系方式" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.mobile" placeholder="请输入会员联系方式" clearable style="width: 240px" />
</Form-item>
<Form-item label="会员分组" prop="groupId">
<Select v-model="searchForm.groupId" clearable filterable style="width: 240px">
<Option v-for="item in memberGroupList" :value="item.id" :key="item.id">{{ item.groupName }}</Option>
</Select>
</Form-item>
<Form-item label="会员等级" prop="gradeId">
<Select v-model="searchForm.gradeId" clearable filterable style="width: 240px">
<Option v-for="item in memberGradeList" :value="item.id" :key="item.id">{{ item.gradeName }}</Option>
</Select>
</Form-item>
<Button @click="handleSearch" class="search-btn" type="primary" icon="ios-search">搜索</Button>
</Form>
</Row>
</Card>
<Card>
<Row class="operation padding-row" v-if="!selectedMember">
<Button @click="addMember" type="primary">添加会员</Button>
<Button
style="margin-left: 10px;"
type="primary"
:disabled="selectedRows.length === 0"
@click="openSetMemberGroup"
>设定会员分组</Button>
</Row>
<Table :loading="loading" border :columns="columns" class="mt_10" :data="data" ref="table"></Table>
<Table
:loading="loading"
:columns="tableColumns"
class="mt_10"
:data="data"
ref="table"
@on-selection-change="onSelectionChange"
></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage"
@on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small" show-total show-elevator
@on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]" size="small" show-total show-elevator
show-sizer></Page>
</Row>
</Card>
@@ -93,6 +122,47 @@
</FormItem>
</Form>
</Modal>
<Modal v-model="walletIncreaseFlag" title="增加余额" width="420">
<Form ref="walletIncreaseForm" :model="walletIncreaseForm" :rules="walletIncreaseRule" :label-width="90">
<FormItem label="充值金额" prop="rechargeMoney">
<InputNumber v-model="walletIncreaseForm.rechargeMoney" :min="0.01" :precision="2" style="width: 240px" />
</FormItem>
</Form>
<div slot="footer">
<Button @click="walletIncreaseFlag = false">取消</Button>
<Button type="primary" :loading="walletIncreaseLoading" @click="submitWalletIncrease">确定</Button>
</div>
</Modal>
<Modal v-model="memberPointFlag" title="修改积分" width="420">
<Form ref="memberPointForm" :model="memberPointForm" :rules="memberPointRule" :label-width="90">
<FormItem label="类型" prop="type">
<RadioGroup type="button" button-style="solid" v-model="memberPointForm.type">
<Radio label="INCREASE">增加</Radio>
<Radio label="REDUCE">减少</Radio>
</RadioGroup>
</FormItem>
<FormItem label="积分" prop="point">
<InputNumber v-model="memberPointForm.point" :min="1" :precision="0" style="width: 240px" />
</FormItem>
</Form>
<div slot="footer">
<Button @click="memberPointFlag = false">取消</Button>
<Button type="primary" :loading="memberPointLoading" @click="submitMemberPoint">确定</Button>
</div>
</Modal>
<Modal v-model="memberGroupFlag" title="设定会员分组" width="420">
<Form ref="memberGroupForm" :model="memberGroupForm" :rules="memberGroupRule" :label-width="90">
<FormItem label="会员分组" prop="groupId">
<Select v-model="memberGroupForm.groupId" clearable filterable style="width: 240px">
<Option v-for="item in memberGroupList" :value="item.id" :key="item.id">{{ item.groupName }}</Option>
</Select>
</FormItem>
</Form>
<div slot="footer">
<Button @click="memberGroupFlag = false">取消</Button>
<Button type="primary" :loading="memberGroupLoading" @click="submitSetMemberGroup">确定</Button>
</div>
</Modal>
<Modal width="1200px" v-model="picModelFlag">
<ossManage @callback="callbackSelected" :isComponent="true" :initialize="picModelFlag" ref="ossManage" />
</Modal>
@@ -112,6 +182,19 @@ export default {
multipleMap,
ossManage,
},
computed: {
tableColumns() {
if (this.selectedMember) return this.columns;
return [
{
type: "selection",
width: 60,
align: "center",
},
...this.columns,
];
},
},
data() {
return {
defaultPic:require('@/assets/default.png'),
@@ -129,14 +212,71 @@ export default {
searchForm: {
// 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20, // 页面大小
order: "desc",
id: "",
username: "",
mobile: "",
disabled: "OPEN",
groupId: "",
},
picModelFlag: false, // 选择图片
form: {}, // 表单数据
walletIncreaseFlag: false,
walletIncreaseLoading: false,
walletIncreaseForm: {
memberId: "",
rechargeMoney: null,
},
walletIncreaseRule: {
rechargeMoney: [
{ required: true, type: "number", message: "请输入充值金额", trigger: "change" },
{
validator: (rule, value, callback) => {
if (typeof value !== "number" || value <= 0) {
callback(new Error("充值金额必须大于0"));
return;
}
callback();
},
trigger: "change",
},
],
},
memberPointFlag: false,
memberPointLoading: false,
memberPointForm: {
memberId: "",
point: null,
type: "INCREASE",
},
selectedRows: [],
memberGroupFlag: false,
memberGroupLoading: false,
memberGroupForm: {
groupId: "",
},
memberGroupRule: {
groupId: [{ required: true, message: "请选择会员分组", trigger: "change" }],
},
memberGroupList: [],
memberGradeList: [],
memberPointRule: {
type: [{ required: true, message: "请选择类型", trigger: "change" }],
point: [
{ required: true, type: "number", message: "请输入积分", trigger: "change" },
{
validator: (rule, value, callback) => {
if (typeof value !== "number" || value <= 0) {
callback(new Error("积分必须大于0"));
return;
}
callback();
},
trigger: "change",
},
],
},
addRule: {
// 验证规则
mobile: [
@@ -151,19 +291,47 @@ export default {
},
ruleValidate: {}, //修改验证
columns: [
{
title: "会员ID",
key: "id",
minWidth: 120, // 减少宽度
tooltip: true,
},
{
title: "头像",
key: "face",
minWidth: 80,
align: "center",
render: (h, params) => {
return h("img", {
attrs: {
src: params.row.face || require('@/assets/default.png'),
alt: "头像"
},
style: {
width: "30px",
height: "30px",
borderRadius: "50%",
objectFit: "cover"
}
});
}
},
{
title: "会员名称",
key: "username",
tooltip: true,
minWidth: 150, // 减少宽度
},
{
title: "会员昵称",
key: "nickName",
tooltip: true,
minWidth: 120, // 减少宽度
},
{
title: "联系方式",
width: 130,
minWidth: 130,
key: "mobile",
render: (h, params) => {
if (params.row.mobile == null) {
@@ -176,13 +344,18 @@ export default {
{
title: "注册时间",
key: "createTime",
width: 180,
minWidth: 160, // 减少宽度
},
{
title: "最后登录时间",
key: "lastLoginDate",
minWidth: 160, // 减少宽度
},
{
title: "积分数量",
align: "left",
width: 100,
minWidth: 120, // 增加宽度
render: (h, params) => {
return h(
"div",
@@ -191,13 +364,39 @@ export default {
);
},
},
{
title: "经验值",
key: "experience",
minWidth: 100,
render: (h, params) => {
return h("div", {}, params.row.experience == null ? "0" : params.row.experience);
},
},
{
title: "会员等级",
key: "gradeName",
minWidth: 120,
tooltip: true,
render: (h, params) => {
return h("div", {}, params.row.gradeName || "-");
},
},
{
title: "余额",
key: "memberWallet",
width: 120,
render: (h, params) => {
return h("priceColorScheme", {props:{value:params.row.memberWallet}} );
},
},
{
title: "操作",
key: "action",
align: "center",
width: 200,
minWidth: 160,
fixed: "right",
render: (h, params) => {
if (this.selectedMember) {
return h(
"div",
{
@@ -208,15 +407,12 @@ export default {
},
[
h(
"Button",
"a",
{
props: {
size: "small",
type: params.row.___selected ? "primary" : "default",
},
style: {
marginRight: "5px",
display: this.selectedMember ? "block" : "none",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -226,17 +422,28 @@ export default {
},
params.row.___selected ? "已选择" : "选择"
),
]
);
}
h(
"Button",
const divider = h(
"span",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
display: this.selectedMember ? "none" : "block",
margin: "0 8px",
color: "#dcdee2",
},
},
"|"
);
const viewLink = h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -245,47 +452,112 @@ export default {
},
},
"查看"
),
h(
"Button",
);
const moreDropdown = h(
"Dropdown",
{
props: {
type: "info",
size: "small",
ghost: true,
},
style: {
marginRight: "5px",
display: this.selectedMember ? "none" : "block",
trigger: "click",
transfer: true,
},
on: {
click: () => {
"on-click": (name) => {
if (name === "edit") {
this.editPerm(params.row);
}
if (name === "disabled") {
this.disabled(params.row);
}
if (name === "increaseWallet") {
this.openWalletIncrease(params.row);
}
if (name === "updatePoint") {
this.openMemberPoint(params.row);
}
},
},
},
"编辑"
),
[
h(
"Button",
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
display: "inline-flex",
alignItems: "center",
},
},
[
h("span", "更多"),
h("Icon", {
props: {
size: "small",
type: "error",
type: "md-arrow-dropdown",
},
style: {
marginRight: "5px",
display: this.selectedMember ? "none" : "block",
marginLeft: "4px",
},
on: {
click: () => {
this.disabled(params.row);
}),
]
),
h(
"DropdownMenu",
{
slot: "list",
},
[
h(
"DropdownItem",
{
props: {
name: "edit",
},
},
"编辑会员"
),
h(
"DropdownItem",
{
props: {
name: "increaseWallet",
},
"禁用"
},
"增加余额"
),
h(
"DropdownItem",
{
props: {
name: "updatePoint",
},
},
"修改积分"
),
h(
"DropdownItem",
{
props: {
name: "disabled",
},
},
"禁用会员"
),
]
),
]
);
return h(
"div",
{
style: {
display: "flex",
justifyContent: "center",
},
},
[viewLink, divider, moreDropdown]
);
},
},
@@ -321,6 +593,59 @@ export default {
},
},
methods: {
onSelectionChange(selection) {
this.selectedRows = selection || [];
},
openSetMemberGroup() {
this.memberGroupFlag = true;
this.memberGroupLoading = false;
this.memberGroupForm = { groupId: "" };
this.$nextTick(() => {
if (this.$refs.memberGroupForm) this.$refs.memberGroupForm.resetFields();
});
this.loadMemberGroupList();
},
loadMemberGroupList() {
API_Member.getMemberGroupByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
this.memberGroupList = res.result.records || [];
}
});
},
loadMemberGradeList() {
API_Member.getMemberGradeByPage({ pageNumber: 1, pageSize: 1000 }).then((res) => {
if (res && res.success && res.result) {
this.memberGradeList = res.result || [];
}
});
},
clearSelection() {
if (this.$refs.table && this.$refs.table.clearSelection) {
this.$refs.table.clearSelection();
}
},
submitSetMemberGroup() {
if (this.selectedRows.length === 0) {
this.$Message.warning("请先选择会员");
return;
}
this.$refs.memberGroupForm.validate((valid) => {
if (!valid) return;
const memberIds = this.selectedRows.map((item) => item.id);
this.memberGroupLoading = true;
API_Member.addMemberGroupUsers(this.memberGroupForm.groupId, memberIds).then((res) => {
this.memberGroupLoading = false;
if (res && res.success) {
this.$Message.success("设置成功");
this.memberGroupFlag = false;
this.selectedRows = [];
this.clearSelection();
} else if (res && res.message) {
this.$Message.error(res.message);
}
});
});
},
// 回调给父级
callback(val, index) {
this.selectMember.forEach(item=>{item.___selected = false})
@@ -373,7 +698,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getData();
},
//查看详情修改
@@ -400,6 +725,11 @@ export default {
//查询会员列表
getData() {
this.loading = true;
if (!this.selectedMember) {
this.selectedRows = [];
this.clearSelection();
}
API_Member.getMemberListData(this.searchForm).then((res) => {
if (res.result.records) {
this.loading = false;
@@ -472,6 +802,69 @@ export default {
});
},
openWalletIncrease(row) {
this.walletIncreaseLoading = false;
this.$set(this, "walletIncreaseForm", { memberId: row.id, rechargeMoney: null });
this.walletIncreaseFlag = true;
this.$nextTick(() => {
this.$refs.walletIncreaseForm && this.$refs.walletIncreaseForm.resetFields();
});
},
submitWalletIncrease() {
this.$refs.walletIncreaseForm.validate((valid) => {
if (!valid) return;
this.walletIncreaseLoading = true;
API_Member.increaseMemberWallet({
memberId: this.walletIncreaseForm.memberId,
rechargeMoney: this.walletIncreaseForm.rechargeMoney,
})
.then((res) => {
if (res && res.success) {
this.$Message.success("充值成功");
this.walletIncreaseFlag = false;
this.getData();
} else {
this.$Message.error((res && res.message) || "充值失败");
}
})
.finally(() => {
this.walletIncreaseLoading = false;
});
});
},
openMemberPoint(row) {
this.memberPointLoading = false;
this.$set(this, "memberPointForm", { memberId: row.id, point: null, type: "INCREASE" });
this.memberPointFlag = true;
this.$nextTick(() => {
this.$refs.memberPointForm && this.$refs.memberPointForm.resetFields();
});
},
submitMemberPoint() {
this.$refs.memberPointForm.validate((valid) => {
if (!valid) return;
this.memberPointLoading = true;
API_Member.updateMemberPoint({
memberId: this.memberPointForm.memberId,
point: this.memberPointForm.point,
type: this.memberPointForm.type,
})
.then((res) => {
if (res && res.success) {
this.$Message.success("修改成功");
this.memberPointFlag = false;
this.getData();
} else {
this.$Message.error((res && res.message) || "修改失败");
}
})
.finally(() => {
this.memberPointLoading = false;
});
});
},
// 提交修改数据
handleSubmitModal() {
const { nickName, sex, username, face, newPassword,id,regionId,region } = this.form;
@@ -482,7 +875,7 @@ export default {
regionId,
region,
nickName,
username,
sex,
birthday,
face,
@@ -502,14 +895,18 @@ export default {
},
mounted() {
this.getData();
if (!this.selectedMember) {
this.loadMemberGroupList();
this.loadMemberGradeList();
}
},
};
</script>
<style lang="scss" scoped>
/deep/ .ivu-table-wrapper {
::v-deep .ivu-table-wrapper {
width: 100%;
}
/deep/ .ivu-card {
::v-deep .ivu-card {
width: 100%;
}
.face {
@@ -517,4 +914,5 @@ export default {
height: 60px;
border-radius: 50%;
}
</style>

View File

@@ -95,7 +95,7 @@
:page-size="pointSearchForm.pageSize"
@on-change="pointChangePage"
@on-page-size-change="pointChangePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -182,7 +182,7 @@
:page-size="orderSearchForm.pageSize"
@on-change="orderChangePage"
@on-page-size-change="orderChangePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -210,7 +210,7 @@
:page-size="addressSearchForm.pageSize"
@on-change="addressChangePage"
@on-page-size-change="addressChangePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -259,7 +259,7 @@
:page-size="walletSearchForm.pageSize"
@on-change="walletChangePage"
@on-page-size-change="walletChangePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -304,7 +304,7 @@
:page-size="receiptRecordSearchForm.pageSize"
@on-change="walletChangePage"
@on-page-size-change="walletChangePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -438,7 +438,7 @@
//历史积分数据查询form
pointSearchForm: {
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
},
orderColumns: [
{
@@ -584,13 +584,12 @@
}
}, [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
marginRight: "5px",
},
on: {
@@ -611,7 +610,7 @@
//TA的订单form
orderSearchForm: {
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
payStatus: "",
orderSn: "",
orderType: "",
@@ -672,7 +671,7 @@
//TA的收货地址form
addressSearchForm: {
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
},
//消费记录
walletColumns: [
@@ -726,7 +725,7 @@
//TA的余额消费记录
walletSearchForm: {
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
},
@@ -735,7 +734,7 @@
//TA的发票记录
receiptRecordSearchForm: {
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
},
@@ -854,6 +853,8 @@
//查询TA的发票记录
getReceiptRecordData(){
this.loading = true;
this.receiptRecordSearchForm.pageNumber = 1;
this.receiptRecordSearchForm.pageSize = 20;
this.receiptRecordSearchForm.memberId = this.id
API_Order.getReceiptPage(this.receiptRecordSearchForm).then((res) => {
this.loading = false;
@@ -868,6 +869,8 @@
//查询TA的订单
getOrderData() {
this.loading = true;
this.orderSearchForm.pageNumber = 1;
this.orderSearchForm.pageSize = 20;
this.orderSearchForm.memberId = this.id
API_Order.getOrderList(this.orderSearchForm).then((res) => {
this.loading = false;

View File

@@ -16,7 +16,7 @@
v-model="searchForm.username"
placeholder="请输入会员名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
@@ -26,7 +26,7 @@
v-model="searchForm.mobile"
placeholder="请输入会员联系方式"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Button
@@ -38,6 +38,8 @@
>
</Form>
</Row>
</Card>
<Card>
<Table
:loading="loading"
border
@@ -55,7 +57,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -169,7 +171,7 @@ export default {
searchForm: {
// 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
order: "desc",
username: "",
mobile: "",
@@ -228,92 +230,56 @@ export default {
width: 200,
fixed: "right",
render: (h, params) => {
const linkStyle = {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
};
const sep = h(
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
);
const children = [];
if (this.selectedMember) {
children.push(
h(
"a",
{ style: linkStyle, on: { click: () => this.callback(params.row) } },
"选择"
)
);
children.push(sep);
}
children.push(
h(
"a",
{ style: linkStyle, on: { click: () => this.detail(params.row) } },
"查看"
)
);
if (!this.selectedMember) {
children.push(sep);
children.push(
h(
"a",
{ style: linkStyle, on: { click: () => this.enable(params.row) } },
"启用"
)
);
children.push(sep);
children.push(
h(
"a",
{ style: linkStyle, on: { click: () => this.editPerm(params.row) } },
"编辑"
)
);
}
return h(
"div",
{
style: {
display: "flex",
justifyContent: "center",
},
},
[
h(
"Button",
{
props: {
size: "small",
},
style: {
marginRight: "5px",
display: this.selectedMember ? "block" : "none",
},
on: {
click: () => {
this.callback(params.row);
},
},
},
"选择"
),
h(
"Button",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"查看"
),
h(
"Button",
{
props: {
size: "small",
type: "success",
},
style: {
marginRight: "5px",
display: this.selectedMember ? "none" : "block",
},
on: {
click: () => {
this.enable(params.row);
},
},
},
"启用"
),
h(
"Button",
{
props: {
type: "info",
size: "small",
ghost: true,
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.editPerm(params.row);
},
},
},
"编辑"
),
]
{ class: "ops", style: { display: "flex", justifyContent: "center" } },
children
);
},
},
@@ -345,7 +311,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getData();
},
//查看详情修改
@@ -435,7 +401,7 @@ export default {
regionId:regionId,
region: region,
nickName,
username,
sex,
birthday,
face: face || "",
@@ -463,4 +429,14 @@ export default {
height: 60px;
border-radius: 50%;
}
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -55,7 +55,7 @@
:page-size="weChatSearchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10,20,50]"
:page-size-opts="[20, 50, 100]"
size="small"
></Page>
</Row>
@@ -82,7 +82,7 @@
:page-size="weChatMPSearchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10,20,50]"
:page-size-opts="[20, 50, 100]"
size="small"
></Page>
</Row>
@@ -126,12 +126,12 @@ export default {
weChatSearchForm: {
// 搜索框对应data对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
},
weChatMPSearchForm: {
// 搜索框对应data对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
},
weChatColumns: [
{
@@ -178,14 +178,12 @@ export default {
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "primary",
size: "small"
},
style: {
marginRight: "5px"
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -196,14 +194,17 @@ export default {
"编辑"
),
h(
"Button",
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
props: {
type: "error",
size: "small"
},
style: {
marginRight: "5px"
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {

View File

@@ -1,12 +1,111 @@
<template>
<div class="search">
<Card style="padding:0 10px 10px 0">
<Card class="points-statistics-card">
<Row type="flex" justify="space-around" align="middle" class="points-statistics">
<Col :xs="24" :sm="12" class="points-statistics-item">
<div class="points-statistics-title">已发放积分数</div>
<div class="points-statistics-subtitle">历史累计发放积分数</div>
<div class="points-statistics-value">{{ formatNumber(pointsStatistics.totalPoint) }}</div>
</Col>
<Col :xs="24" :sm="12" class="points-statistics-item">
<div class="points-statistics-title">未使用积分数</div>
<div class="points-statistics-subtitle">会员账户未使用积分数</div>
<div class="points-statistics-value">{{ formatNumber(pointsStatistics.unUsedPoint) }}</div>
</Col>
</Row>
</Card>
<div class="point-tabs-wrap">
<Tabs v-model="activeTab" class="point-tabs">
<TabPane label="积分列表" name="pointList">
<Card class="point-content-card">
<Form
@keydown.enter.native="handleMemberSearch"
ref="memberSearchForm"
:model="memberSearchForm"
inline
:label-width="70"
@submit.native.prevent
class="search-form"
>
<Form-item label="客户名称" prop="nickName">
<Input
v-model="memberSearchForm.nickName"
placeholder="请输入客户名称"
clearable
style="width: 180px"
/>
</Form-item>
<Form-item label="客户账号" prop="username">
<Input
v-model="memberSearchForm.username"
placeholder="请输入客户账号"
clearable
style="width: 180px"
/>
</Form-item>
<Form-item label="账号状态" prop="disabled">
<Select
v-model="memberSearchForm.disabled"
clearable
placeholder="请选择账号状态"
style="width: 160px"
>
<Option value="OPEN">启用</Option>
<Option value="CLOSE">禁用</Option>
</Select>
</Form-item>
<Form-item label="积分值">
<InputNumber
v-model="memberSearchForm.minPoint"
:min="0"
:precision="0"
placeholder="最小积分值"
style="width: 140px"
/>
<span class="point-range-separator">-</span>
<InputNumber
v-model="memberSearchForm.maxPoint"
:min="0"
:precision="0"
placeholder="最大积分值"
style="width: 140px"
/>
</Form-item>
<Button @click="handleMemberSearch" class="search-btn" type="primary" icon="ios-search">搜索</Button>
</Form>
</Card>
<Card class="point-content-card member-list-card">
<div slot="title" class="card-title">用户列表</div>
<Table
:loading="memberLoading"
:columns="memberColumns"
:data="memberData"
class="member-table"
>
</Table>
<Row type="flex" justify="end" class="mt_10">
<Page
:current="memberSearchForm.pageNumber"
:total="memberTotal"
:page-size="memberSearchForm.pageSize"
@on-change="changeMemberPage"
@on-page-size-change="changeMemberPageSize"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
</Card>
</TabPane>
<TabPane label="积分增减记录" name="pointChangeRecord">
<Card class="point-content-card">
<Form
@keydown.enter.native="handleSearch"
ref="searchForm"
:model="searchForm"
inline
style="margin-top:10px"
:label-width="70"
@submit.native.prevent
class="search-form"
@@ -17,18 +116,20 @@
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Button @click="handleSearch" class="search-btn" type="primary" icon="ios-search">搜索</Button >
<Button @click="handleSearch" class="search-btn" type="primary" icon="ios-search">搜索</Button>
</Form>
</Card>
<Card class="point-content-card">
<Table
:loading="loading"
border
:columns="columns"
:data="data"
ref="table"
class="mt_10"
class="mt_10 point-table"
>
</Table>
<Row type="flex" justify="end" class="mt_10">
@@ -38,7 +139,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -46,27 +147,40 @@
></Page>
</Row>
</Card>
</TabPane>
</Tabs>
</div>
</div>
</template>
<script>
import * as API_Member from "@/api/member.js";
import ossManage from "@/views/sys/oss-manage/ossManage";
export default {
// 积分历史页面
name: "point",
components: {
ossManage,
},
data() {
return {
activeTab: "pointList",
loading: true, // 表单加载状态
memberLoading: false,
pointsStatistics: {
totalPoint: 0,
unUsedPoint: 0,
},
searchForm: { // 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
},
memberSearchForm: {
pageNumber: 1,
pageSize: 20,
nickName: "",
username: "",
disabled: "",
minPoint: null,
maxPoint: null,
},
columns: [
{
@@ -78,20 +192,20 @@
{
title: "操作内容",
key: "content",
minWidth: 180,
minWidth: 200,
tooltip: true
},
{
title: "之前积分",
key: "beforePoint",
width: 110,
width: 200,
},
{
title: "变动积分",
key: "variablePoint",
width: 110,
width: 200,
render: (h, params) => {
if (params.row.pointType == 'INCREASE') {
return h("priceColorScheme", {props:{value:params.row.variablePoint,color:'green',unit:"+"}} );
@@ -103,16 +217,88 @@
{
title: "当前积分",
key: "point",
width: 110,
width: 200,
},
{
title: "操作时间",
key: "createTime",
width: 170
width: 200
},
],
memberColumns: [
{
title: "客户名称",
key: "nickName",
width: 180,
tooltip: true,
render: (h, params) => {
return h("span", params.row.nickName || "-");
},
},
{
title: "客户账号",
key: "username",
width: 180,
tooltip: true,
render: (h, params) => {
return h("span", params.row.username || "-");
},
},
{
title: "账号状态",
key: "disabled",
width: 180,
render: (h, params) => {
const enabled = params.row.disabled === true;
return h(
"Tag",
{
props: {
color: enabled ? "success" : "default",
},
},
enabled ? "启用" : "禁用"
);
},
},
{
title: "积分余额",
key: "point",
width: 180,
render: (h, params) => {
const point = params.row.point == null ? 0 : params.row.point;
return h("span", point);
},
},
{
title: "操作",
key: "action",
width: 100,
align: "center",
render: (h, params) => {
return h(
"a",
{
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
},
},
},
"详情"
);
},
},
],
data: [], // 表单数据
memberData: [],
memberTotal: 0,
total: 0, // 表单数据总数
};
},
@@ -121,10 +307,31 @@
callback(val) {
this.$emit("callback", val);
},
// 查看会员详情
detail(row) {
this.$options.filters.customRouterPush({ name: "member-detail", query: { id: row.id } });
},
// 初始化数据
init() {
this.getStatistics();
this.getMemberList();
this.getData();
},
getStatistics() {
API_Member.queryMemberPointsStatistics().then((res) => {
if (res && res.success && res.result) {
this.pointsStatistics = {
totalPoint: res.result.totalPoint || 0,
unUsedPoint: res.result.unUsedPoint || 0,
};
}
});
},
formatNumber(value) {
const numericValue = Number(value || 0);
if (!Number.isFinite(numericValue)) return "0";
return numericValue.toLocaleString();
},
// 分页 改变页码
changePage(v) {
this.searchForm.pageNumber = v;
@@ -139,9 +346,43 @@
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getData();
},
handleMemberSearch() {
this.memberSearchForm.pageNumber = 1;
this.getMemberList();
},
changeMemberPage(v) {
this.memberSearchForm.pageNumber = v;
this.getMemberList();
},
changeMemberPageSize(v) {
this.memberSearchForm.pageNumber = 1;
this.memberSearchForm.pageSize = v;
this.getMemberList();
},
getMemberList() {
this.memberLoading = true;
const params = {
pageNumber: this.memberSearchForm.pageNumber,
pageSize: this.memberSearchForm.pageSize,
nickName: this.memberSearchForm.nickName || undefined,
username: this.memberSearchForm.username || undefined,
disabled: this.memberSearchForm.disabled || undefined,
minPoint: this.memberSearchForm.minPoint,
maxPoint: this.memberSearchForm.maxPoint,
};
API_Member.getMemberListData(params).then((res) => {
this.memberLoading = false;
if (res && res.success && res.result && res.result.records) {
this.memberData = res.result.records;
this.memberTotal = res.result.total;
}
}).catch(() => {
this.memberLoading = false;
});
},
//查新积分列表
getData() {
this.loading = true;
@@ -161,6 +402,114 @@
};
</script>
<style lang="scss" scoped>
.points-statistics-card {
margin-bottom: 10px;
}
.point-tabs-wrap {
padding: 12px 16px 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(23, 35, 61, 0.04);
}
.point-tabs {
::v-deep .ivu-tabs-bar {
margin-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
::v-deep .ivu-tabs-nav .ivu-tabs-tab {
padding: 8px 18px;
color: #515a6e;
background: #fff;
transition: all 0.2s ease;
}
::v-deep .ivu-tabs-nav .ivu-tabs-tab-active {
color: #2d8cf0;
font-weight: 500;
}
::v-deep .ivu-tabs-ink-bar {
height: 2px;
border-radius: 2px;
}
}
.points-statistics {
width: 100%;
}
.points-statistics-item {
padding: 10px 0;
text-align: center;
}
.points-statistics-title {
font-size: 14px;
color: #17233d;
line-height: 20px;
}
.points-statistics-subtitle {
font-size: 12px;
color: #808695;
line-height: 18px;
margin-top: 4px;
}
.points-statistics-value {
font-size: 18px;
font-weight: 600;
color: #fa6419;
line-height: 26px;
margin-top: 8px;
}
.point-table {
::v-deep .ivu-table-border th,
::v-deep .ivu-table-border td {
border-right: 0;
}
}
.point-content-card {
margin-bottom: 12px;
::v-deep .ivu-card-body {
padding: 18px 20px;
}
}
.member-list-card {
::v-deep .ivu-card-head {
border-bottom: 1px solid #f5f5f5;
}
}
.card-title {
font-size: 14px;
font-weight: 500;
color: #17233d;
}
.member-table {
::v-deep .ivu-table-header th {
background: #fafafa;
}
::v-deep .ivu-tag {
margin-right: 0;
}
}
.point-range-separator {
display: inline-block;
margin: 0 8px;
color: #808695;
}
.face {
width: 60px;
height: 60px;

View File

@@ -20,7 +20,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -46,7 +46,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -72,7 +72,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -99,7 +99,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -155,7 +155,7 @@
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
serviceType: "RETURN_MONEY"
@@ -184,13 +184,12 @@
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
marginRight: "5px"
},
on: {
@@ -202,11 +201,17 @@
"编辑"
),
h(
"Button",
"span",
{ style: { margin: "0 8px", color: "#dcdee2" } },
"|"
),
h(
"a",
{
props: {
type: "error",
size: "small",
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -241,7 +246,7 @@
//切换tab
handleClickType(v) {
this.searchForm.pageNumber = 1 // 当前页数
this.searchForm.pageSize = 10 // 页面大小
this.searchForm.pageSize = 20 // 页面大小
//退款
if (v == "RETURN_MONEY") {
this.searchForm.serviceType = "RETURN_MONEY"

View File

@@ -9,13 +9,23 @@
:label-width="70"
class="search-form"
>
<Form-item label="关键字" prop="keywords" style="display: block; width: 100%;">
<Input
type="text"
v-model="searchForm.keywords"
placeholder="请输入商品名称、订单编号搜索"
clearable
style="width: 240px"
/>
</Form-item>
<br>
<Form-item label="订单编号" prop="orderSn">
<Input
type="text"
v-model="searchForm.orderSn"
placeholder="请输入订单编号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="售后单号" prop="sn">
@@ -24,7 +34,7 @@
v-model="searchForm.sn"
placeholder="请输入售后单号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="申请时间">
@@ -35,7 +45,7 @@
clearable
@on-change="selectDateRange"
placeholder="选择起始时间"
style="width: 200px"
style="width: 240px"
></DatePicker>
</Form-item>
<Form-item label="商家名称" prop="storeName">
@@ -44,7 +54,7 @@
v-model="searchForm.storeName"
placeholder="请输入商家名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="会员名称" prop="memberName">
@@ -53,7 +63,7 @@
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="售后类型">
@@ -61,7 +71,7 @@
v-model="searchForm.serviceType"
placeholder="全部"
clearable
style="width: 200px"
style="width: 240px"
>
<Option value="RETURN_MONEY">退款</Option>
<Option value="RETURN_GOODS">退货</Option>
@@ -76,16 +86,17 @@
>
</Form>
</Row>
</Card>
<Card>
<div class="order-tab">
<div v-for="(item,index) in serviceStatus" :key="index" :class="{'current': currentStatus === item.value}" @click="serviceStatusClick(item)">
{{item.title}}
</div>
<Tabs v-model="currentStatus" @on-click="serviceStatusClick">
<TabPane v-for="item in serviceStatusWithCount" :key="item.value" :label="item.title" :name="item.value">
</TabPane>
</Tabs>
</div>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
ref="table"
@@ -95,31 +106,16 @@
<template slot="goodsSlot" slot-scope="{ row }">
<div style="margin-top: 5px; height: 80px; display: flex">
<div style="">
<img :src="row.goodsImage" style="height: 60px; margin-top: 3px" />
<img :src="row.goodsImage" style="width: 60px; height: 60px; margin-top: 3px; object-fit: cover; border-radius: 4px;" />
</div>
<div style="margin-left: 13px">
<div class="div-zoom">
<a @click="linkTo(row.goodsId, row.skuId)">{{ row.goodsName }}</a>
</div>
<Poptip trigger="hover" title="扫码在手机中查看" transfer>
<div slot="content">
<vue-qr
:text="wapLinkTo(row.goodsId, row.skuId)"
:margin="0"
colorDark="#000"
colorLight="#fff"
:size="150"
></vue-qr>
<div style="color: #999; font-size: 12px; margin-top: 5px;">
商品ID: {{ row.goodsId }}
</div>
<img
src="../../../assets/qrcode.svg"
class="hover-pointer"
width="20"
height="20"
alt=""
/>
</Poptip>
</div>
</div>
</template>
@@ -131,7 +127,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -156,7 +152,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
@@ -166,6 +162,7 @@ export default {
serviceStatus: "",
storeName: "",
sn: "",
keywords: "", // 新增关键字搜索字段
},
selectDate: null, // 选择时间段
form: {
@@ -189,6 +186,13 @@ export default {
minWidth: 120,
tooltip: true,
},
// 移除这个独立的商品ID列
// {
// title: "商品ID",
// key: "goodsId",
// minWidth: 120,
// tooltip: true,
// },
{
title: "商品",
key: "goodsName",
@@ -196,15 +200,21 @@ export default {
tooltip: true,
slot: "goodsSlot",
},
{
title: "会员ID",
key: "memberId",
minWidth: 120,
tooltip: true,
},
{
title: "会员名称",
key: "memberName",
width: 140,
},
{
title: "商家名称",
title: "店铺名称",
key: "storeName",
minWidth: 100,
width: 100,
tooltip: true,
},
{
@@ -233,7 +243,7 @@ export default {
{
title: "售后状态",
key: "serviceStatus",
width: 150,
width: 180,
render: (h, params) => {
if (params.row.serviceStatus == "APPLY") {
return h("div", [h("tag", { props: { color: "blue" } }, "申请中")]);
@@ -272,14 +282,12 @@ export default {
render: (h, params) => {
return h("div", [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none"
},
on: {
click: () => {
@@ -306,13 +314,15 @@ export default {
{title: '卖家终止售后', value: 'SELLER_TERMINATION'},
{title: '买家取消售后', value: 'BUYER_CANCEL'}
],
currentStatus: ''
currentStatus: '',
afterSaleNumData: {} // 售后数量统计数据
};
},
methods: {
// 初始化数据
init() {
this.getDataList();
this.getAfterSaleNumData();
},
// 分页 改变页码
changePage(v) {
@@ -328,8 +338,9 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
this.getAfterSaleNumData();
},
// 开始结束时间分别赋值
selectDateRange(v) {
@@ -348,9 +359,19 @@ export default {
this.total = res.result.total;
}
});
// 获取售后状态数量
this.total = this.data.length;
this.loading = false;
},
// 获取售后数量统计
getAfterSaleNumData() {
const { serviceStatus, ...searchParams } = this.searchForm;
API_Order.getAfterSaleNumVO(searchParams).then((res) => {
if (res.success) {
this.afterSaleNumData = res.result;
}
});
},
// 跳转售后详情
detail(v) {
let sn = v.sn;
@@ -361,37 +382,44 @@ export default {
},
// 售后筛选
serviceStatusClick(item) {
this.currentStatus = item.value;
this.searchForm.serviceStatus = item.value;
this.currentStatus = item;
// 如果是全部空字符串则删除serviceStatus字段
if (item === 0) {
delete this.searchForm.serviceStatus;
} else {
this.searchForm.serviceStatus = item;
}
this.getDataList();
this.getAfterSaleNumData();
},
},
mounted() {
this.init();
},
};
</script>
<style lang="scss" scoped>
.order-tab {
width: 950px;
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #f0f0f0;
padding: 0 10px;
margin: 10px 20px 10px 0;
div {
text-align: center;
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
computed: {
// 带数量的售后状态
serviceStatusWithCount() {
return [
{title: '全部', value: ''},
{title: `申请售后${this.afterSaleNumData.applyNum ? '(' + this.afterSaleNumData.applyNum + ')' : ''}`, value: 'APPLY'},
{title: `通过售后${this.afterSaleNumData.passNum ? '(' + this.afterSaleNumData.passNum + ')' : ''}`, value: 'PASS'},
{title: `拒绝售后${this.afterSaleNumData.refuseNum ? '(' + this.afterSaleNumData.refuseNum + ')' : ''}`, value: 'REFUSE'},
{title: `待收货${this.afterSaleNumData.buyerReturnNum ? '(' + this.afterSaleNumData.buyerReturnNum + ')' : ''}`, value: 'BUYER_RETURN'},
{title: `确认收货${this.afterSaleNumData.sellerConfirmNum ? '(' + this.afterSaleNumData.sellerConfirmNum + ')' : ''}`, value: 'SELLER_CONFIRM'},
{title: `完成售后${this.afterSaleNumData.completeNum ? '(' + this.afterSaleNumData.completeNum + ')' : ''}`, value: 'COMPLETE'},
{title: `卖家终止售后${this.afterSaleNumData.sellerTerminationNum ? '(' + this.afterSaleNumData.sellerTerminationNum + ')' : ''}`, value: 'SELLER_TERMINATION'},
{title: `买家取消售后${this.afterSaleNumData.buyerCancelNum ? '(' + this.afterSaleNumData.buyerCancelNum + ')' : ''}`, value: 'BUYER_CANCEL'},
{title: `等待平台退款${this.afterSaleNumData.waitRefundNum ? '(' + this.afterSaleNumData.waitRefundNum + ')' : ''}`, value: 'WAIT_REFUND'}
];
}
.current {
background-color: #ffffff;
}
}
</script>
<style lang="scss" scoped>
// Tab组件样式
.order-tab {
::v-deep .ivu-tabs-tab {
font-size: 14px;
}
}
</style>

View File

@@ -19,7 +19,7 @@
</dd>
</dl>
<dl>
<dt>退货状态</dt>
<dt>售后状态</dt>
<dd>{{ afterSaleInfo.serviceName }}</dd>
</dl>
<dl>
@@ -376,11 +376,11 @@ export default {
status: "APPLY",
},
{
name: "通过",
name: "通过售后",
status: "PASS",
},
{
name: "拒绝",
name: "拒绝售后",
status: "REFUSE",
},
{
@@ -404,7 +404,7 @@ export default {
status: "COMPLETE",
},
{
name: "待平台退款",
name: "待平台退款",
status: "WAIT_REFUND",
},
],
@@ -511,7 +511,7 @@ export default {
const ob = this.afterSaleStatusList.filter((e) => {
return e.status === status;
});
return ob[0].name;
return ob.length > 0 ? ob[0].name : status;
},
// 根据订单状态判断是否显示物流信息
showDelivery(status) {

View File

@@ -9,7 +9,7 @@
v-model="searchForm.orderSn"
placeholder="请输入订单编号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="会员名称" prop="memberName">
@@ -18,11 +18,11 @@
v-model="searchForm.memberName"
placeholder="请输入会员名称"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="状态" prop="status">
<Select v-model="searchForm.status" placeholder="请选择" clearable style="width: 200px">
<Select v-model="searchForm.status" placeholder="请选择" clearable style="width: 240px">
<Option value="NEW">新投诉</Option>
<Option value="CANCEL">已撤销</Option>
<Option value="WAIT_APPEAL">待申诉</Option>
@@ -34,6 +34,8 @@
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
</Form>
</Row>
</Card>
<Card>
<Table
:loading="loading"
border
@@ -59,7 +61,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -84,7 +86,7 @@
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
},
@@ -152,14 +154,12 @@
if(params.row.complainStatus === "COMPLETE"){
return h("div", [
h(
"Button",
"a",
{
props: {
type: "info",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -173,14 +173,12 @@
}else{
return h("div", [
h(
"Button",
"a",
{
props: {
type: "primary",
size: "small",
},
style: {
marginRight: "5px",
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
@@ -225,7 +223,7 @@
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 获取列表数据

View File

@@ -2,35 +2,34 @@
<div class="search">
<Card>
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="100" class="search-form">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="订单号" prop="sn">
<Input type="text" v-model="searchForm.sn" placeholder="订单/交易号" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.sn" placeholder="订单/交易号" clearable style="width: 240px" />
</Form-item>
<Form-item label="付款状态" prop="orderStatus">
<Select v-model="searchForm.payStatus" placeholder="请选择" clearable style="width: 200px">
<Select v-model="searchForm.payStatus" placeholder="请选择" clearable style="width: 240px">
<Option value="UNPAID">未付款</Option>
<Option value="PAID">已付款</Option>
</Select>
</Form-item>
<Form-item label="支付方式" prop="orderStatus">
<Select v-model="searchForm.paymentMethod" placeholder="请选择" clearable style="width: 200px">
<Select v-model="searchForm.paymentMethod" placeholder="请选择" clearable style="width: 240px">
<Option value="">全部</Option>
<Option value="WECHAT">微信</Option>
<Option value="ALIPAY">支付宝</Option>
<Option value="WALLET">余额</Option>
<Option value="BANK_TRANSFER">银行转账</Option>
</Select>
</Form-item>
<Form-item label="订单创建时间">
<DatePicker v-model="times" type="datetimerange" format="yyyy-MM-dd HH:mm" clearable @on-change="changeDate" placeholder="选择支付时间" style="width: 200px"></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
</Form>
</Row>
</Card>
<Card>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" class="mt_10"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small"
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]" size="small"
show-total show-elevator show-sizer></Page>
</Row>
</Card>
@@ -48,7 +47,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
sn: "",
@@ -185,7 +184,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
changeDate(val) {

View File

@@ -15,7 +15,7 @@
v-model="searchForm.orderSn"
placeholder="订单/交易号"
clearable
style="width: 200px"
style="width: 240px"
/>
</Form-item>
<Form-item label="退款状态">
@@ -23,7 +23,7 @@
v-model="searchForm.isRefund"
placeholder="请选择"
clearable
style="width: 200px"
style="width: 240px"
>
<Option value="false">未退款</Option>
<Option value="true">已退款</Option>
@@ -37,7 +37,7 @@
clearable
@on-change="selectDateRange"
placeholder="选择起始时间"
style="width: 200px"
style="width: 240px"
></DatePicker>
</Form-item>
<Button
@@ -49,6 +49,8 @@
>
</Form>
</Row>
</Card>
<Card>
<Table
:loading="loading"
border
@@ -91,7 +93,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -113,7 +115,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
@@ -194,7 +196,7 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 起止时间从新赋值

View File

@@ -4,41 +4,33 @@
<Row @keydown.enter.native="handleSearch">
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
<Form-item label="订单号" prop="orderSn">
<Input type="text" v-model="searchForm.orderSn" placeholder="请输入订单号" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.orderSn" placeholder="请输入订单号" clearable style="width: 240px" />
</Form-item>
<Form-item label="会员名称" prop="buyerName">
<Input type="text" v-model="searchForm.buyerName" placeholder="请输入会员名称" clearable style="width: 200px" />
<Input type="text" v-model="searchForm.buyerName" placeholder="请输入会员名称" clearable style="width: 240px" />
</Form-item>
<!-- <Form-item label="订单状态" prop="orderStatus">-->
<!-- <Select v-model="searchForm.orderStatus" placeholder="请选择" clearable style="width: 200px">-->
<!-- <Option value="NEW">新订单</Option>-->
<!-- <Option value="CONFIRM">已确认</Option>-->
<!-- <Option value="TAKE">待核验</Option>-->
<!-- <Option value="COMPLETE">已完成</Option>-->
<!-- <Option value="WAIT_PINTUAN">待成团</Option>-->
<!-- <Option value="CANCELLED">已关闭</Option>-->
<!-- </Select>-->
<!-- </Form-item>-->
<Form-item label="下单时间">
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd" clearable
@on-change="selectDateRange" placeholder="选择起始时间" style="width: 200px"></DatePicker>
@on-change="selectDateRange" placeholder="选择起始时间" style="width: 240px"></DatePicker>
</Form-item>
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
</Form>
</Row>
</Card>
<Card>
<div class="order-tab">
<div v-for="(item,index) in orderStatus" :key="index" :class="{'current': currentStatus === item.value}" @click="orderStatusClick(item)">
{{item.title}}
</div>
<Tabs v-model="currentStatus" @on-click="orderStatusClick">
<TabPane v-for="(item,index) in orderStatus" :key="index" :label="item.title" :name="item.value">
</TabPane>
</Tabs>
</div>
<Table :loading="loading" border :columns="columns" :data="data" ref="table" class="mt_10" sortable="custom"
@on-sort-change="changeSort"></Table>
<Row type="flex" justify="end" class="mt_10">
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePage"
@on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]" size="small" show-total show-elevator
@on-page-size-change="changePageSize" :page-size-opts="[20, 50, 100]" size="small" show-total show-elevator
show-sizer></Page>
</Row>
</Card>
@@ -56,7 +48,7 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "", // 默认排序字段
order: "", // 默认排序方式
startDate: "", // 起始时间
@@ -215,7 +207,7 @@ export default {
// 搜索
handleSearch () {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
},
// 列表排序
@@ -275,8 +267,8 @@ export default {
},
// 订单筛选
orderStatusClick(item) {
this.currentStatus = item.value;
this.searchForm.orderStatus = item.value;
this.currentStatus = item; // 使用参数 item
this.searchForm.orderStatus = item; // 使用参数 item
this.getDataList();
},
},
@@ -288,23 +280,11 @@ export default {
<style lang="scss" scoped>
// Tab组件样式
.order-tab {
margin: 10px 20px 10px 0;
width: 950px;
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #f0f0f0;
padding: 0 10px;
div {
text-align: center;
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
}
.current {
background-color: #ffffff;
margin-top: 20px;
::v-deep .ivu-tabs-tab {
font-size: 14px;
}
}
</style>

View File

@@ -9,13 +9,22 @@
:label-width="70"
class="search-form"
>
<Form-item label="关键字" prop="keywords" style="display: block; width: 100%;">
<Input
type="text"
v-model="searchForm.keywords"
placeholder="请输入商品名称/收货人/收货人手机号/店铺名称"
clearable
style="width: 500px"
/>
</Form-item>
<Form-item label="订单号" prop="orderSn">
<Input
type="text"
v-model="searchForm.orderSn"
placeholder="请输入订单号"
clearable
style="width: 160px"
style="width: 240px"
/>
</Form-item>
<Form-item label="会员名称" prop="buyerName">
@@ -24,16 +33,33 @@
v-model="searchForm.buyerName"
placeholder="请输入会员名称"
clearable
style="width: 160px"
style="width: 240px"
/>
</Form-item>
<Form-item label="商品名称" prop="goodsName">
<Input
type="text"
v-model="searchForm.goodsName"
placeholder="请输入商品名称"
clearable
style="width: 240px"
/>
</Form-item>
<Form-item label="收货人" prop="shipName">
<Input
type="text"
v-model="searchForm.shipName"
placeholder="请输入收货人姓名"
clearable
style="width: 240px"
/>
</Form-item>
<Form-item label="订单类型" prop="orderType">
<Select
v-model="searchForm.orderPromotionType"
placeholder="请选择"
clearable
style="width: 160px"
style="width: 240px"
>
<Option value="NORMAL">普通订单</Option>
<Option value="PINTUAN">拼团订单</Option>
@@ -42,6 +68,19 @@
<Option value="KANJIA">砍价订单</Option>
</Select>
</Form-item>
<Form-item label="支付方式" prop="paymentMethod">
<Select
v-model="searchForm.paymentMethod"
placeholder="请选择支付方式"
clearable
style="width: 240px"
>
<Option value="WECHAT">微信支付</Option>
<Option value="ALIPAY">支付宝</Option>
<Option value="WALLET">余额支付</Option>
<Option value="BANK_TRANSFER">线下转账</Option>
</Select>
</Form-item>
<Form-item label="下单时间">
<DatePicker
v-model="selectDate"
@@ -50,26 +89,9 @@
clearable
@on-change="selectDateRange"
placeholder="选择起始时间"
style="width: 160px"
style="width: 240px"
></DatePicker>
</Form-item>
<!-- <Form-item label="订单状态" prop="orderStatus">-->
<!-- <Select-->
<!-- v-model="searchForm.orderStatus"-->
<!-- placeholder="请选择"-->
<!-- clearable-->
<!-- style="width: 160px"-->
<!-- >-->
<!-- <Option value="UNPAID">未付款</Option>-->
<!-- <Option value="PAID">已付款</Option>-->
<!-- <Option value="UNDELIVERED">待发货</Option>-->
<!-- <Option value="DELIVERED">已发货</Option>-->
<!-- <Option value="COMPLETED">已完成</Option>-->
<!-- <Option value="TAKE">待核验</Option>-->
<!-- <Option value="CANCELLED">已关闭</Option>-->
<!-- <Option value="STAY_PICKED_UP">待自提</Option>-->
<!-- </Select>-->
<!-- </Form-item>-->
<Button
@click="handleSearch"
type="primary"
@@ -78,19 +100,19 @@
>搜索</Button
>
</Form>
</Card>
<Card>
<div class="order-tab">
<Tabs v-model="currentStatus" @on-click="orderStatusClick">
<TabPane v-for="(item,index) in orderStatusWithCount" :key="index" :label="item.title" :name="item.value">
</TabPane>
</Tabs>
</div>
<div>
<Button @click="exportOrder" type="info" class="export">导出订单</Button>
</div>
<div class="order-tab">
<div v-for="(item,index) in orderStatus" :key="index" :class="{'current': currentStatus === item.value}" @click="orderStatusClick(item)">
{{item.title}}
</div>
</div>
<Table
:loading="loading"
border
:columns="columns"
:data="data"
ref="table"
@@ -104,7 +126,7 @@
:page-size="searchForm.pageSize"
@on-change="changePage"
@on-page-size-change="changePageSize"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
size="small"
show-total
show-elevator
@@ -163,15 +185,19 @@ export default {
searchForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc", // 默认排序方式
startDate: "", // 起始时间
endDate: "", // 终止时间
orderType: "",
orderSn: "",
keywords: "", // 新增关键字搜索字段
buyerName: "",
goodsName: "", // 新增商品名称筛选字段
shipName: "", // 新增收货人筛选字段
orderStatus: "",
paymentMethod: "", // 新增支付方式筛选字段
},
selectDate: null,
columns: [
@@ -181,7 +207,6 @@ export default {
minWidth: 240,
tooltip: true,
},
{
title: "订单来源",
key: "clientType",
@@ -235,6 +260,19 @@ export default {
tooltip: true,
},
{
title: "会员ID",
key: "memberId",
minWidth: 120,
tooltip: true,
},
{
title: "店铺名称",
key: "storeName",
minWidth: 150,
tooltip: true,
},
{
title: "订单金额",
key: "flowPrice",
@@ -245,7 +283,24 @@ export default {
},
},
{
title: "支付方式",
key: "paymentMethod",
width: 120,
render: (h, params) => {
if (params.row.paymentMethod == "WECHAT") {
return h("div", {}, "微信支付");
} else if (params.row.paymentMethod == "ALIPAY") {
return h("div", {}, "支付宝");
} else if (params.row.paymentMethod == "WALLET") {
return h("div", {}, "余额支付");
} else if (params.row.paymentMethod == "BANK_TRANSFER") {
return h("div", {}, "线下转账");
} else {
return h("div", {}, params.row.paymentMethod || "-");
}
},
},
{
title: "订单状态",
key: "orderStatus",
@@ -302,10 +357,17 @@ export default {
width: 100,
render: (h, params) => {
return h(
"Button",
"div",
{ class: "ops" },
[
h(
"a",
{
props: { type: "info", size: "small" },
style: { marginRight: "5px" },
style: {
color: "#2d8cf0",
cursor: "pointer",
textDecoration: "none",
},
on: {
click: () => {
this.detail(params.row);
@@ -313,12 +375,15 @@ export default {
},
},
"查看"
),
]
);
},
},
],
data: [], // 表单数据
total: 0, // 表单数据总数
orderNumData: {}, // 新增:订单数量统计数据
orderStatus: [
{title: '全部', value: ''},
{title: '未付款', value: 'UNPAID'},
@@ -330,15 +395,32 @@ export default {
{title: '待自提', value: 'STAY_PICKED_UP'},
{title: '已完成', value: 'COMPLETED'},
{title: '已关闭', value: 'CANCELLED'},
],
currentStatus: ''
};
},
computed: {
// 新增:带数量的订单状态选项
orderStatusWithCount() {
return [
{title: '全部', value: ''},
{title: `未付款${this.orderNumData.waitPayNum ? '(' + this.orderNumData.waitPayNum + ')' : ''}`, value: 'UNPAID'},
{title: `已付款${this.orderNumData.waitDeliveryNum ? '(' + this.orderNumData.waitDeliveryNum + ')' : ''}`, value: 'PAID'},
{title: `待发货${this.orderNumData.waitShipNum ? '(' + this.orderNumData.waitShipNum + ')' : ''}`, value: 'UNDELIVERED'},
{title: `部分发货${this.orderNumData.partsDeliveredNumNum ? '(' + this.orderNumData.partsDeliveredNumNum + ')' : ''}`, value: 'PARTS_DELIVERED'},
{title: `待收货${this.orderNumData.deliveredNum ? '(' + this.orderNumData.deliveredNum + ')' : ''}`, value: 'DELIVERED'},
{title: `待核验${this.orderNumData.waitCheckNum ? '(' + this.orderNumData.waitCheckNum + ')' : ''}`, value: 'TAKE'},
{title: `待自提${this.orderNumData.waitSelfPickNum ? '(' + this.orderNumData.waitSelfPickNum + ')' : ''}`, value: 'STAY_PICKED_UP'},
{title: `已完成${this.orderNumData.finishNum ? '(' + this.orderNumData.finishNum + ')' : ''}`, value: 'COMPLETED'},
{title: `已关闭${this.orderNumData.closeNum ? '(' + this.orderNumData.closeNum + ')' : ''}`, value: 'CANCELLED'},
];
}
},
methods: {
// 初始化数据
init() {
this.getDataList();
this.getOrderNumData(); // 新增:获取订单数量统计
},
// 分页 改变页码
changePage(v) {
@@ -354,8 +436,9 @@ export default {
// 搜索
handleSearch() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchForm.pageSize = 20;
this.getDataList();
this.getOrderNumData(); // 新增:搜索时也更新数量统计
},
// 起止时间从新赋值
selectDateRange(v) {
@@ -419,11 +502,29 @@ export default {
},
// 订单筛选
orderStatusClick(item) {
this.currentStatus = item.value;
this.searchForm.orderStatus = item.value;
orderStatusClick(name) {
if (name === 0) {
// 点击"全部"时设置为空字符串在getDataList中会被过滤掉
this.searchForm.orderStatus = '';
} else {
// 其他状态正常赋值
this.searchForm.orderStatus = name;
}
this.currentStatus = name;
this.getDataList();
},
getOrderNumData() {
// 创建一个不包含orderStatus字段的搜索参数
const { orderStatus, ...searchParams } = this.searchForm;
API_Order.getOrderNum(searchParams).then((res) => {
if (res.success) {
this.orderNumData = res.result;
}
}).catch((err) => {
console.error('获取订单数量统计失败:', err);
});
},
},
mounted() {
this.init();
@@ -437,23 +538,21 @@ export default {
.export-excel-wrapper {
display: inline;
}
// Tab组件样式
.order-tab {
width: 950px;
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #f0f0f0;
padding: 0 10px;
margin-bottom: 10px;
div {
text-align: center;
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
}
.current {
background-color: #ffffff;
::v-deep .ivu-tabs-tab {
font-size: 14px;
}
}
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
}
</style>

View File

@@ -65,7 +65,7 @@
:total="total"
show-sizer
size="small"
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
show-elevator
style="float: right; overflow: hidden"
@on-change="changePageNum"
@@ -109,7 +109,7 @@ export default {
},
searchForm: {
pageNumber: 1,
pageSize: 10,
pageSize: 20,
sort: "createTime",
order: "desc",
pageType: "INDEX",

View File

@@ -371,7 +371,7 @@ export default {
.search {
width: 460px;
margin: 0 auto;
/deep/ .ivu-input.ivu-input-large {
::v-deep .ivu-input.ivu-input-large {
border: 2px solid $theme_color;
font-size: 12px;
height: 34px;
@@ -379,7 +379,7 @@ export default {
box-shadow: none;
}
}
/deep/ .ivu-input-group-append {
::v-deep .ivu-input-group-append {
border: 1px solid $theme_color;
border-left: none;
height: 30px;

View File

@@ -104,6 +104,7 @@ export default {
pageData: JSON.stringify(modelForm),
// 如果是专题页面永远关闭
pageShow: this.$route.query.pageType === 'SPECIAL' ? 'CLOSE' : pageShow,
pageClientType: 'PC',
};
API_floor.updateHome(this.$route.query.id, data).then((res) => {
this.submitLoading = false

View File

@@ -1,4 +1,4 @@
/deep/ .ivu-modal-mask,
::v-deep .ivu-modal-mask,
.ivu-modal-wrap {
z-index: 800;
}

View File

@@ -84,7 +84,7 @@
text-align: center;
font-weight: bold;
line-height: 2;
/deep/ img {
::v-deep img {
margin: 0 auto;
}
}

View File

@@ -44,7 +44,7 @@
:current="params.pageNumber"
:page-size="params.pageSize"
show-sizer
:page-size-opts="[10, 20, 50]"
:page-size-opts="[20, 50, 100]"
@on-page-size-change="changePageSize"
/>
</Card>
@@ -95,7 +95,7 @@ export default {
params: {
// 请求参数
pageNumber: 1,
pageSize: 10,
pageSize: 20,
sort: "createTime",
order: "desc",
pageType: "INDEX",

View File

@@ -19,29 +19,21 @@
primary-key="id"
>
<template slot="action" slot-scope="scope">
<Button
type="info"
<a
style="color:#2d8cf0;cursor:pointer;text-decoration:none;margin-right:5px"
@click="edit(scope.row)"
size="small"
style="margin-right: 5px"
>编辑
</Button>
<Button
type="error"
>编辑</a>
<span style="margin:0 8px;color:#dcdee2">|</span>
<a
style="color:#2d8cf0;cursor:pointer;text-decoration:none;margin-right:5px"
@click="remove(scope.row)"
size="small"
style="margin-right: 5px"
>删除
</Button>
<Button
>删除</a>
<span v-show="scope.row.level != 1" style="margin:0 8px;color:#dcdee2">|</span>
<a
v-show="scope.row.level != 1"
type="success"
style="color:#2d8cf0;cursor:pointer;text-decoration:none"
@click="addChildren(scope.row)"
size="small"
style="margin-right: 5px"
>添加子分类
</Button>
>添加子分类</a>
</template>
</tree-table>
</Card>

View File

@@ -128,7 +128,7 @@ export default {
}
}
/deep/ .ivu-input {
::v-deep .ivu-input {
width: 70px !important;
}
@@ -138,7 +138,7 @@ export default {
}
.label-btns {
/deep/ .ivu-btn {
::v-deep .ivu-btn {
margin-right: 10px;
}
}

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