Merge remote-tracking branch 'origin/dev/message-oa' into feature/invoice
# Conflicts: # apps/web-antd/src/views/system/system-config/index.vue
This commit is contained in:
@@ -28,6 +28,7 @@ import { createAdminGridOptions } from './table-config';
|
||||
import { getRoleMeta } from './role-meta';
|
||||
import DoctorCardModal from '#/views/doctor/doctor/components/DoctorCardModal.vue';
|
||||
import PromoterInfoCard from '#/components/promoter/PromoterInfoCard.vue';
|
||||
import OaAccountModal from '#/views/system/admin/_shared/components/oa-account-modal.vue';
|
||||
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||
|
||||
function formatAdminRegion(row: {
|
||||
@@ -121,6 +122,20 @@ const openDoctorCard = (row: { doctor_id?: number }) => {
|
||||
DoctorCardModalApi.open();
|
||||
};
|
||||
|
||||
/** 员工 OA 账号弹窗:每个员工可配置各平台的 @ 标识 */
|
||||
const [OaAccountModals, OaAccountModalApi] = useVbenModal({
|
||||
connectedComponent: OaAccountModal,
|
||||
});
|
||||
|
||||
/** 打开员工 OA 账号弹窗 */
|
||||
const openOaAccount = (row: { id: number; nick_name?: string; name?: string }) => {
|
||||
OaAccountModalApi.setData({
|
||||
id: row.id,
|
||||
name: row.nick_name || row.name,
|
||||
});
|
||||
OaAccountModalApi.open();
|
||||
};
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
const records = gridApi.grid.getCheckboxRecords();
|
||||
@@ -210,6 +225,7 @@ const copyToClipboard = async (text: string) => {
|
||||
<Form />
|
||||
</Modal>
|
||||
<DoctorCardModals />
|
||||
<OaAccountModals />
|
||||
<Grid>
|
||||
<template #toolbar-buttons>
|
||||
<TableAction
|
||||
@@ -368,6 +384,13 @@ const copyToClipboard = async (text: string) => {
|
||||
confirm: resetPasswordApi.bind(null, row.id),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'OA 账号',
|
||||
type: 'link',
|
||||
icon: 'ant-design:notification-outlined',
|
||||
size: 'small',
|
||||
onClick: openOaAccount.bind(null, row),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Empty, message, Spin, Tabs } from 'ant-design-vue';
|
||||
|
||||
import { getOaPlatformList } from '#/views/system/oa-platform/api';
|
||||
import {
|
||||
getAdminOaAccounts,
|
||||
saveAdminOaAccounts,
|
||||
} from '#/views/system/admin/_shared/oa-account/api';
|
||||
|
||||
/**
|
||||
* 员工 OA 账号管理弹窗
|
||||
*
|
||||
* 用途:在员工管理页的「更多操作」中点击「OA 账号」打开,
|
||||
* 为该员工配置每个平台的 @ 标识(企业微信 userid、钉钉 userid、飞书 open_id 等)。
|
||||
*
|
||||
* 设计要点:
|
||||
* - 每个启用平台一个 Tab,Tab 内可以添加多条账号(一个员工在一个平台可有多个账号)
|
||||
* - 账号标识和昵称两个输入框:account_id 用于 @,account_name 仅用于展示
|
||||
* - 保存时全量覆盖该员工的所有账号(先软删旧的,再插入新的)
|
||||
*/
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const platforms = ref<any[]>([]);
|
||||
const activeTab = ref('');
|
||||
const adminId = ref(0);
|
||||
const adminName = ref('');
|
||||
|
||||
/** 按平台分组的账号列表:[platform_code => [{account_id, account_name, key}]] */
|
||||
const accountsByPlatform = ref<Record<string, Array<{ account_id: string; account_name: string; key: number }>>>({});
|
||||
|
||||
let keyCounter = 0;
|
||||
function genKey() {
|
||||
keyCounter += 1;
|
||||
return keyCounter;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
draggable: true,
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
if (!adminId.value) {
|
||||
message.error('员工 ID 不能为空');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
// 提交时去除 key 字段(后端不需要),过滤空 account_id
|
||||
const payload: Record<string, any[]> = {};
|
||||
for (const [platformCode, accounts] of Object.entries(accountsByPlatform.value)) {
|
||||
const filtered = accounts
|
||||
.filter((a) => a.account_id.trim() !== '')
|
||||
.map((a) => ({
|
||||
account_id: a.account_id.trim(),
|
||||
account_name: a.account_name,
|
||||
}));
|
||||
if (filtered.length > 0) {
|
||||
payload[platformCode] = filtered;
|
||||
}
|
||||
}
|
||||
await saveAdminOaAccounts(adminId.value, payload);
|
||||
message.success('保存成功');
|
||||
modalApi.close();
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const data = modalApi.getData<{ id: number; name?: string }>();
|
||||
adminId.value = data?.id || 0;
|
||||
adminName.value = data?.name || '';
|
||||
loadData();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 加载平台列表 + 该员工的已有账号
|
||||
*/
|
||||
async function loadData() {
|
||||
if (!adminId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const [platformList, accountList] = await Promise.all([
|
||||
getOaPlatformList(),
|
||||
getAdminOaAccounts(adminId.value),
|
||||
]);
|
||||
const platformRows: any[] = Array.isArray(platformList)
|
||||
? platformList
|
||||
: platformList?.data || [];
|
||||
platforms.value = platformRows.sort(
|
||||
(a: any, b: any) => (b.sort || 0) - (a.sort || 0),
|
||||
);
|
||||
activeTab.value = platforms.value[0]?.platform_code || '';
|
||||
|
||||
// 把后端返回的按平台分组的账号填充到表单
|
||||
const grouped: typeof accountsByPlatform.value = {};
|
||||
for (const platform of platforms.value) {
|
||||
const code = platform.platform_code;
|
||||
const accounts: any[] = (accountList?.[code] || accountList?.data?.[code]) || [];
|
||||
grouped[code] = accounts.map((a) => ({
|
||||
account_id: String(a.account_id || ''),
|
||||
account_name: String(a.account_name || ''),
|
||||
key: genKey(),
|
||||
}));
|
||||
// 至少留一条空行,便于新增
|
||||
if (grouped[code].length === 0) {
|
||||
grouped[code] = [{ account_id: '', account_name: '', key: genKey() }];
|
||||
}
|
||||
}
|
||||
accountsByPlatform.value = grouped;
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '加载数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一行账号输入框
|
||||
*/
|
||||
function addAccount(platformCode: string) {
|
||||
if (!accountsByPlatform.value[platformCode]) {
|
||||
accountsByPlatform.value[platformCode] = [];
|
||||
}
|
||||
accountsByPlatform.value[platformCode].push({
|
||||
account_id: '',
|
||||
account_name: '',
|
||||
key: genKey(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一行账号
|
||||
*/
|
||||
function removeAccount(platformCode: string, key: number) {
|
||||
accountsByPlatform.value[platformCode] = (
|
||||
accountsByPlatform.value[platformCode] || []
|
||||
).filter((a) => a.key !== key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台编码 → 显示名
|
||||
*/
|
||||
function platformName(code: string): string {
|
||||
return platforms.value.find((p) => p.platform_code === code)?.name || code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台编码 → 该平台 @ 标识的提示文案
|
||||
*/
|
||||
function platformTip(code: string): string {
|
||||
if (code === 'work_wechat') return '企业微信 userid(在通讯录中查看,组织内唯一)';
|
||||
if (code === 'work_wechat_app')
|
||||
return '企业微信 userid(应用 API @ 群成员可选;员工 @ 主路径仍用手机号)';
|
||||
if (code === 'dingtalk') return '钉钉 userid(管理后台 → 通讯录 → 成员详情)';
|
||||
if (code === 'feishu') return '飞书 open_id(ou_ 开头)';
|
||||
return '该平台的账号标识';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:title="`员工 OA 账号 - ${adminName || `#${adminId}`}`"
|
||||
class="w-[50%]"
|
||||
>
|
||||
<Spin :spinning="loading || saving">
|
||||
<div v-if="platforms.length === 0 && !loading" class="py-6">
|
||||
<Empty description="暂无启用的 OA 平台" />
|
||||
</div>
|
||||
<Tabs v-else v-model:active-key="activeTab">
|
||||
<Tabs.TabPane
|
||||
v-for="platform in platforms"
|
||||
:key="platform.platform_code"
|
||||
:tab="platform.name"
|
||||
>
|
||||
<div class="oa-account-tab">
|
||||
<div class="mb-3 text-sm text-gray-500">
|
||||
{{ platformTip(platform.platform_code) }}
|
||||
</div>
|
||||
<div
|
||||
v-for="account in accountsByPlatform[platform.platform_code] || []"
|
||||
:key="account.key"
|
||||
class="mb-2 flex items-center gap-2"
|
||||
>
|
||||
<input
|
||||
v-model="account.account_id"
|
||||
class="ant-input ant-input-sm flex-1"
|
||||
placeholder="账号标识(用于 @)"
|
||||
/>
|
||||
<input
|
||||
v-model="account.account_name"
|
||||
class="ant-input ant-input-sm flex-1"
|
||||
placeholder="账号昵称(仅用于展示,可选)"
|
||||
/>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="removeAccount(platform.platform_code, account.key)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
block
|
||||
@click="addAccount(platform.platform_code)"
|
||||
>
|
||||
+ 添加{{ platformName(platform.platform_code) }}账号
|
||||
</Button>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.oa-account-tab {
|
||||
padding: 8px;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 员工 OA 账号管理 API
|
||||
* 路由前缀:admin-oa-account/
|
||||
*/
|
||||
const prefix = 'admin-oa-account/';
|
||||
|
||||
/**
|
||||
* 获取指定员工的所有 OA 账号(按平台分组)
|
||||
* 返回结构:['work_wechat' => [{id, account_id, account_name}, ...], ...]
|
||||
*/
|
||||
export async function getAdminOaAccounts(adminId: number) {
|
||||
return requestClient.get<any>(`${prefix}list`, { params: { admin_id: adminId } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存员工的 OA 账号配置(全量同步)
|
||||
* accounts 结构:{ platform_code: [{account_id, account_name}, ...] }
|
||||
*/
|
||||
export async function saveAdminOaAccounts(adminId: number, accounts: Record<string, any[]>) {
|
||||
return requestClient.post<any>(`${prefix}save`, { admin_id: adminId, accounts });
|
||||
}
|
||||
Reference in New Issue
Block a user