feat: 商户账号管理

This commit is contained in:
lzhizhao 2025-09-11 10:36:47 +08:00
parent eefaf2140e
commit 83078e70c7
3 changed files with 371 additions and 11 deletions

View File

@ -0,0 +1,17 @@
import request from '@/utils/httpRequest'
export function inviteJoin(data) {
return request({
url: '/merchant-api/merchant/invite_join',
method: 'post',
data
})
}
export function setMerchantPermissions(data) {
return request({
url: '/merchant-api/subaccount/set-merchant-permissions',
method: 'post',
data
})
}

View File

@ -1,13 +1,326 @@
<template> <template>
<div>商户账号管理</div> <div class="app-container">
<el-form
ref="ruleFormRef"
:model="form"
label-position="left"
label-width="auto"
:rules="rules"
style="max-width: 600px"
>
<el-form-item v-if="!marketId && markets.length > 0" label="选择市场" prop="selectedMarketId">
<el-select v-model="form.selectedMarketId" placeholder="请选择市场" style="width: 100%">
<el-option
v-for="market in markets"
:key="market.marketId"
:label="market.marketName"
:value="market.marketId"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="操作类型" prop="operationType">
<el-select v-model="form.operationType" placeholder="请选择操作类型" style="width: 100%">
<el-option label="创建新商户" value="create"></el-option>
<el-option label="维护商户信息" value="update"></el-option>
</el-select>
</el-form-item>
<template v-if="form.operationType === 'create'">
<el-form-item label="商户账号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入商户账号"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入密码"></el-input>
</el-form-item>
</template>
<template v-if="form.operationType === 'update'">
<el-form-item label="选择商户账号" prop="accountId">
<el-select v-model="form.accountId" placeholder="请选择要更新的商户账号" @change="handleAccountChange" style="width: 100%">
<el-option
v-for="item in merchantAccountList"
:key="item.accountId"
:label="item.name"
:value="item.accountId"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="warning" @click="openResetPasswordDialog">重置密码</el-button>
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="form.enable" active-text="启用" inactive-text="禁用"></el-switch>
</el-form-item>
</template>
<el-form-item label="权限分配" prop="permissionCodes">
<el-tree
ref="permissionTree"
:data="permissionList"
show-checkbox
node-key="id"
:props="defaultProps"
@check="handleTreeCheck"
>
</el-tree>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
</el-form-item>
</el-form>
<el-dialog
title="重置密码"
:visible.sync="resetPasswordDialogVisible"
width="30%"
@close="newPassword = ''"
>
<el-form label-width="80px">
<el-form-item label="新密码">
<el-input v-model="newPassword" type="password" placeholder="请输入新密码"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="resetPasswordDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleResetPassword"> </el-button>
</span>
</el-dialog>
</div>
</template> </template>
<script> <script>
import {
getAvailablePermissions,
checkUsername,
resetPassword
} from '@/api/modules/subaccount' // Assuming some APIs can be reused
import { inviteJoin, setMerchantPermissions } from '@/api/modules/merchant' // New merchant API
import routerConfig from '@/router/full-routers'
import { mapState } from 'vuex'
// Function to filter the full menu tree based on available permissions
const filterTreeByPerms = (tree, availablePerms) => {
if (!Array.isArray(tree)) {
return [];
}
return tree.reduce((acc, node) => {
const children = node.list && node.list.length > 0
? filterTreeByPerms(node.list, availablePerms)
: [];
if (availablePerms.includes(node.perms) || children.length > 0) {
acc.push({
...node,
list: children,
});
}
return acc;
}, []);
};
// Function to map the filtered tree to the format required by el-tree
const mapTreeForElTree = (tree) => {
return tree.map((item) => ({
id: item.perms,
label: item.name,
children: item.list && item.list.length > 0 ? mapTreeForElTree(item.list) : [],
}));
};
export default { export default {
name: 'MerchantAccount' name: 'MerchantAccount',
data() {
const validateMobile = (rule, value, callback) => {
if (this.form.operationType !== 'create') {
return callback();
}
if (!value) {
return callback(new Error('请输入商户账号'));
}
checkUsername(value, this.form.selectedMarketId || this.marketId).then(res => {
if (res && res.data && res.data.data && res.data.data.available === true) {
callback();
} else {
callback(new Error('商户账号已被占用'));
}
}).catch(() => {
callback(new Error('商户账号校验失败'));
});
};
return {
resetPasswordDialogVisible: false,
newPassword: '',
form: {
operationType: 'create',
mobile: '',
password: '',
permissionCodes: [],
accountId: null,
enable: true,
selectedMarketId: ''
},
merchantAccountList: [], // To be populated with merchant accounts
permissionList: [],
defaultProps: {
children: 'children',
label: 'label'
},
rules: {
operationType: [
{ required: true, message: "请选择操作类型", trigger: "change" },
],
mobile: [
{ required: true, validator: validateMobile, trigger: "blur" },
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
],
permissionCodes: [
{ required: true, message: "请选择菜单权限", trigger: "change", type: 'array' },
],
accountId: [
{ required: true, message: "请选择要更新的商户账号", trigger: "change" },
],
},
};
},
computed: {
...mapState("userData", [
"isMerchant",
"marketList",
"storeList",
"marketId",
"shopId",
"markets"
])
},
watch: {
'form.operationType'(newType) {
this.form.mobile = '';
this.form.password = '';
this.form.permissionCodes = [];
this.form.accountId = null;
this.$nextTick(() => {
this.$refs.ruleFormRef.clearValidate();
});
if (this.$refs.permissionTree) {
this.$refs.permissionTree.setCheckedKeys([]);
}
if (newType === 'update') {
// this.loadMerchantAccountList(); // To be implemented
}
}
},
created() {
this.getPermissions();
},
methods: {
getPermissions() {
getAvailablePermissions().then((res) => {
const availablePerms = (res && res.data && Array.isArray(res.data.data)) ? res.data.data : [];
const menuList = (routerConfig && Array.isArray(routerConfig.menuList)) ? routerConfig.menuList : [];
const filteredTree = filterTreeByPerms(menuList, availablePerms);
this.permissionList = mapTreeForElTree(filteredTree);
});
},
openResetPasswordDialog() {
if (!this.form.accountId) {
this.$message.error('请先选择要更新的商户账号');
return;
}
this.resetPasswordDialogVisible = true;
},
handleResetPassword() {
if (!this.newPassword) {
this.$message.error('请输入新密码');
return;
}
resetPassword(this.form.accountId, this.newPassword, this.form.selectedMarketId || this.marketId).then(() => {
this.$message.success('密码重置成功');
this.resetPasswordDialogVisible = false;
});
},
handleTreeCheck() {
this.form.permissionCodes = this.$refs.permissionTree.getCheckedKeys(true);
},
// To be implemented
// loadMerchantAccountList() {
// getMerchantAccountList().then(res => {
// this.merchantAccountList = res.data.data.data || [];
// });
// },
handleAccountChange(accountId) {
if (!accountId) return;
// getMerchantAccountDetail(accountId).then(res => {
// const accountDetails = res.data.data;
// this.form.permissionCodes = accountDetails.permissionCodes || [];
// this.form.enable = accountDetails.enabled;
// this.$nextTick(() => {
// this.$refs.permissionTree.setCheckedKeys(this.form.permissionCodes);
// });
// });
},
save() {
this.$refs.ruleFormRef.validate((valid) => {
if (valid) {
const marketId = this.form.selectedMarketId || this.marketId;
if (!marketId) {
this.$message.error('请选择市场');
return;
}
if (this.form.operationType === 'create') {
const createData = {
marketId,
name: this.form.mobile,
mobile: this.form.mobile,
remark: '',
username: this.form.mobile,
password: this.form.password
};
inviteJoin(createData).then((res) => {
const merchantAccountId = res.data.data.merchantId; // Assuming this is the merchant ID
const permissionsData = {
merchantAccountId,
permissionCodes: this.form.permissionCodes,
marketId
};
setMerchantPermissions(permissionsData).then(() => {
this.$message.success('商户创建成功且权限已设置');
this.$refs.ruleFormRef.resetFields();
});
});
} else if (this.form.operationType === 'update') {
if (!this.form.accountId) {
this.$message.error('请先选择要更新的商户账号');
return;
}
const updateData = {
subAccountId: this.form.accountId,
permissionCodes: this.form.permissionCodes,
enabled: this.form.enable,
marketId
};
// updateMerchantAccount(updateData).then(() => { // To be implemented
// this.$message.success('');
// this.$refs.ruleFormRef.resetFields();
// });
}
}
});
}
},
} }
</script> </script>
<style scoped> <style scoped>
.app-container {
</style> padding: 20px;
}
</style>

