gameapi-client/src/views/merchant/businessInformation/index.vue

570 lines
22 KiB
Vue
Raw Normal View History

2025-08-14 10:33:48 +08:00
<template>
<div class="app-container">
<table-search-card :model="queryParams" @getList="getList" @handleQuery="handleQuery" @resetQuery="resetQuery">
<template #left>
<el-form-item :label="t('商户账号')" prop="tenantKey">
<el-input
v-model="queryParams.tenantKey"
:placeholder="t('请输入商户账号')"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item :label="t('状态')" prop="tenantStatus">
<CustomSelect v-model="queryParams.tenantStatus" :options="tenantStatusArr" placeholder="请选择状态" style="width: 200px" />
</el-form-item>
</template>
<template #right>
<el-form-item>
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['super:tenant:create']">{{ t('') }}</el-button>
</el-form-item>
</template>
</table-search-card>
<el-table v-loading="loading" :data="agentList" class="c-table-main" border>
<!-- <el-table-column type="selection" width="55" align="center" /> -->
<el-table-column :label="t('商户标识')" width="100" align="center" prop="tenantSn" >
<template #default="{row}">
{{ row.tenantSn }}<br/>
<span v-if="row.agentAccount">{{ t('') }}: {{ row.agentAccount }}</span>
</template>
</el-table-column>
2025-08-29 09:26:00 +08:00
<el-table-column :label="t('商户账号')" align="center" prop="tenantKey" width="170px" >
<template #default="{row}">
<div style="width: 100%;text-align: left;">{{ t('账号') }} {{ row.tenantKey }}</div>
<div style="width: 100%;text-align: left;">{{ t('谷歌') }}
<span v-if="row.googleCode == null || row.googleCode == ''" style="color: #909399;">{{ t('') }}</span>
<span v-else style="color: #1ab394;">{{ t('已绑定') }} <el-button link type="primary" @click="handleUnbindGoogle(row)" v-hasPermi="['super:tenant:resetGoogleCode']">{{ t('') }}</el-button></span>
</div>
</template>
</el-table-column>
2025-08-14 10:33:48 +08:00
<el-table-column :label="t('商户额度')" width="100" align="center" >
<template #default="{row}">
<el-button link type="primary" @click="handleAdjustment(row)" v-hasPermi="['super:tenant:quota:update']">{{ t('') }}</el-button>
</template>
</el-table-column>
<!-- <el-table-column :label="t('商户Api平台配置')" width="100" align="center" >
<template #default="{row}">
<el-button link type="primary" @click="handleView(scope.row)" v-hasPermi="['agent:tenant:view']">{{ t('') }}</el-button>
</template>
</el-table-column> -->
<el-table-column :label="t('注册时间')" align="center" prop="createTime" :show-overflow-tooltip="true">
<template #default="scope">{{ parseTime(scope.row.createTime)|| '--' }}</template>
</el-table-column>
<el-table-column :label="t('最后登录时间')" align="center" prop="loginData" :show-overflow-tooltip="true">
<template #default="scope">{{ parseTime(scope.row.loginData) || '--' }}</template>
</el-table-column>
<el-table-column :label="t('状态')" align="center">
<template #default="{row}">
<!-- <dict-tag :options="ff_tenant_status" :value="String(scope.row.tenantStatus)" /> -->
<base-switch v-model="row.tenantStatus" :active-value="true" :inactive-value="false"
:before-change="() => beforeSwitchChange(row, 'tenantStatus')" v-hasPermi="['super:tenant:switch']" />
</template>
</el-table-column>
<el-table-column :label="t('操作')" align="center" width="260" class-name="small-padding fixed-width">
<template #default="scope">
<el-popconfirm
class="box-item"
:title="t('确定重置该商户密码?')"
placement="top"
@confirm="resetPassword(scope.row)"
>
<template #reference>
<!-- @click="resetPassword(scope.row)" -->
<el-button link type="primary" v-hasPermi="['super:tenant:resetPwd']">{{ t('') }}</el-button>
</template>
</el-popconfirm>
<el-button link type="primary" @click="handleConnection(scope.row)" v-hasPermi="['super:tenant:list']">{{ t('') }}</el-button>
<el-button link type="primary" @click="edit(scope.row)" v-hasPermi="['super:tenant:detail']">{{ t('') }}</el-button>
<el-button link type="primary" @click="whitelist(scope.row)" v-hasPermi="['super:white:list']">{{ t('') }}</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 详情 -->
<el-dialog :title="t('成本比例')" v-model="openView" width="820px" append-to-body>
<el-form :model="form" :rules="rules" label-width="120px">
<el-form-item :label="t('商户账号')" prop="account">
<el-input :disabled="openView" v-model="form.account" :placeholder="t('请输入商户账号')" />
</el-form-item>
<el-form-item :label="t('商户模式')" prop="tenantType">
<el-radio-group v-model="form.tenantType" :disabled="openView">
<el-radio-button v-for="item in ff_tenant_type" :key="item.value" :value="item.value">{{ item.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('买分比例')" >
<el-input :disabled="openView" v-model="form.scoreRatio" placeholder="" />
</el-form-item>
<div class="label-scoreRatio">
<span>{{ t('平台比例') }}</span>
<div>
<el-button type="danger" :disabled="openView">-0.5</el-button>
<el-button type="primary" :disabled="openView">+0.5</el-button>
</div>
</div>
<el-table :data="form.tenantSystemPlatforms" class="scoreRatioTable">
<el-table-column :label="t('平台')" align="center" prop="platformCode" />
<el-table-column :label="t('币种')" align="center" prop="currencyCode" />
<el-table-column :label="t('成本比例(%')" align="center" prop="cost">
<template #default="scope">
{{ scope.row.cost }}%
</template>
</el-table-column>
<el-table-column :label="t('商户通用比例(%')" align="center" prop="useCost">
<template #default="scope">
{{ scope.row.useCost }}%
</template>
</el-table-column>
</el-table>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="openView = false">{{ t('关 闭') }}</el-button>
</div>
</template>
</el-dialog>
<el-dialog :title="t('对接信息')" align-center v-model="openReset" width="620px" append-to-body>
<template #header>
<div class="dialog-header">
<div class="dialog-title" style="width: 100%;text-align: center;">{{ openResetTiele }}</div>
</div>
</template>
<el-descriptions border :column="1" class="c-descriptions" >
<el-descriptions-item label-width="150" label-align="right" :label="t('商户后台')">
<div style="width: 300px;">{{ yueURL }} <CopyIcon :colors="'#409EFF'" v-if="yueURL" :text="String(yueURL)"></CopyIcon></div>
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('登录账号')">
<div style="width: 300px;">{{ ConnectionArr.tenantKey }}<CopyIcon :colors="'#409EFF'" v-if="ConnectionArr.tenantKey" :text="String(ConnectionArr.tenantKey)"></CopyIcon></div>
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('初始密码')">
<div style="width: 300px;"> {{ ConnectionArr.originalPassword }}<CopyIcon :colors="'#409EFF'" v-if="ConnectionArr.originalPassword" :text="String(ConnectionArr.originalPassword)"></CopyIcon></div>
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('系统说明')">
{{ t('为了账户安全,登录成功后请先修改密码,绑定谷歌验证码。') }}
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('商户KEY')">
<div style="width: 300px;">{{ ConnectionArr.tenantKey }}<CopyIcon :colors="'#409EFF'" v-if="ConnectionArr.tenantKey" :text="String(ConnectionArr.tenantKey)"></CopyIcon></div>
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('商户密钥')">
<div style="width: 300px;">{{ ConnectionArr.tenantSecret }}<CopyIcon :colors="'#409EFF'" v-if="ConnectionArr.tenantSecret" :text="String(ConnectionArr.tenantSecret)"></CopyIcon></div>
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('api请求地址')">
<div style="width: 300px;">{{ ConnectionArr.tenantApiUrl }}<CopyIcon :colors="'#409EFF'" v-if="ConnectionArr.tenantApiUrl" :text="String(ConnectionArr.tenantApiUrl)"></CopyIcon></div>
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('对接文档')">
<div style="width: 300px;"> {{ ConnectionArr.documentUrl }}<CopyIcon :colors="'#409EFF'" v-if="ConnectionArr.documentUrl" :text="String(ConnectionArr.documentUrl)"></CopyIcon></div>
</el-descriptions-item>
<el-descriptions-item label-width="150" label-align="right" :label="t('文档密码')">
<div style="width: 300px;"> {{ ConnectionArr.documentPass }}<CopyIcon :colors="'#409EFF'" v-if="ConnectionArr.documentPass" :text="String(ConnectionArr.documentPass)"></CopyIcon></div>
</el-descriptions-item>
</el-descriptions>
<div style="padding-left: 10px;margin-top: 10px;">
<el-button link type="primary" @click="copyAccountInfo(ConnectionArr)">{{ t('') }}</el-button>
</div>
<template #footer>
<div class="dialog-footer" style="width: 100%;text-align: center;">
<el-button type="primary" v-if="openResetTiele == '账号信息'" @click="cancel">{{ t('') }}</el-button>
<el-button @click="cancel" v-if="openResetTiele == '对接信息'">{{ t(' ') }}</el-button>
</div>
</template>
</el-dialog>
<add-merchants-dialog v-if="openShowDialog" :addEditStatus="addEditStatus" :modifyDate="modifyDate" @submit="submitOn"
v-model:show="openShowDialog"></add-merchants-dialog>
<adjustment-dialog v-if="openAdjustment" :addEditStatus="addEditStatus" :modifyDate="modifyDate" @submit="getList"
v-model:show="openAdjustment"></adjustment-dialog>
<whitelist-dialog v-if="whitelistShow" :addEditStatus="addEditStatus" :modifyDate="modifyDate" @submit="getList"
v-model:show="whitelistShow"></whitelist-dialog>
<renew-dialog v-if="renewShow" v-model:show="renewShow" :addEditStatus="addEditStatus" :modifyDate="modifyDate" @submit="submitOn"></renew-dialog>
</div>
</template>
<script setup name="Agent">
2025-08-29 09:26:00 +08:00
import { superTenantList, createTenantCreate,updateSuperTenantResetPwd,updateSuperResetGoogleCode,getSuperTenant,superTenantQuotaList,updateSuperTenantQuotaUpdate,updateSuperTenantSwitch } from "@/api/super/tenant.js";
2025-08-14 10:33:48 +08:00
import { superPlatformSystem } from "@/api/agent";
import { getLocalStorage } from "@/utils/auth";
import AddMerchantsDialog from './components/AddMerchantsDialog'
import AdjustmentDialog from './components/AdjustmentDialog';
import WhitelistDialog from './components/WhitelistDialog';
import RenewDialog from './components/RenewDialog';
import BaseSwitch from '@/components/BaseSwitch'
import CustomSelect from '@/components/CustomSelect'
import CopyIcon from '@/components/CopyIcon'
import Crontab from '@/components/Crontab'
import { parseTime } from '@/utils/ruoyi'; // 时间格式化
const router = useRouter();
const { proxy } = getCurrentInstance();
const { ff_tenant_type, ff_tenant_status } = proxy.useDict("ff_tenant_type", "ff_tenant_status");
const agentList = ref([]);
const openShowDialog = ref(false),addEditStatus = ref('add'),modifyDate = ref({});
const loading = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const openView = ref(false);
const tenantSystemPlatforms = ref([]);
const realBalance = ref([]);
const openResetTiele = ref("");
const tenantStatusArr = ref([
{ label: '正常', value: '1' },
{ label: '停用', value: '0' },
]);
const data = reactive({
form: {
proportion:0,
},
queryParams: {
pageNum: 1,
pageSize: 10,
tenantKey: "",
},
rules: {
account: [{ required: true, message: proxy.t('商户账号不能为空'), trigger: "blur" }],
password: [{ required: true, message: proxy.t('密码不能为空'), trigger: "blur" }],
scoreRatio: [{ required: true, message: proxy.t('买分比例不能为空'), trigger: "blur" }],
tenantType: [{ required: true, message: proxy.t('商户模式不能为空'), trigger: "change" }],
realBalanceNum: [{ required: true, message: proxy.t('信誉额度不能为空'), trigger: "change" }],
}
});
const { queryParams, form, rules } = toRefs(data);
const yueURL = ref(window.location.origin);
const rulesReset = reactive({
pwd: [{ required: true, message: proxy.t('密码不能为空'), trigger: "change" }],
});
const formReset = reactive({
pwd: "",
});
const openReset = ref(false);
/** 查询列表 */
function getList() {
loading.value = true;
superTenantList(queryParams.value).then(response => {
agentList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
// 列表开关事件
const beforeSwitchChange = async (row, undateKeys) => {
const _status = row[undateKeys] == 1 ? 0 : 1
const _data = {
id: row.id,
}
_data[undateKeys] = _status
try {
await updateSuperTenantSwitch(_data).then(() => {
proxy.$modal.msgSuccess(_status === 1 ? proxy.t('开启成功') : proxy.t('关闭成功'))
getList()
})
return true;
} catch (error) {
console.error(proxy.t('接口调用失败'), error);
return false; // 阻止开关状态改变
}
}
/** 任务组名字典翻译 */
function jobGroupFormat(row, column) {
return proxy.selectDictLabel(sys_job_group.value, row.jobGroup);
}
const submitOn = (row) => {
openReset.value = true;
openResetTiele.value = "对接信息";
ConnectionArr.value = row;
getList();
};
/** 取消按钮 */
function cancel() {
openShowDialog.value = false;
openReset.value = false;
reset();
}
/** 表单重置 */
function reset() {
form.value = {
account: "",
password: "",
scoreRatio: 1,
tenantType: 1,
tenantSystemPlatforms: [],
realBalance: [],
};
proxy.resetForm("agentRef");
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
const whitelistShow = ref(false);
const whitelist = (row) => {
whitelistShow.value = true;
modifyDate.value = row;
}
const ConnectionArr = ref({});
const handleConnection = (row) => {
openReset.value = true;
openResetTiele.value = "对接信息";
ConnectionArr.value = row;
}
2025-08-29 09:26:00 +08:00
//解除
const handleUnbindGoogle = (row) => {
proxy.$confirm('确定解除绑定吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
console.log(row)
updateSuperResetGoogleCode({
id: row.id,
}).then(res => {
proxy.$modal.msgSuccess(proxy.t('解除成功'));
getList();
})
}).catch(() => {});
}
2025-08-14 10:33:48 +08:00
const handleCopy = async(text) => {
// 支持现代 API需 HTTPS 或 localhost
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
}
// fallback兼容 HTTP、老浏览器、iframe 等环境
return new Promise((resolve, reject) => {
try {
const textarea = document.createElement('textarea');
textarea.value = text;
// 设置样式防跳动
textarea.style.position = 'fixed';
textarea.style.top = '-9999px';
textarea.style.left = '-9999px';
textarea.setAttribute("readonly", true);
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand("copy");
// copyTextSuccess();
document.body.removeChild(textarea);
success ? resolve() : reject(new Error("execCommand copy failed"));
} catch (err) {
reject(err);
}
});
}
const resetPassword = (row) => {
let objItem = {
id: row.id,
}
updateSuperTenantResetPwd(objItem).then(res => {
handleCopy(res.data);
getList();
openResetTiele.value = "账号信息";
openReset.value = true;
ConnectionArr.value ={
...row,
originalPassword:res.data
};
// proxy.$modal.msgSuccess(proxy.t('密码已复制'));
})
// proxy.$prompt('请输入谷歌验证码', "提示", {
// confirmButtonText: "确定",
// cancelButtonText: "取消",
// closeOnClickModal: false,
// inputValidator: (value) => {
// if (!value) {
// return '验证码不能为空';
// }
// return true;
// }
// }).then(({ value }) => {
// }).catch(() => {});
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
const switchBeforeChange = () => {
return false
}
// 多选框选中数据
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.jobId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 详细信息 */
function handleView(row) {
form.value = {
account: row.tenantKey,
tenantType: 1,
scoreRatio: proxy.t('1万法定货币=1万通用额度'),
tenantSystemPlatforms: row.tenantSystemPlatforms,
realBalance: row.realBalance,
}
openView.value = true;
}
const openAdjustment = ref(false);
const handleAdjustment = (row) => {
openAdjustment.value = true;
modifyDate.value = row;
}
const handleAdjustmentUpdate = (row) => {
let obj = {
id:row.id,
balance:row.balance,
isOut:true
}
updateSuperTenantQuotaUpdate(obj).then(response => {
proxy.$modal.msgSuccess(proxy.t('重置成功'));
openAdjustment.value = false;
handleAdjustment(row);
})
}
const renewShow = ref(false);
const edit = (row) => {
reset();
getSelectPlatform();
getSuperCommonCurrencySelect();
getSuperTenant(row.id).then(response => {
2025-08-26 11:18:24 +08:00
renewShow.value = true;
2025-08-14 10:33:48 +08:00
title.value = proxy.t('修改商户');
// form.value = {
// account: row.tenantKey,
// tenantType: 1,
// scoreRatio: proxy.t('1万法定货币=1万通用额度'),
// tenantSystemPlatforms: row.tenantSystemPlatforms,
// realBalance: row.realBalance,
// }
modifyDate.value = {
...row,
curType: response.data.curType,
account: response.data.tenantKey,
2025-08-26 11:18:24 +08:00
tenantType: response.data.tenantType,
googleCodeSwitch: response.data.googleCodeSwitch,
2025-08-14 10:33:48 +08:00
scoreRatio:response.data.scoreRatio,
tenantSystemPlatforms:row.tenantPlatforms
};
2025-08-26 11:18:24 +08:00
2025-08-14 10:33:48 +08:00
})
}
const platformSystem = ref({
pageNum:1,
pageSize:100,
orderByColumn:'platformCode',
isAsc:'desc'
});
const totalPlatform = ref(0);
/** 获取平台利润 */
function getSelectPlatform() {
superPlatformSystem(platformSystem.value).then(response => {
form.value.tenantSystemPlatforms = response.rows;
totalPlatform.value = response.total;
tenantSystemPlatforms.value = JSON.parse(JSON.stringify(response.rows));
});
}
/** 获取货币 */
function getSuperCommonCurrencySelect() {
let res = getLocalStorage('currencySelect');
let _data = res.map(item => {
return {
...item,
balance:'',
currencyCode:item.currencyCode,
}
})
form.value.realBalance = _data;
realBalance.value = JSON.parse(JSON.stringify(_data));
}
/** 新增按钮操作 */
function handleAdd() {
reset();
openShowDialog.value = true;
title.value = proxy.t('添加商户');
}
const copyAccountInfo = (row)=>{
const content = `
商户后台${yueURL.value}
登录账号${row.tenantKey}
初始密码${row.originalPassword}
系统说明为了账户安全登录成功后请先修改密码绑定谷歌验证
商户KEY${row.tenantKey}
商户密钥${row.tenantSecret}
对接文档${row.documentUrl}
文档密码${row.documentPass}
`.trim();
handleCopy(content).then(() => {
proxy.$modal.msgSuccess(proxy.t('复制成功'));
});
}
const submitFormReset = () => {
proxy.$refs["agentResetRef"].validate(valid => {
if (valid) {
updateSuperTenantResetPwd(formReset).then(response => {
proxy.$modal.msgSuccess(proxy.t('重置成功'));
openReset.value = false;
getList();
});
}
});
}
getList();
</script>
<style scoped lang="scss">
.label-scoreRatio{
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
span{
width: 120px;
text-align: right;
font-weight: 700;
padding-right: 12px;
}
}
.scoreRatioTable{
width: calc(100% - 120px);
margin-left: 120px;
}
</style>