批量优化
This commit is contained in:
1 parent
438145cae7
commit
c925b198d1
50 files changed
+5716
-291
No files matched your search
@@ -0,0 +1,40 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
/**
|
||||
* 小程序多应用管理
|
||||
*/
|
||||
|
||||
/** 获取小程序应用列表(AppSecret 不回显,返回 configured 标记) */
|
||||
export function listMiniApps() {
|
||||
return request({
|
||||
url: "/platform/wechatMini/apps",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增/更新小程序应用
|
||||
* @param {Object} data
|
||||
* @param {number} [data.id] 更新时携带;新增不传
|
||||
* @param {string} data.name 应用名称
|
||||
* @param {string} data.app_id 小程序 AppID
|
||||
* @param {string} [data.app_secret] 掩码/空串 = 不修改原值
|
||||
* @param {number} data.enabled 1=启用 0=停用
|
||||
* @param {number} data.auto_bind 1=手机号命中身份后自动绑定
|
||||
* @param {string} [data.remark] 备注
|
||||
*/
|
||||
export function saveMiniApp(data) {
|
||||
return request({
|
||||
url: "/platform/wechatMini/app/save",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除应用(软删并停用) */
|
||||
export function deleteMiniApp(id) {
|
||||
return request({
|
||||
url: `/platform/wechatMini/app/delete/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
/**
|
||||
* 获取企业申请列表(platform 审核用)
|
||||
* @param {Object} params
|
||||
* @param {number} params.page 页码
|
||||
* @param {number} params.pageSize 每页数量
|
||||
* @param {number} params.type 类型筛选:1=加入企业 2=创建企业
|
||||
* @param {number} params.status 状态筛选:0=待审核 1=已通过 2=已驳回
|
||||
*/
|
||||
export function getTenantApplies(params) {
|
||||
return request({
|
||||
url: "/platform/tenantApply/list",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取申请详情
|
||||
* @param {number|string} id 申请ID
|
||||
*/
|
||||
export function getTenantApplyDetail(id) {
|
||||
return request({
|
||||
url: `/platform/tenantApply/detail/${id}`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过创建企业申请:自动创建租户并把申请人绑定为企业管理员
|
||||
* @param {number|string} id 申请ID
|
||||
*/
|
||||
export function approveTenantApply(id) {
|
||||
return request({
|
||||
url: `/platform/tenantApply/approve/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回申请
|
||||
* @param {number|string} id 申请ID
|
||||
* @param {{reason: string}} data 驳回原因
|
||||
*/
|
||||
export function rejectTenantApply(id, data) {
|
||||
return request({
|
||||
url: `/platform/tenantApply/reject/${id}`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -39,6 +39,10 @@
|
||||
controls-position="right" />
|
||||
<div class="form-tip">默认 20 人;达到上限后需增购用户数才能继续开账号</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!formData.id" label="指定管理员" prop="admin_identifier">
|
||||
<el-input v-model="formData.admin_identifier" placeholder="选填:已有用户的账号或手机号" clearable />
|
||||
<div class="form-tip">填已有用户,创建后自动绑定为该租户的管理员(不新建账号、不修改其原密码);留空则租户暂无管理员</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
@@ -109,7 +113,8 @@ const initialData = {
|
||||
address: '',
|
||||
status: 1,
|
||||
package_id: undefined as number | undefined,
|
||||
user_quota: 20
|
||||
user_quota: 20,
|
||||
admin_identifier: '' as string
|
||||
};
|
||||
|
||||
const formData = reactive({ ...initialData });
|
||||
@@ -184,12 +189,30 @@ const submitForm = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 发起真正的保存请求
|
||||
const saveApi = formData.id ? editTenant(formData.id, formData) : createTenant(formData);
|
||||
// 3. 发起真正的保存请求(创建时把「指定管理员」拆成后端归并键字段)
|
||||
let payload: any = { ...formData };
|
||||
if (!formData.id) {
|
||||
const identifier = String(formData.admin_identifier || '').trim();
|
||||
delete payload.admin_identifier;
|
||||
if (identifier) {
|
||||
if (/^1[3-9]\d{9}$/.test(identifier)) {
|
||||
payload.admin_phone = identifier;
|
||||
} else {
|
||||
payload.admin_account = identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
const saveApi = formData.id ? editTenant(formData.id, formData) : createTenant(payload);
|
||||
const saveRes = await saveApi;
|
||||
|
||||
if (saveRes.code === 200) {
|
||||
ElMessage.success('保存成功');
|
||||
if (saveRes.data && saveRes.data.admin_bound) {
|
||||
ElMessage.success('保存成功,已绑定指定用户为租户管理员');
|
||||
} else if (saveRes.msg && saveRes.msg !== 'success') {
|
||||
ElMessage.warning(saveRes.msg);
|
||||
} else {
|
||||
ElMessage.success('保存成功');
|
||||
}
|
||||
visible.value = false;
|
||||
emit('success');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div class="miniapp-settings">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>小程序应用</span>
|
||||
<el-button type="primary" size="small" @click="openAdd">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增小程序
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="tip-alert"
|
||||
title="支持配置多个小程序。小程序端登录时按其 AppID 匹配对应配置;未传 AppID 时使用列表中第一条启用记录。"
|
||||
/>
|
||||
|
||||
<el-table :data="apps" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="60" align="center" />
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="应用名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.name || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="app_id"
|
||||
label="AppID"
|
||||
min-width="180"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="AppSecret" width="110" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.configured ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ scope.row.configured ? "已配置" : "未配置" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="启用" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
:model-value="scope.row.enabled === 1"
|
||||
@change="(val) => toggleEnabled(scope.row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机号自动绑定" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
:model-value="scope.row.auto_bind === 1"
|
||||
@change="(val) => toggleAutoBind(scope.row, val)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark || "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="create_time"
|
||||
label="创建时间"
|
||||
width="170"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" link @click="openEdit(scope.row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDelete(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="form.id ? '编辑小程序' : '新增小程序'"
|
||||
width="520px"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="应用名称" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="如:商城小程序"
|
||||
maxlength="64"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="AppID" prop="app_id">
|
||||
<el-input v-model="form.app_id" placeholder="wx 开头的小程序 AppID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="AppSecret" prop="app_secret">
|
||||
<el-input
|
||||
v-model="form.app_secret"
|
||||
type="password"
|
||||
show-password
|
||||
:placeholder="
|
||||
form.id && secretConfigured
|
||||
? '已配置,留空 / 掩码表示不修改'
|
||||
: '小程序 AppSecret'
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="form.enabled" :active-value="1" :inactive-value="0" />
|
||||
<span class="form-tip">启用后小程序端才可使用微信一键登录</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号自动绑定">
|
||||
<el-switch v-model="form.auto_bind" :active-value="1" :inactive-value="0" />
|
||||
<span class="form-tip">手机号命中已有身份后自动完成绑定</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
placeholder="选填"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSave">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import { listMiniApps, saveMiniApp, deleteMiniApp } from "@/api/miniApp";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const apps = ref([]);
|
||||
const dialogVisible = ref(false);
|
||||
const formRef = ref(null);
|
||||
const secretConfigured = ref(false);
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: 0,
|
||||
name: "",
|
||||
app_id: "",
|
||||
app_secret: "",
|
||||
enabled: 0,
|
||||
auto_bind: 1,
|
||||
remark: "",
|
||||
});
|
||||
|
||||
const form = ref(emptyForm());
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: "请输入应用名称", trigger: "blur" }],
|
||||
app_id: [
|
||||
{ required: true, message: "请输入小程序 AppID", trigger: "blur" },
|
||||
{ pattern: /^wx[0-9a-zA-Z]{10,}$/, message: "AppID 一般以 wx 开头", trigger: "blur" },
|
||||
],
|
||||
};
|
||||
|
||||
const fetchApps = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listMiniApps();
|
||||
if (res.code === 200) {
|
||||
apps.value = res.data.list || [];
|
||||
} else {
|
||||
ElMessage.error(res.msg || "获取应用列表失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "获取应用列表失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openAdd = () => {
|
||||
form.value = emptyForm();
|
||||
secretConfigured.value = false;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const openEdit = (row) => {
|
||||
form.value = {
|
||||
id: row.id,
|
||||
name: row.name || "",
|
||||
app_id: row.app_id || "",
|
||||
app_secret: "",
|
||||
enabled: row.enabled,
|
||||
auto_bind: row.auto_bind,
|
||||
remark: row.remark || "",
|
||||
};
|
||||
secretConfigured.value = !!row.configured;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
saving.value = true;
|
||||
try {
|
||||
const res = await saveMiniApp({ ...form.value });
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg || "保存成功");
|
||||
dialogVisible.value = false;
|
||||
fetchApps();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "保存失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "保存失败");
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 快捷开关:直接以当前值保存
|
||||
const toggleEnabled = async (row, val) => {
|
||||
await quickToggle(row, "enabled", val);
|
||||
};
|
||||
|
||||
const toggleAutoBind = async (row, val) => {
|
||||
await quickToggle(row, "auto_bind", val);
|
||||
};
|
||||
|
||||
const quickToggle = async (row, field, val) => {
|
||||
try {
|
||||
const res = await saveMiniApp({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
app_id: row.app_id,
|
||||
app_secret: "",
|
||||
enabled: field === "enabled" ? (val ? 1 : 0) : row.enabled,
|
||||
auto_bind: field === "auto_bind" ? (val ? 1 : 0) : row.auto_bind,
|
||||
remark: row.remark || "",
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("已更新");
|
||||
fetchApps();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "更新失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "更新失败");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除小程序「${row.name || row.app_id}」?删除后该小程序端将无法使用微信一键登录。`,
|
||||
"删除应用",
|
||||
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" },
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await deleteMiniApp(row.id);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg || "已删除");
|
||||
fetchApps();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "删除失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "删除失败");
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchApps();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.miniapp-settings {
|
||||
.settings-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tip-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
margin-left: 10px;
|
||||
color: #8a94ad;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -410,44 +410,6 @@
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 小程序一键登录配置 -->
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>微信小程序(一键登录)配置</span>
|
||||
<el-tag v-if="Number(miniCfg.enabled) === 1 && miniCfg.configured" type="success" size="small">已启用</el-tag>
|
||||
<el-tag v-else type="info" size="small" class="ml8">未启用:小程序端仅账号密码登录</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="miniCfg.enabled" :active-value="1" :inactive-value="0" />
|
||||
<span class="form-tip-inline">开启后 uniapp 小程序端登录页展示「微信手机号一键登录」主入口</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="AppID" required>
|
||||
<el-input v-model="miniCfg.app_id" placeholder="小程序后台「开发管理 → 开发设置」中的 AppID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="AppSecret">
|
||||
<el-input v-model="miniCfg.app_secret" type="password" show-password placeholder="已保存时显示掩码,保持不变则无需修改" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="自动绑定">
|
||||
<el-switch v-model="miniCfg.auto_bind" :active-value="1" :inactive-value="0" />
|
||||
<span class="form-tip-inline">开启后未通过手机号验证的 openid 也会自动绑定账号;关闭时仅在手机号验证通过后建立绑定</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="miniCfg.remark" placeholder="仅后台可见" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="miniSaving" @click="saveMiniConfig">保存配置</el-button>
|
||||
<div class="form-tip">
|
||||
① 此处 AppID 必须与 uniapp manifest.json「mp-weixin.appid」一致,否则 code 换 openid 会报 40029;<br />
|
||||
② 「手机号一键登录」需小程序为已认证企业主体,并在小程序后台开通「手机号快速验证」能力;<br />
|
||||
③ 保存并启用后,小程序端登录页自动切换为微信认证登录优先、账号密码其次。
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 绑定我的微信 -->
|
||||
<!-- <el-card shadow="never" class="settings-card bind-card">
|
||||
<template #header>
|
||||
@@ -626,6 +588,19 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 小程序配置(支持多个小程序应用) -->
|
||||
<el-tab-pane name="miniapp">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><Cellphone /></el-icon>
|
||||
<span>小程序配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<miniAppSettings />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
@@ -661,8 +636,6 @@ import { formatTime } from "@/utils/datetime";
|
||||
import {
|
||||
getWechatMpConfig,
|
||||
saveWechatMpConfig,
|
||||
getWechatMiniConfig,
|
||||
saveWechatMiniConfig,
|
||||
testWechatMpConnection,
|
||||
publishWechatMpMenu,
|
||||
uploadWechatFollowQrcode,
|
||||
@@ -678,6 +651,7 @@ import {
|
||||
pushWechatMpMessage,
|
||||
pushWechatMpTest
|
||||
} from "@/api/wechatMp";
|
||||
import miniAppSettings from "./miniAppSettings.vue";
|
||||
|
||||
const STORAGE_KEY = "notification_settings_draft";
|
||||
const activeSubTab = ref("email");
|
||||
@@ -1089,56 +1063,6 @@ const saveWechatConfig = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
/* ==================== 微信小程序(一键登录) ==================== */
|
||||
|
||||
const miniCfg = reactive({
|
||||
app_id: "",
|
||||
app_secret: "",
|
||||
enabled: 0,
|
||||
auto_bind: 1,
|
||||
remark: "",
|
||||
configured: false,
|
||||
});
|
||||
const miniSaving = ref(false);
|
||||
|
||||
const loadMiniConfig = async () => {
|
||||
try {
|
||||
const res = await getWechatMiniConfig();
|
||||
if (res.code === 200 && res.data) {
|
||||
Object.assign(miniCfg, res.data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("加载小程序配置失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const saveMiniConfig = async () => {
|
||||
if (!miniCfg.app_id) {
|
||||
ElMessage.warning("请先填写 AppID");
|
||||
return;
|
||||
}
|
||||
miniSaving.value = true;
|
||||
try {
|
||||
const res = await saveWechatMiniConfig({
|
||||
app_id: miniCfg.app_id,
|
||||
app_secret: miniCfg.app_secret,
|
||||
enabled: Number(miniCfg.enabled) === 1 ? 1 : 0,
|
||||
auto_bind: Number(miniCfg.auto_bind) === 1 ? 1 : 0,
|
||||
remark: miniCfg.remark,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("小程序配置保存成功");
|
||||
await loadMiniConfig();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "保存失败");
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || "保存失败");
|
||||
} finally {
|
||||
miniSaving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关注二维码上传/删除(存 data URL,供租户端「绑定微信」页展示扫码)
|
||||
const followQrcodeUploading = ref(false);
|
||||
const handleFollowQrcodeUpload = async ({ file }) => {
|
||||
@@ -1434,7 +1358,6 @@ onMounted(() => {
|
||||
loadSiteReminderConfig();
|
||||
loadBarkConfig();
|
||||
loadWechatConfig();
|
||||
loadMiniConfig();
|
||||
loadWechatBinding();
|
||||
});
|
||||
|
||||
@@ -1442,7 +1365,6 @@ onMounted(() => {
|
||||
watch(activeSubTab, (tab) => {
|
||||
if (tab === "wechat") {
|
||||
loadWechatConfig();
|
||||
loadMiniConfig();
|
||||
loadWechatBinding();
|
||||
loadFollowers();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
<template>
|
||||
<div class="tenant-apply">
|
||||
<div class="header-bar">
|
||||
<h2>企业创建审核</h2>
|
||||
<div>
|
||||
<el-button @click="refresh">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
|
||||
<!-- 搜索筛选 -->
|
||||
<div class="filter-bar">
|
||||
<el-form :inline="true" :model="filterForm" class="filter-form">
|
||||
<el-form-item label="申请类型">
|
||||
<el-select
|
||||
v-model="filterForm.type"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option label="创建企业" :value="2" />
|
||||
<el-option label="加入企业" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select
|
||||
v-model="filterForm.status"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option label="待审核" :value="0" />
|
||||
<el-option label="已通过" :value="1" />
|
||||
<el-option label="已驳回" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 申请列表 -->
|
||||
<el-table :data="applies" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="70" align="center" />
|
||||
<el-table-column label="申请类型" width="110" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.type === 2 ? 'primary' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ scope.row.type === 2 ? "创建企业" : "加入企业" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请人" min-width="140" align="center">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.identity_nickname || "-" }}</div>
|
||||
<div class="sub-text">{{ scope.row.identity_mobile || "" }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="tenant_name"
|
||||
label="企业名称"
|
||||
min-width="170"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="credit_code"
|
||||
label="统一社会信用代码"
|
||||
width="190"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="legal_person"
|
||||
label="法定代表人"
|
||||
width="110"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="联系人" min-width="140" align="center">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.contact_person || "-" }}</div>
|
||||
<div class="sub-text">{{ scope.row.contact_phone || "" }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="statusTagType(scope.row.status)" size="small">
|
||||
{{ statusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="190" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" link @click="handleDetail(scope.row)">
|
||||
详情
|
||||
</el-button>
|
||||
<template v-if="scope.row.type === 2 && scope.row.status === 0">
|
||||
<el-button size="small" type="success" link @click="handleApprove(scope.row)">
|
||||
通过
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleReject(scope.row)">
|
||||
驳回
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-bar">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog v-model="detailVisible" title="申请详情" width="640px">
|
||||
<el-descriptions v-if="current" :column="2" border>
|
||||
<el-descriptions-item label="申请类型" :span="2">
|
||||
{{ current.type === 2 ? "创建企业" : "加入企业" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">
|
||||
{{ current.identity_nickname || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人手机号">
|
||||
{{ current.identity_mobile || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="企业名称" :span="2">
|
||||
{{ current.tenant_name || "-" }}
|
||||
</el-descriptions-item>
|
||||
<template v-if="current.type === 2">
|
||||
<el-descriptions-item label="统一社会信用代码">
|
||||
{{ current.credit_code || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人">
|
||||
{{ current.legal_person || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="营业执照" :span="2">
|
||||
<template v-if="current.license_file">
|
||||
<el-image
|
||||
v-if="isImage(current.license_file)"
|
||||
:src="current.license_file"
|
||||
:preview-src-list="[current.license_file]"
|
||||
fit="cover"
|
||||
style="width: 120px; height: 90px; border-radius: 4px"
|
||||
/>
|
||||
<el-link
|
||||
v-else
|
||||
type="primary"
|
||||
:href="current.license_file"
|
||||
target="_blank"
|
||||
>
|
||||
查看(PDF)
|
||||
</el-link>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
<el-descriptions-item label="联系人">
|
||||
{{ current.contact_person || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">
|
||||
{{ current.contact_phone || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="电子邮箱">
|
||||
{{ current.contact_email || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司地址">
|
||||
{{ current.address || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
{{ statusText(current.status) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">
|
||||
{{ formatDateTime(current.create_time) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审核人">
|
||||
{{ current.reviewer_name || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审核时间">
|
||||
{{ current.review_time ? formatDateTime(current.review_time) : "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
v-if="current.status === 2"
|
||||
label="驳回原因"
|
||||
:span="2"
|
||||
>
|
||||
{{ current.reject_reason || "-" }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
<template v-if="current && current.type === 2 && current.status === 0">
|
||||
<el-button type="success" @click="handleApprove(current); detailVisible = false">
|
||||
通过
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleReject(current); detailVisible = false">
|
||||
驳回
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Refresh, Search } from "@element-plus/icons-vue";
|
||||
import {
|
||||
getTenantApplies,
|
||||
getTenantApplyDetail,
|
||||
approveTenantApply,
|
||||
rejectTenantApply,
|
||||
} from "@/api/tenantApply";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
const page = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const total = ref(0);
|
||||
const loading = ref(false);
|
||||
const applies = ref([]);
|
||||
const detailVisible = ref(false);
|
||||
const current = ref(null);
|
||||
|
||||
const filterForm = ref({
|
||||
type: "",
|
||||
status: "",
|
||||
});
|
||||
|
||||
const statusText = (status) => {
|
||||
const map = { 0: "待审核", 1: "已通过", 2: "已驳回" };
|
||||
return map[status] || "未知";
|
||||
};
|
||||
|
||||
const statusTagType = (status) => {
|
||||
const map = { 0: "warning", 1: "success", 2: "danger" };
|
||||
return map[status] || "info";
|
||||
};
|
||||
|
||||
const isImage = (url) => /\.(png|jpe?g|gif|webp|bmp)(\?.*)?$/i.test(url || "");
|
||||
|
||||
// 获取申请列表
|
||||
const fetchApplies = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
};
|
||||
if (filterForm.value.type !== "") params.type = filterForm.value.type;
|
||||
if (filterForm.value.status !== "") params.status = filterForm.value.status;
|
||||
const res = await getTenantApplies(params);
|
||||
if (res.code === 200) {
|
||||
applies.value = res.data.list || [];
|
||||
total.value = res.data.total || 0;
|
||||
} else {
|
||||
ElMessage.error(res.msg || "获取申请列表失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "获取申请列表失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
fetchApplies();
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
page.value = 1;
|
||||
fetchApplies();
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
filterForm.value = { type: "", status: "" };
|
||||
page.value = 1;
|
||||
fetchApplies();
|
||||
};
|
||||
|
||||
const handleSizeChange = () => {
|
||||
page.value = 1;
|
||||
fetchApplies();
|
||||
};
|
||||
|
||||
const handlePageChange = () => {
|
||||
fetchApplies();
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const handleDetail = async (row) => {
|
||||
current.value = { ...row };
|
||||
detailVisible.value = true;
|
||||
try {
|
||||
const res = await getTenantApplyDetail(row.id);
|
||||
if (res.code === 200 && res.data) {
|
||||
current.value = { ...current.value, ...res.data };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取申请详情失败", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 通过申请:创建租户并绑定申请人为企业管理员
|
||||
const handleApprove = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认通过「${row.tenant_name}」的创建企业申请?通过后将自动创建租户,申请人(${row.identity_nickname || row.identity_mobile})会成为该企业的管理员账号,不可更改。`,
|
||||
"通过申请",
|
||||
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" },
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await approveTenantApply(row.id);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg || "已通过");
|
||||
fetchApplies();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "操作失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "操作失败");
|
||||
}
|
||||
};
|
||||
|
||||
// 驳回申请(需填写原因)
|
||||
const handleReject = async (row) => {
|
||||
let reason = "";
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt("请输入驳回原因", `驳回「${row.tenant_name}」的申请`, {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
inputPlaceholder: "驳回原因(可选)",
|
||||
inputValidator: (v) => {
|
||||
if (v && v.length > 200) return "原因不能超过200字";
|
||||
return true;
|
||||
},
|
||||
});
|
||||
reason = value || "";
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await rejectTenantApply(row.id, { reason });
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg || "已驳回");
|
||||
fetchApplies();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "操作失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "操作失败");
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchApplies();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tenant-apply {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.filter-form {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.sub-text {
|
||||
color: #8a94ad;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user