mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-06-21 09:30:24 +08:00
feat(会员管理): 添加第三方账户绑定和会员等级功能
- 在登录API中新增获取第三方账户绑定列表、绑定和解绑功能 - 在会员API中新增获取当前会员等级、等级规则和等级列表的接口 - 在会员中心页面中添加第三方账户绑定的UI和逻辑 - 新增会员等级页面,展示当前等级、经验值和经验值记录
This commit is contained in:
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -489,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
|
||||
|
||||
@@ -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 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,6 +131,7 @@ export default {
|
||||
this.$router.push({name: 'ModifyPwd', query: { status: 1 }})
|
||||
},
|
||||
// 获取密码状态
|
||||
getPwdStatus () {},
|
||||
// getPwdStatus () {
|
||||
// getPwdStatus().then(res => {
|
||||
// if (res) {
|
||||
@@ -49,6 +141,124 @@ export default {
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
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>
|
||||
@@ -76,4 +286,67 @@ export default {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
431
buyer/src/pages/home/memberCenter/MemberGrade.vue
Normal file
431
buyer/src/pages/home/memberCenter/MemberGrade.vue
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -76,6 +76,11 @@ const member = [{
|
||||
icon: '',
|
||||
title: '我的积分',
|
||||
path: 'Point'
|
||||
},
|
||||
{
|
||||
icon: '',
|
||||
title: '我的等级',
|
||||
path: 'MemberGrade'
|
||||
}
|
||||
],
|
||||
display: true
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user