View File

@ -8,6 +8,17 @@
:rules="rules" :rules="rules"
style="max-width: 600px" style="max-width: 600px"
> >
<el-form-item v-if="!marketId && markets.length > 0" label="选择市场" prop="selectedMarketId">
<el-select v-model="form.selectedMarketId" placeholder="请选择市场" style="width: 100%">
<el-option
v-for="market in markets"
:key="market.marketId"
:label="market.marketName"
:value="market.marketId"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="操作类型" prop="operationType"> <el-form-item label="操作类型" prop="operationType">
<el-select v-model="form.operationType" placeholder="请选择操作类型" style="width: 100%"> <el-select v-model="form.operationType" placeholder="请选择操作类型" style="width: 100%">
<el-option label="创建新账号" value="create"></el-option> <el-option label="创建新账号" value="create"></el-option>
@ -100,6 +111,7 @@ import {
resetPassword resetPassword
} from '@/api/modules/subaccount' } from '@/api/modules/subaccount'
import routerConfig from '@/router/full-routers' import routerConfig from '@/router/full-routers'
import { mapState } from 'vuex'
// Function to filter the full menu tree based on available permissions // Function to filter the full menu tree based on available permissions
const filterTreeByPerms = (tree, availablePerms) => { const filterTreeByPerms = (tree, availablePerms) => {
@ -143,7 +155,7 @@ export default {
if (!value) { if (!value) {
return callback(new Error('请输入账号')); return callback(new Error('请输入账号'));
} }
checkUsername(value).then(res => { checkUsername(value, this.form.selectedMarketId || this.marketId).then(res => {
if (res && res.data && res.data.data && res.data.data.available === true) { if (res && res.data && res.data.data && res.data.data.available === true) {
callback(); callback();
} else { } else {
@ -166,7 +178,8 @@ export default {
permissionCodes: [], permissionCodes: [],
remark: '', remark: '',
accountId: null, accountId: null,
enable: true enable: true,
selectedMarketId: ''
}, },
subAccountList: [], subAccountList: [],
permissionList: [], permissionList: [],
@ -199,6 +212,16 @@ export default {
}, },
}; };
}, },
computed: {
...mapState("userData", [
"isMerchant",
"marketList",
"storeList",
"marketId",
"shopId",
"markets"
])
},
watch: { watch: {
'form.operationType'(newType) { 'form.operationType'(newType) {
// Manually reset form fields to avoid resetting the operationType // Manually reset form fields to avoid resetting the operationType
@ -249,7 +272,7 @@ export default {
this.$message.error('请输入新密码'); this.$message.error('请输入新密码');
return; return;
} }
resetPassword(this.form.accountId, this.newPassword).then(() => { resetPassword(this.form.accountId, this.newPassword, this.form.selectedMarketId || this.marketId).then(() => {
this.$message.success('密码重置成功'); this.$message.success('密码重置成功');
this.resetPasswordDialogVisible = false; this.resetPasswordDialogVisible = false;
}); });
@ -259,13 +282,13 @@ export default {
this.form.permissionCodes = this.$refs.permissionTree.getCheckedKeys(true); this.form.permissionCodes = this.$refs.permissionTree.getCheckedKeys(true);
}, },
loadSubAccountList() { loadSubAccountList() {
getSubAccountList().then(res => { getSubAccountList(this.form.selectedMarketId || this.marketId).then(res => {
this.subAccountList = res.data.data.data || []; this.subAccountList = res.data.data.data || [];
}); });
}, },
handleAccountChange(accountId) { handleAccountChange(accountId) {
if (!accountId) return; if (!accountId) return;
getSubAccountDetail(accountId).then(res => { getSubAccountDetail(accountId, this.form.selectedMarketId || this.marketId).then(res => {
const accountDetails = res.data.data; const accountDetails = res.data.data;
this.form.name = accountDetails.name; this.form.name = accountDetails.name;
this.form.mobile = accountDetails.mobile; this.form.mobile = accountDetails.mobile;
@ -280,8 +303,14 @@ export default {
save() { save() {
this.$refs.ruleFormRef.validate((valid) => { this.$refs.ruleFormRef.validate((valid) => {
if (valid) { if (valid) {
const marketId = this.form.selectedMarketId || this.marketId;
if (!marketId) {
this.$message.error('请选择市场');
return;
}
if (this.form.operationType === 'create') { if (this.form.operationType === 'create') {
const createData = { ...this.form }; const createData = { ...this.form, marketId };
if (createData.accountId === null) { if (createData.accountId === null) {
delete createData.accountId; delete createData.accountId;
} }
@ -300,7 +329,8 @@ export default {
mobile: this.form.mobile, mobile: this.form.mobile,
permissionCodes: this.form.permissionCodes, permissionCodes: this.form.permissionCodes,
remark: this.form.remark, remark: this.form.remark,
enabled: this.form.enable enabled: this.form.enable,
marketId
}; };
updateSubAccount(updateData).then(() => { updateSubAccount(updateData).then(() => {
this.$message.success('更新成功'); this.$message.success('更新成功');