feat(会员管理): 添加第三方账户绑定和会员等级功能

- 在登录API中新增获取第三方账户绑定列表、绑定和解绑功能
- 在会员API中新增获取当前会员等级、等级规则和等级列表的接口
- 在会员中心页面中添加第三方账户绑定的UI和逻辑
- 新增会员等级页面,展示当前等级、经验值和经验值记录
This commit is contained in:
pikachu1995@126.com
2026-05-13 18:53:33 +08:00
parent fe4e0ce75c
commit aa4bc81da4
22 changed files with 3262 additions and 386 deletions

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>