feat(emergency): 实现急救宣教推送到 IM 会话功能

- 新增 FirstAidPush 组件:分类筛选 + 搜索 + 单条/一键推送
- IM 端切换会话时 postMessage 携带 orderId 通知父页面
- 父页面监听 sessionChanged 联动推送按钮与后端调用
- 推送调后端 push/send 记录状态 + IM postMessage 推卡片
- 资源管理正文改富文本编辑器,修批量操作重复提示
- 重建 iweb 产物
This commit is contained in:
wanghao
2026-06-24 17:23:36 +08:00
parent a2a0e274f4
commit 6bb4b97ff4
21 changed files with 993 additions and 234 deletions
@@ -151,6 +151,19 @@
store.commit('setOrderId', item.children.orderId);
store.commit('setSessionId', item.children.sessionId);
store.commit('setConsultInfo', { consultType: item.type, consultStatus: item?.children?.contentStatus });
// 通知父页面会话切换,携带 orderId 用于急救宣教推送等场景
try {
window.parent.postMessage(
JSON.stringify({
code: 'sessionChanged',
orderId: item.children.orderId,
sessionId: item.children.sessionId,
}),
'*'
);
} catch (e) {
console.log(e);
}
const it = item.children;
if (hrefParams.isSpecialized === 'expert') {
store.commit(
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"><link rel="icon" href="faviconnew.png"><title>即时通信</title><link href="css/vendors~app.28a73048.css" rel="stylesheet"><link href="css/app.7113887a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but 腾讯云即时通信 Web-IM doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="./TCaptcha.js"></script><script src="./aegis.min.js"></script><script>/**
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"><link rel="icon" href="faviconnew.png"><title>即时通信</title><link href="css/vendors~app.3f294141.css" rel="stylesheet"><link href="css/app.e6e597ee.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but 腾讯云即时通信 Web-IM doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="./TCaptcha.js"></script><script src="./aegis.min.js"></script><script>/**
* 根据基础字体大小 和 效果图宽度设置 根节点字体大小
* @param baseFontSize 浏览器默认字体大小 [注]浏览器最小字体为12像素,所以该值必需大于等于12
* @param baseWidth 设计稿的尺寸
@@ -53,4 +53,4 @@
},
false
);
};</script><script src="js/vendors~app.b38bf4ff.js"></script><script src="js/app.2f662ea4.js"></script></body></html>
};</script><script src="js/vendors~app.be028cd5.js"></script><script src="js/app.d3bfe68a.js"></script></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -4,6 +4,7 @@ enum Api {
pendingList = '/health-emergency/emergency/firstAid/audit/pending',
passedList = '/health-emergency/emergency/firstAid/audit/passed',
rejectedList = '/health-emergency/emergency/firstAid/audit/rejected',
recordsList = '/health-emergency/emergency/firstAid/audit/records',
approve = '/health-emergency/emergency/firstAid/audit/approve',
reject = '/health-emergency/emergency/firstAid/audit/reject',
}
@@ -11,5 +12,6 @@ enum Api {
export const pendingListApi = (params: any) => defHttp.get({ url: Api.pendingList, params });
export const passedListApi = (params: any) => defHttp.get({ url: Api.passedList, params });
export const rejectedListApi = (params: any) => defHttp.get({ url: Api.rejectedList, params });
export const recordsListApi = (params: any) => defHttp.get({ url: Api.recordsList, params });
export const approveApi = (params: any) => defHttp.post({ url: Api.approve, params });
export const rejectApi = (params: any) => defHttp.post({ url: Api.reject, params });
+29 -10
View File
@@ -77,7 +77,16 @@
</a-tab-pane>
<a-tab-pane key="records" tab="审核记录">
<a-alert message="审核记录接口后端尚未提供独立列表,当前合并展示已通过+已驳回数据" type="info" show-icon style="margin-bottom: 12px" />
<div class="filter-bar">
<a-input v-model:value="recordsFilter.title" placeholder="搜索标题" allow-clear style="width: 200px" @press-enter="loadRecords" />
<a-select v-model:value="recordsFilter.auditStatus" placeholder="审核操作" allow-clear style="width: 140px">
<a-select-option :value="10">全部</a-select-option>
<a-select-option :value="11">已通过</a-select-option>
<a-select-option :value="12">已驳回</a-select-option>
</a-select>
<a-range-picker v-model:value="recordsFilter.dateRange" style="width: 240px" />
<a-button type="primary" @click="loadRecords">查询</a-button>
</div>
<a-table
:columns="recordColumns"
:data-source="recordsList"
@@ -88,8 +97,8 @@
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<a-tag :color="record._action === 'pass' ? 'green' : 'red'">
{{ record._action === 'pass' ? '通过' : '驳回' }}
<a-tag :color="record.auditStatus === 11 ? 'green' : 'red'">
{{ record.auditStatus === 11 ? '通过' : '驳回' }}
</a-tag>
</template>
</template>
@@ -110,9 +119,11 @@
pendingListApi,
passedListApi,
rejectedListApi,
recordsListApi,
approveApi,
rejectApi,
} from './check.api';
import dayjs from 'dayjs';
import { CONTENT_TYPE } from '/@/views/resource/resource.data';
import PreviewModal from '/@/views/resource/components/PreviewModal.vue';
import RejectReasonModal from './components/RejectReasonModal.vue';
@@ -135,6 +146,11 @@
const recordsList = ref<any[]>([]);
const recordsLoading = ref(false);
const recordsFilter = reactive({
title: '',
auditStatus: undefined as number | undefined,
dateRange: undefined as any,
});
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const [registerRejectModal, { openModal: openRejectModal }] = useModal();
@@ -228,13 +244,16 @@
async function loadRecords() {
recordsLoading.value = true;
try {
const [passed, rejected]: any = await Promise.all([
passedListApi({ pageNo: 1, pageSize: 100 }),
rejectedListApi({ pageNo: 1, pageSize: 100 }),
]);
const passedRecords = (passed?.records || []).map((item: any) => ({ ...item, _action: 'pass' }));
const rejectedRecords = (rejected?.records || []).map((item: any) => ({ ...item, _action: 'reject' }));
recordsList.value = [...passedRecords, ...rejectedRecords];
const params: any = {
pageNo: 1,
pageSize: 100,
title: recordsFilter.title || undefined,
auditStatus: recordsFilter.auditStatus ?? undefined,
startDate: recordsFilter.dateRange?.[0] ? dayjs(recordsFilter.dateRange[0]).format('YYYY-MM-DD') : undefined,
endDate: recordsFilter.dateRange?.[1] ? dayjs(recordsFilter.dateRange[1]).format('YYYY-MM-DD') : undefined,
};
const res: any = await recordsListApi(params);
recordsList.value = res?.records || [];
} finally {
recordsLoading.value = false;
}
@@ -0,0 +1,429 @@
<template>
<div class="firstaid-section">
<div class="firstaid-header">
<span class="firstaid-title">急救宣教推送</span>
<button
class="firstaid-push-all-btn"
:disabled="list.length === 0 || batchLoading"
@click="pushAll"
>
{{ batchLoading ? '推送中...' : '一键推送' }}
</button>
</div>
<div class="firstaid-search-wrap">
<input
v-model="keyword"
placeholder="搜索宣教内容、分类或关键词…"
@input="onSearch"
@keyup.enter="loadList"
/>
<button v-if="keyword" class="firstaid-search-clear" title="清空" @click="clearSearch">×</button>
</div>
<!-- 分类标签 -->
<div class="firstaid-tabs">
<span
class="firstaid-tab"
:class="{ active: !currentCategoryId }"
@click="switchCategory('')"
>
全部
</span>
<span
v-for="cat in categoryList"
:key="cat.id"
class="firstaid-tab"
:class="{ active: currentCategoryId === cat.id }"
@click="switchCategory(cat.id)"
>
{{ cat.name }}
</span>
</div>
<!-- 宣教列表 -->
<div class="firstaid-list">
<a-spin :spinning="loading">
<div v-for="item in list" :key="item.id" class="firstaid-item">
<div class="firstaid-item-info">
<div class="firstaid-item-title" :title="item.title">{{ item.title }}</div>
<div class="firstaid-item-meta">
<span class="firstaid-type-tag" :style="getTypeTagStyle(item.contentType)">
{{ getTypeLabel(item.contentType) }}
</span>
<span v-if="item.categoryName">{{ item.categoryName }}</span>
</div>
</div>
<button
class="firstaid-push-btn"
:class="{ pushed: item.pushed }"
:disabled="item.pushed || pushingId === item.id"
@click="pushSingle(item)"
>
{{ item.pushed ? '已推送' : '推送' }}
</button>
</div>
<div v-if="!loading && list.length === 0" class="firstaid-empty">未找到相关宣教内容</div>
</a-spin>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, type PropType, type Ref } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { pushResourceListApi, pushSendApi, pushBatchSendApi } from './firstAidPush.api';
import { categoryListAllApi } from '/@/views/resource/resource.api';
import { CONTENT_TYPE } from '/@/views/resource/resource.data';
const props = defineProps({
orderId: { type: String, default: '' },
iframeRef: { type: Object as PropType<Ref<HTMLIFrameElement>>, default: null },
hasInteractedWithIm: { type: Boolean, default: false },
});
const { createMessage } = useMessage();
const keyword = ref('');
const currentCategoryId = ref('');
const categoryList = ref<any[]>([]);
const list = ref<any[]>([]);
const loading = ref(false);
const pushingId = ref('');
const batchLoading = ref(false);
let searchTimer: any = null;
function getTypeLabel(type: string) {
return CONTENT_TYPE[type as keyof typeof CONTENT_TYPE]?.label || type;
}
function getTypeTagStyle(type: string) {
const color = CONTENT_TYPE[type as keyof typeof CONTENT_TYPE]?.color || 'default';
const colorMap: Record<string, string> = {
blue: '#1890ff',
red: '#f5222d',
orange: '#fa8c16',
default: '#8c8c8c',
};
const c = colorMap[color] || colorMap.default;
return {
color: c,
borderColor: c,
};
}
async function loadList() {
loading.value = true;
try {
const res: any = await pushResourceListApi({
orderId: props.orderId || undefined,
keyword: keyword.value || undefined,
categoryId: currentCategoryId.value || undefined,
pageNo: 1,
pageSize: 50,
});
list.value = res?.records || [];
} finally {
loading.value = false;
}
}
function onSearch() {
if (searchTimer) clearTimeout(searchTimer);
searchTimer = setTimeout(loadList, 300);
}
function clearSearch() {
keyword.value = '';
loadList();
}
function switchCategory(id: string) {
currentCategoryId.value = id;
loadList();
}
// 通过 IM iframe 的 postMessage 发送自定义消息到当前会话
// 复用 iweb 端 message-custom.vue 中 businessID='system' 的卡片渲染
function postMessageToIm(item: any) {
// Vue 3 中父组件传 ref 给子组件 prop 会自动解包,props.iframeRef 已是 iframe DOM 元素
const iframe = props.iframeRef as any;
if (!iframe?.contentWindow) {
return false;
}
const payload = {
code: 'custom',
data: {
businessID: 'system',
title: item.title,
messageImageUrl: item.coverImage || '',
content: item.summary || '',
// 唯一时间戳,避免 IM SDK 对相同 payload 去重导致重复推送不显示
_ts: Date.now(),
},
};
iframe.contentWindow.postMessage(JSON.stringify(payload), '*');
return true;
}
async function pushSingle(item: any) {
if (!props.hasInteractedWithIm) {
createMessage.warning('请先在左侧选择会话');
return;
}
if (!props.orderId) {
createMessage.warning('当前会话未关联工单,无法记录推送状态');
return;
}
pushingId.value = item.id;
try {
// 1. 调后端接口写入推送记录(后端会触发推送通知给目标用户)
await pushSendApi({ resourceId: item.id, orderId: props.orderId });
// 2. 同时通过 IM iframe 推送卡片到当前会话(即时展示)
postMessageToIm(item);
item.pushed = true;
createMessage.success(`已推送:${item.title}`);
} finally {
pushingId.value = '';
}
}
async function pushAll() {
if (!props.hasInteractedWithIm) {
createMessage.warning('请先在左侧选择会话');
return;
}
if (!props.orderId) {
createMessage.warning('当前会话未关联工单,无法记录推送状态');
return;
}
const unpushed = list.value.filter((item) => !item.pushed);
if (unpushed.length === 0) {
createMessage.info('当前列表已全部推送');
return;
}
batchLoading.value = true;
try {
// 1. 调后端接口批量推送(后端按条件查出资源并触发推送通知)
await pushBatchSendApi({
orderId: props.orderId,
resourceIds: unpushed.map((item) => item.id),
});
// 2. 同时通过 IM iframe 推送卡片到当前会话(即时展示)
for (const item of unpushed) {
postMessageToIm(item);
await new Promise((resolve) => setTimeout(resolve, 100));
}
list.value.forEach((item) => (item.pushed = true));
createMessage.success(`已推送 ${unpushed.length} 条宣教内容`);
} finally {
batchLoading.value = false;
}
}
watch(
() => props.orderId,
() => {
// 切换对话后重新拉取,刷新每条资源的 pushed 状态
loadList();
}
);
onMounted(async () => {
try {
categoryList.value = (await categoryListAllApi()) || [];
} catch {
categoryList.value = [];
}
loadList();
});
</script>
<style lang="less" scoped>
.firstaid-section {
margin: 10px 15px 0 15px;
border-top: 1px solid #e8e8e8;
padding-top: 10px;
}
.firstaid-header {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
}
.firstaid-title {
color: rgba(0, 0, 0, 0.85);
}
.firstaid-push-all-btn {
font-size: 12px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
padding: 3px 10px;
cursor: pointer;
transition: background 0.2s;
&:hover:not(:disabled) {
background: #096dd9;
}
&:disabled {
background: #d9d9d9;
color: rgba(255, 255, 255, 0.8);
cursor: not-allowed;
}
}
.firstaid-search-wrap {
display: flex;
align-items: center;
margin-bottom: 8px;
background: #f7f7f7;
border-radius: 6px;
padding: 0 8px;
border: 1px solid #ddd;
input {
flex: 1;
border: none;
background: transparent;
outline: none;
font-size: 13px;
padding: 6px 0;
color: #333;
&::placeholder {
color: #bbb;
}
}
}
.firstaid-search-clear {
border: none;
background: none;
cursor: pointer;
padding: 0;
color: #bbb;
font-size: 18px;
line-height: 1;
&:hover {
color: #999;
}
}
.firstaid-tabs {
display: flex;
gap: 6px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.firstaid-tab {
font-size: 11px;
padding: 2px 8px;
border-radius: 12px;
border: 1px solid #d9d9d9;
background: #fff;
cursor: pointer;
color: #666;
transition: all 0.2s;
user-select: none;
&:hover {
border-color: #1890ff;
color: #1890ff;
}
&.active {
background: #1890ff;
color: #fff;
border-color: #1890ff;
}
}
.firstaid-list {
max-height: 260px;
overflow-y: auto;
border: 1px solid #e8e8e8;
border-radius: 6px;
background: #fafafa;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 2px;
}
}
.firstaid-item {
display: flex;
align-items: center;
padding: 7px 10px;
border-bottom: 1px solid #f0f0f0;
font-size: 12px;
background: #fff;
transition: background 0.15s;
&:last-child {
border-bottom: none;
}
&:hover {
background: #f0f7ff;
}
}
.firstaid-item-info {
flex: 1;
overflow: hidden;
}
.firstaid-item-title {
font-weight: 500;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px;
}
.firstaid-item-meta {
color: #999;
font-size: 11px;
margin-top: 2px;
display: flex;
align-items: center;
gap: 4px;
}
.firstaid-type-tag {
border-radius: 3px;
padding: 0 4px;
font-size: 10px;
border: 1px solid;
background: #fff;
}
.firstaid-push-btn {
flex-shrink: 0;
margin-left: 8px;
background: #52c41a;
color: white;
border: none;
border-radius: 4px;
padding: 3px 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
min-width: 42px;
&:hover:not(:disabled) {
background: #389e0d;
}
&:disabled {
background: #b7eb8f;
color: #389e0d;
cursor: default;
}
}
.firstaid-empty {
text-align: center;
padding: 20px;
color: #bbb;
font-size: 13px;
}
</style>
@@ -0,0 +1,19 @@
import { defHttp } from '/@/utils/http/axios';
/**
* 急救宣教推送 - 应急工单场景下推送给求助者
*/
enum Api {
resourceList = '/health-emergency/emergency/firstAid/push/resourceList',
send = '/health-emergency/emergency/firstAid/push/send',
batchSend = '/health-emergency/emergency/firstAid/push/batchSend',
}
// 可推送资源列表(只返回已发布+审核通过+时间有效,每条带 pushed 字段)
export const pushResourceListApi = (params: any) => defHttp.get({ url: Api.resourceList, params });
// 推送单条 { resourceId, orderId }
export const pushSendApi = (params: any) => defHttp.post({ url: Api.send, params }, { successNeedMessage: false });
// 批量推送 { resourceIds: [], orderId } 或 { orderId, filterParams: {...} }
export const pushBatchSendApi = (params: any) => defHttp.post({ url: Api.batchSend, params }, { successNeedMessage: false });
@@ -153,16 +153,18 @@
</a-row>
<forHelpAdv :isSpecialized="true" ref="forHelpAdvRef" />
<detail ref="detailRef" />
<FirstAidPush :orderId="orderId" :iframeRef="iframeRef" :hasInteractedWithIm="hasInteractedWithIm" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import { Dayjs } from 'dayjs';
import detail from './detail.vue';
import { stat, scheduleRecordApi, scheduleRecordNewApi } from './commApi';
import forHelpAdv from './forHelpAdv.vue';
import FirstAidPush from './FirstAidPush.vue';
import moment from 'moment';
import qs from 'qs';
import { imAddressSrc } from '/@/utils/imAddressSrc';
@@ -175,6 +177,20 @@
});
const route = useRoute();
// 当前应急工单IDIM 会话 sessionId),用于急救宣教推送
// 初始值取 URL 中的 sessionId;后续用户在 IM 内切换会话时由 postMessage 更新
const orderId = ref<string>((route.query?.sessionId as string) || '');
// 用户是否已与 IM iframe 交互(粗略判断是否已选会话)
// 跨域 iframe 内部点击事件无法冒泡到父页面,用 window blur + activeElement 近似判断
const hasInteractedWithIm = ref(false);
onMounted(() => {
window.addEventListener('blur', () => {
if (document.activeElement && (document.activeElement as HTMLElement).tagName === 'IFRAME') {
hasInteractedWithIm.value = true;
}
});
});
moment.updateLocale('en', {
weekdaysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
@@ -184,6 +200,7 @@
const statInfo = ref<Object>({});
const dayS = ref<Array>();
const loadingVisible = ref<Boolean>(true); // loading
const iframeRef = ref<HTMLIFrameElement>();
const forHelpDetail = (res) => {
detailRef.value.showDrawer(res);
};
@@ -217,6 +234,10 @@
case 'forHelpDetail':
forHelpDetail(result.userId);
break;
case 'sessionChanged':
// 用户在 IM 内切换会话,更新当前工单 ID
orderId.value = result.orderId || '';
break;
case 'closeLoading':
closeLoading();
break;
@@ -0,0 +1,103 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="520" :minHeight="300" @ok="handleSubmit">
<a-form layout="vertical">
<a-form-item label="分类名称" required>
<a-input v-model:value="formState.name" placeholder="请输入分类名称" :maxlength="50" />
</a-form-item>
<a-form-item label="父级分类">
<a-tree-select
v-model:value="formState.parentId"
:tree-data="categoryTreeData"
:field-names="{ label: 'name', value: 'id', children: 'children' }"
placeholder="顶级分类(不选)"
allow-clear
tree-default-expand-all
:disabled="!!formState.id"
/>
</a-form-item>
<a-form-item label="排序号">
<a-input-number v-model:value="formState.sortNo" :min="0" style="width: 100%" />
</a-form-item>
<a-form-item label="描述">
<a-textarea v-model:value="formState.description" :rows="3" placeholder="可选" :maxlength="200" />
</a-form-item>
</a-form>
</BasicModal>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { categoryListAllApi, categoryAddApi, categoryEditApi, buildTree } from '../label.api';
const emit = defineEmits(['success', 'register']);
const { createMessage } = useMessage();
const title = ref('新增分类');
const categoryTreeData = ref<any[]>([]);
const formState = reactive({
id: '',
name: '',
parentId: undefined as string | undefined,
sortNo: 1,
description: '',
});
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
const isUpdate = data?.isUpdate;
title.value = isUpdate ? '编辑分类' : '新增分类';
formState.id = data?.record?.id || '';
formState.name = data?.record?.name || '';
formState.parentId = data?.record?.parentId || (data?.parentId || undefined);
formState.sortNo = data?.record?.sortNo ?? 1;
formState.description = data?.record?.description || '';
// 编辑时父级分类禁用(避免循环引用)
if (isUpdate) {
formState.parentId = data?.record?.parentId || undefined;
}
await loadCategoryTree();
});
async function loadCategoryTree() {
const list = (await categoryListAllApi()) || [];
categoryTreeData.value = buildTree(list);
}
function validate(): boolean {
if (!formState.name) {
createMessage.warning('请输入分类名称');
return false;
}
return true;
}
async function handleSubmit() {
if (!validate()) return;
setModalProps({ confirmLoading: true });
try {
const params = {
id: formState.id || undefined,
name: formState.name,
parentId: formState.parentId || undefined,
sortNo: formState.sortNo,
description: formState.description,
};
if (formState.id) {
await categoryEditApi(params);
createMessage.success('已更新分类');
} else {
await categoryAddApi(params);
createMessage.success('已新增分类');
}
emit('success');
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
onMounted(() => {
loadCategoryTree();
});
</script>
@@ -0,0 +1,74 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="480" :minHeight="260" @ok="handleSubmit">
<a-form layout="vertical">
<a-form-item label="标签名称" required>
<a-input v-model:value="formState.name" placeholder="请输入标签名称" :maxlength="20" />
</a-form-item>
<a-form-item label="排序号">
<a-input-number v-model:value="formState.sortNo" :min="0" style="width: 100%" />
</a-form-item>
<a-form-item label="描述">
<a-textarea v-model:value="formState.description" :rows="3" placeholder="可选" :maxlength="200" />
</a-form-item>
</a-form>
</BasicModal>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { tagAddApi, tagEditApi } from '../label.api';
const emit = defineEmits(['success', 'register']);
const { createMessage } = useMessage();
const title = ref('新增标签');
const formState = reactive({
id: '',
name: '',
sortNo: 1,
description: '',
});
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
const isUpdate = data?.isUpdate;
title.value = isUpdate ? '编辑标签' : '新增标签';
formState.id = data?.record?.id || '';
formState.name = data?.record?.name || '';
formState.sortNo = data?.record?.sortNo ?? 1;
formState.description = data?.record?.description || '';
});
function validate(): boolean {
if (!formState.name) {
createMessage.warning('请输入标签名称');
return false;
}
return true;
}
async function handleSubmit() {
if (!validate()) return;
setModalProps({ confirmLoading: true });
try {
const params = {
id: formState.id || undefined,
name: formState.name,
sortNo: formState.sortNo,
description: formState.description,
};
if (formState.id) {
await tagEditApi(params);
createMessage.success('已更新标签');
} else {
await tagAddApi(params);
createMessage.success('已新增标签');
}
emit('success');
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
+28 -122
View File
@@ -22,9 +22,10 @@
block-node
@select="onSelectCategory"
>
<template #title="{ name, id, status }">
<template #title="{ name, id, status, resourceCount }">
<div class="tree-node-content">
<span class="tree-name">{{ name }}</span>
<span v-if="resourceCount != null" class="tree-count">({{ resourceCount }})</span>
<a-tag v-if="status === 2" color="red" style="margin-left: 4px">冻结</a-tag>
<span class="tree-actions">
<a-button type="link" size="small" @click.stop="handleEditCategory({ id, name })"></a-button>
@@ -61,6 +62,7 @@
@close.prevent="handleDeleteTag(tag)"
>
{{ tag.name }}
<span v-if="tag.resourceCount != null" class="tag-count">{{ tag.resourceCount }}</span>
</a-tag>
<a-empty v-if="filteredTags.length === 0 && !tagLoading" description="暂无标签" />
</div>
@@ -68,7 +70,7 @@
</a-card>
<a-card title="分类权限配置" :bordered="false" class="card-section">
<a-alert message="权限矩阵接口后端尚未提供,当前为占位展示" type="info" show-icon style="margin-bottom: 12px" />
<!-- <a-alert message="权限矩阵接口后端尚未提供,当前为占位展示" type="info" show-icon style="margin-bottom: 12px" />-->
<a-table
:columns="permissionColumns"
:data-source="permissionList"
@@ -89,50 +91,9 @@
</a-row>
<!-- 分类新增/编辑弹窗 -->
<a-modal
v-model:open="categoryModalVisible"
:title="categoryModalTitle"
@ok="handleCategorySubmit"
:width="480"
:confirm-loading="categorySubmitting"
>
<a-form layout="vertical">
<a-form-item label="分类名称" required>
<a-input v-model:value="categoryForm.name" placeholder="请输入分类名称" />
</a-form-item>
<a-form-item label="父级分类">
<a-tree-select
v-model:value="categoryForm.parentId"
:tree-data="categoryTreeSelectData"
:field-names="{ label: 'name', value: 'id', children: 'children' }"
placeholder="顶级分类(不选)"
allow-clear
tree-default-expand-all
/>
</a-form-item>
<a-form-item label="排序号">
<a-input-number v-model:value="categoryForm.sortNo" :min="0" style="width: 100%" />
</a-form-item>
<a-form-item label="描述">
<a-textarea v-model:value="categoryForm.description" :rows="2" placeholder="可选" />
</a-form-item>
</a-form>
</a-modal>
<!-- 标签新增弹窗 -->
<a-modal v-model:open="tagModalVisible" title="新增标签" @ok="handleTagSubmit" :width="480" :confirm-loading="tagSubmitting">
<a-form layout="vertical">
<a-form-item label="标签名称" required>
<a-input v-model:value="tagForm.name" placeholder="请输入标签名称" />
</a-form-item>
<a-form-item label="排序号">
<a-input-number v-model:value="tagForm.sortNo" :min="0" style="width: 100%" />
</a-form-item>
<a-form-item label="描述">
<a-textarea v-model:value="tagForm.description" :rows="2" placeholder="可选" />
</a-form-item>
</a-form>
</a-modal>
<CategoryFormModal @register="registerCategoryModal" @success="loadCategoryTree" />
<!-- 标签新增/编辑弹窗 -->
<TagFormModal @register="registerTagModal" @success="loadTags" />
</div>
</template>
@@ -140,20 +101,22 @@
import { ref, reactive, computed, onMounted } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { useModal } from '/@/components/Modal';
import {
categoryListAllApi,
categoryQueryByIdApi,
categoryAddApi,
categoryEditApi,
categoryDeleteApi,
tagListAllApi,
tagAddApi,
tagDeleteApi,
buildTree,
} from './label.api';
import CategoryFormModal from './components/CategoryFormModal.vue';
import TagFormModal from './components/TagFormModal.vue';
const { createMessage } = useMessage();
const [registerCategoryModal, { openModal: openCategoryModal }] = useModal();
const [registerTagModal, { openModal: openTagModal }] = useModal();
// ============ 分类树 ============
const categoryList = ref<any[]>([]);
const categoryLoading = ref(false);
@@ -188,29 +151,15 @@
}
function handleAddCategory(parentId?: string) {
categoryModalTitle.value = '新增分类';
categoryForm.id = '';
categoryForm.name = '';
categoryForm.parentId = parentId || undefined;
categoryForm.sortNo = 1;
categoryForm.description = '';
categoryModalVisible.value = true;
openCategoryModal(true, { isUpdate: false, parentId });
}
function handleAddSubCategory(parentId: string) {
handleAddCategory(parentId);
openCategoryModal(true, { isUpdate: false, parentId });
}
async function handleEditCategory(record: any) {
// 拉详情获取完整字段
const detail = await categoryQueryByIdApi({ id: record.id }).catch(() => null);
categoryModalTitle.value = '编辑分类';
categoryForm.id = detail?.id || record.id;
categoryForm.name = detail?.name || record.name;
categoryForm.parentId = detail?.parentId || undefined;
categoryForm.sortNo = detail?.sortNo ?? 1;
categoryForm.description = detail?.description || '';
categoryModalVisible.value = true;
function handleEditCategory(record: any) {
openCategoryModal(true, { isUpdate: true, record });
}
function handleDeleteCategory(record: any) {
@@ -220,35 +169,6 @@
});
}
async function handleCategorySubmit() {
if (!categoryForm.name) {
createMessage.warning('请输入分类名称');
return;
}
categorySubmitting.value = true;
try {
const payload = {
id: categoryForm.id || undefined,
name: categoryForm.name,
parentId: categoryForm.parentId,
sortNo: categoryForm.sortNo,
description: categoryForm.description,
status: 1,
};
if (categoryForm.id) {
await categoryEditApi(payload);
createMessage.success('已更新分类');
} else {
await categoryAddApi(payload);
createMessage.success('已新增分类');
}
categoryModalVisible.value = false;
loadCategoryTree();
} finally {
categorySubmitting.value = false;
}
}
// ============ 标签 ============
const tagList = ref<any[]>([]);
const tagLoading = ref(false);
@@ -269,10 +189,7 @@
}
function handleAddTag() {
tagForm.name = '';
tagForm.sortNo = 1;
tagForm.description = '';
tagModalVisible.value = true;
openTagModal(true, { isUpdate: false });
}
function handleDeleteTag(tag: any) {
@@ -282,27 +199,6 @@
});
}
async function handleTagSubmit() {
if (!tagForm.name) {
createMessage.warning('请输入标签名称');
return;
}
tagSubmitting.value = true;
try {
await tagAddApi({
name: tagForm.name,
sortNo: tagForm.sortNo,
description: tagForm.description,
status: 1,
});
createMessage.success('已新增标签');
tagModalVisible.value = false;
loadTags();
} finally {
tagSubmitting.value = false;
}
}
// ============ 权限矩阵(mock,后端未提供接口) ============
const permissionList = ref<any[]>([
{ role: '系统管理员', permissions: ['rw', 'rw', 'rw', 'rw', 'rw'] },
@@ -400,6 +296,11 @@
.tree-name {
flex: 1;
}
.tree-count {
font-size: 11px;
color: #999;
margin-left: 4px;
}
.tree-actions {
display: none;
margin-left: auto;
@@ -419,4 +320,9 @@
padding: 4px 14px;
cursor: default;
}
.tag-count {
opacity: 0.5;
font-size: 11px;
margin-left: 4px;
}
</style>
@@ -20,7 +20,7 @@
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="所属分类" required>
<a-select v-model:value="formState.categoryId" :options="categoryOptions" placeholder="请选择分类" />
<a-select v-model:value="formState.categoryId" :options="categoryOptions" placeholder="请选择分类" :field-names="{ label: 'name', value: 'id' }" />
</a-form-item>
</a-col>
<a-col :span="12">
@@ -40,25 +40,28 @@
/>
</a-form-item>
<a-form-item label="正文内容" required>
<a-textarea v-model:value="formState.content" :rows="5" placeholder="请输入急救知识内容,支持富文本编辑..." />
<JEditor v-model:value="formState.content" />
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="封面图">
<div class="upload-area">
<div class="upload-icon">🖼</div>
<div class="upload-text">点击或拖拽上传封面图</div>
<div class="upload-hint">支持 JPG/PNG建议尺寸 750×420px</div>
</div>
<JImageUpload
v-model:value="formState.coverImage"
bizPath="firstAid/cover"
text="上传封面图"
tipText="支持 JPG/PNG,建议尺寸 750×420px"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="附件/媒体文件">
<div class="upload-area">
<div class="upload-icon">📎</div>
<div class="upload-text">点击或拖拽上传文件</div>
<div class="upload-hint">支持 MP4/MP3/PDF单文件最大 500MB</div>
</div>
<JUpload
v-model:value="fileList"
bizPath="firstAid/file"
:multiple="true"
text="上传文件"
tipText="支持 MP4/MP3/PDF,单文件最大 500MB"
/>
</a-form-item>
</a-col>
</a-row>
@@ -90,12 +93,17 @@
import { useMessage } from '/@/hooks/web/useMessage';
import { TYPE_OPTIONS, SCENE_OPTIONS } from '../resource.data';
import { addApi, editApi, submitForReviewApi, categoryListAllApi, tagListAllApi } from '../resource.api';
import JImageUpload from '/@/components/Form/src/jeecg/components/JImageUpload.vue';
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
const emit = defineEmits(['success', 'register']);
const { createMessage } = useMessage();
const title = ref('新增急救宣教资源');
const categoryOptions = ref<any[]>([]);
const tagOptions = ref<any[]>([]);
// 附件列表(JUpload 用 array 绑定,提交时 JSON.stringify 为后端 fileUrl 字段)
const fileList = ref<any[]>([]);
const formState = reactive({
id: '',
@@ -123,6 +131,12 @@
if (formState.tagIdList && typeof (formState as any).tags === 'string') {
formState.tagIdList = ((formState as any).tags || '').split(',').filter(Boolean);
}
// 后端 fileUrl 是 JSON 数组字符串,前端需要 parse 成数组给 JUpload
try {
fileList.value = formState.fileUrl ? JSON.parse(formState.fileUrl) : [];
} catch {
fileList.value = [];
}
} else {
title.value = '新增急救宣教资源';
resetForm();
@@ -142,6 +156,7 @@
formState.fileUrl = '';
formState.effectiveTime = null;
formState.expiryTime = null;
fileList.value = [];
}
function buildPayload(): any {
@@ -155,7 +170,7 @@
tags: formState.tagIdList.join(','),
content: formState.content,
coverImage: formState.coverImage,
fileUrl: formState.fileUrl,
fileUrl: JSON.stringify(fileList.value || []),
effectiveTime: formState.effectiveTime,
expiryTime: formState.expiryTime,
};
@@ -1,6 +1,7 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="版本历史" :width="560" :minHeight="400" :showOkBtn="false" cancelText="关闭">
<a-spin :spinning="loading">
<BasicModal v-bind="$attrs" @register="registerModal" :title="modalTitle" :width="modalWidth" :minHeight="400" :showOkBtn="false" cancelText="关闭">
<!-- 版本列表视图 -->
<a-spin :spinning="loading" v-if="!diffVisible">
<div class="version-list">
<div v-for="(item, idx) in versions" :key="item.id" class="version-item">
<div class="version-num" :class="{ 'version-old': idx > 0 }">v{{ item.versionNo }}</div>
@@ -8,7 +9,7 @@
<div class="version-op">{{ item.changeDescription || item.title }}</div>
<div class="version-meta">
{{ item.createBy }} · {{ item.createTime }}
<a @click="handleDiff(item)">查看差异</a>
<a v-if="idx < versions.length - 1" @click="handleDiff(item, idx)">查看差异</a>
<a v-if="idx > 0" @click="handleRollback(item)">回滚</a>
</div>
</div>
@@ -16,22 +17,65 @@
<a-empty v-if="!loading && versions.length === 0" description="暂无版本记录" />
</div>
</a-spin>
<!-- 差异对比视图 -->
<div v-else>
<div class="diff-toolbar">
<a-button size="small" @click="diffVisible = false"> 返回版本列表</a-button>
<span class="diff-toolbar-tip">v{{ diffCurrentVersionNo }} v{{ diffOlderVersionNo }}</span>
</div>
<a-spin :spinning="diffLoading">
<a-row :gutter="16" v-if="diffData">
<a-col :span="12">
<div class="diff-panel diff-old">
<div class="diff-panel-title">
v{{ diffData.version1?.versionNo }} · {{ diffData.version1?.createTime }}
</div>
<div class="diff-panel-sub">{{ diffData.version1?.title }}</div>
<div class="diff-panel-desc">{{ diffData.version1?.changeDescription }}</div>
<div class="diff-panel-body" v-html="diffData.version1?.content || '无内容'"></div>
</div>
</a-col>
<a-col :span="12">
<div class="diff-panel diff-new">
<div class="diff-panel-title">
v{{ diffData.version2?.versionNo }} · {{ diffData.version2?.createTime }}
</div>
<div class="diff-panel-sub">{{ diffData.version2?.title }}</div>
<div class="diff-panel-desc">{{ diffData.version2?.changeDescription }}</div>
<div class="diff-panel-body" v-html="diffData.version2?.content || '无内容'"></div>
</div>
</a-col>
</a-row>
<a-empty v-else description="无差异数据" />
</a-spin>
</div>
</BasicModal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ref, computed } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { versionListApi, versionRollbackApi } from '../resource.api';
import { versionListApi, versionRollbackApi, versionDiffApi } from '../resource.api';
const { createMessage, createConfirm } = useMessage();
const record = ref<any>({});
const versions = ref<any[]>([]);
const loading = ref(false);
const diffVisible = ref(false);
const diffLoading = ref(false);
const diffData = ref<any>(null);
const diffCurrentVersionNo = ref('');
const diffOlderVersionNo = ref('');
const modalTitle = computed(() => (diffVisible.value ? '版本差异对比' : '版本历史'));
const modalWidth = computed(() => (diffVisible.value ? 900 : 560));
const [registerModal] = useModalInner(async (data) => {
record.value = data?.record || {};
diffVisible.value = false;
await loadVersions();
});
@@ -46,8 +90,39 @@
}
}
function handleDiff(item: any) {
createMessage.info(`查看差异:v${item.versionNo}`);
async function handleDiff(item: any, idx: number) {
const older = versions.value[idx + 1];
if (!older) {
createMessage.info('无更早版本可对比');
return;
}
diffCurrentVersionNo.value = item.versionNo;
diffOlderVersionNo.value = older.versionNo;
diffVisible.value = true;
diffLoading.value = true;
diffData.value = null;
try {
const res: any = await versionDiffApi({ versionId1: item.id, versionId2: older.id });
// 兼容后端不同返回结构:{version1, version2} / [v1, v2] / {old, new}
let v1: any = null;
let v2: any = null;
if (Array.isArray(res)) {
[v1, v2] = res;
} else if (res?.version1 || res?.version2) {
v1 = res.version1;
v2 = res.version2;
} else if (res?.old || res?.new) {
v1 = res.old;
v2 = res.new;
} else if (res?.result) {
const inner = res.result;
if (Array.isArray(inner)) [v1, v2] = inner;
else { v1 = inner.version1 || inner.old; v2 = inner.version2 || inner.new; }
}
diffData.value = { version1: v1, version2: v2 };
} finally {
diffLoading.value = false;
}
}
function handleRollback(item: any) {
@@ -56,7 +131,7 @@
title: '确认回滚',
content: `是否回滚至 v${item.versionNo}?回滚前会自动保存当前状态为新版本。`,
onOk: async () => {
await versionRollbackApi({ versionId: item.id });
await versionRollbackApi({ id: item.id });
createMessage.success('已回滚');
await loadVersions();
},
@@ -117,4 +192,61 @@
}
}
}
.diff-wrap {
margin-top: 8px;
}
.diff-toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.diff-toolbar-tip {
font-size: 13px;
color: #1890ff;
font-weight: 500;
}
.diff-panel {
border: 1px solid #f0f0f0;
border-radius: 2px;
padding: 12px;
max-height: 500px;
overflow-y: auto;
&.diff-old {
background: #fafafa;
}
&.diff-new {
background: #f6ffed;
border-color: #b7eb8f;
}
}
.diff-panel-title {
font-size: 13px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 6px;
}
.diff-panel-sub {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
margin-bottom: 4px;
}
.diff-panel-desc {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px dashed #e8e8e8;
}
.diff-panel-body {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
line-height: 1.8;
:deep(img) {
max-width: 100%;
}
}
</style>
+17 -3
View File
@@ -74,6 +74,7 @@
copyApi,
batchPublishApi,
batchTakeDownApi,
exportXlsApi,
} from './resource.api';
import ResourceFormModal from './components/ResourceFormModal.vue';
import PreviewModal from './components/PreviewModal.vue';
@@ -113,7 +114,7 @@
},
});
const [registerTable, { reload }] = tableContext;
const [registerTable, { reload, getForm }] = tableContext;
function onSelectChange(keys: string[]) {
selectedRowKeys.value = keys;
@@ -240,8 +241,21 @@
openFormModal(true, { isUpdate: false });
}
function handleExport() {
createMessage.info('导出功能待后端提供接口');
async function handleExport() {
const formValues = getForm().getFieldsValue() || {};
const params: any = {
title: formValues.title || undefined,
categoryId: formValues.categoryId || undefined,
contentType: formValues.contentType || undefined,
// combinedStatus 拆分为 status/auditStatus:≤9 查 status,≥10 查 auditStatus
status: formValues.combinedStatus != null && formValues.combinedStatus <= 9 ? formValues.combinedStatus : undefined,
auditStatus: formValues.combinedStatus != null && formValues.combinedStatus >= 10 ? formValues.combinedStatus : undefined,
// 勾选行优先导出指定资源
selections: selectedRowKeys.value.length > 0 ? selectedRowKeys.value.join(',') : undefined,
};
Object.keys(params).forEach((k) => params[k] === undefined && delete params[k]);
await exportXlsApi(params);
createMessage.success('导出成功');
}
async function handleOffline(record: Recordable) {
+23 -11
View File
@@ -1,5 +1,6 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import { downloadFile } from '/@/api/common/api';
const { createConfirm } = useMessage();
@@ -18,9 +19,11 @@ enum Api {
batchPublish = '/health-emergency/emergency/firstAid/resource/batchPublish',
incrementView = '/health-emergency/emergency/firstAid/resource/incrementView',
copy = '/health-emergency/emergency/firstAid/resource/copy',
exportXls = '/health-emergency/emergency/firstAid/resource/exportXls',
// 版本
versionList = '/health-emergency/emergency/firstAid/version/list',
versionRollback = '/health-emergency/emergency/firstAid/version/rollback',
versionDiff = '/health-emergency/emergency/firstAid/version/diff',
// 分类 & 标签(下拉用)
categoryListAll = '/health-emergency/emergency/firstAid/category/listAll',
tagListAll = '/health-emergency/emergency/firstAid/tag/listAll',
@@ -28,17 +31,26 @@ enum Api {
export const listApi = (params: any) => defHttp.get({ url: Api.list, params });
export const queryByIdApi = (params: any) => defHttp.get({ url: Api.queryById, params });
export const addApi = (params: any) => defHttp.post({ url: Api.add, params });
export const editApi = (params: any) => defHttp.post({ url: Api.edit, params });
export const submitForReviewApi = (params: any) => defHttp.post({ url: Api.submitForReview, params });
export const takeDownApi = (params: any) => defHttp.post({ url: Api.takeDown, params });
export const publishApi = (params: any) => defHttp.post({ url: Api.publish, params });
export const batchTakeDownApi = (params: any) => defHttp.post({ url: Api.batchTakeDown, params });
export const batchPublishApi = (params: any) => defHttp.post({ url: Api.batchPublish, params });
export const addApi = (params: any) => defHttp.post({ url: Api.add, params }, { successNeedMessage: false });
export const editApi = (params: any) => defHttp.post({ url: Api.edit, params }, { successNeedMessage: false });
export const submitForReviewApi = (params: any) => defHttp.post({ url: Api.submitForReview, params }, { successNeedMessage: false });
export const takeDownApi = (params: any) => defHttp.post({ url: Api.takeDown, params }, { successNeedMessage: false });
export const publishApi = (params: any) => defHttp.post({ url: Api.publish, params }, { successNeedMessage: false });
export const batchTakeDownApi = (params: any) => defHttp.post({ url: Api.batchTakeDown, params }, { successNeedMessage: false });
export const batchPublishApi = (params: any) => defHttp.post({ url: Api.batchPublish, params }, { successNeedMessage: false });
export const incrementViewApi = (params: any) => defHttp.post({ url: Api.incrementView, params });
export const copyApi = (params: any) => defHttp.post({ url: Api.copy, params });
export const copyApi = (params: any) => defHttp.post({ url: Api.copy, params }, { successNeedMessage: false });
/**
* 导出资源 Excel
* 同步导出,返回文件流。参数支持 selections(勾选ID,逗号分隔)+ 筛选条件
*/
export const exportXlsApi = (params: any) => {
return downloadFile(Api.exportXls, '急救宣教资源报表.xls', params);
};
export const versionListApi = (params: any) => defHttp.get({ url: Api.versionList, params });
export const versionRollbackApi = (params: any) => defHttp.post({ url: Api.versionRollback, params });
export const versionRollbackApi = (params: any) => defHttp.post({ url: Api.versionRollback, params }, { successNeedMessage: false });
export const versionDiffApi = (params: any) => defHttp.get({ url: Api.versionDiff, params });
export const categoryListAllApi = (params?: any) => defHttp.get({ url: Api.categoryListAll, params });
export const tagListAllApi = (params?: any) => defHttp.get({ url: Api.tagListAll, params });
@@ -50,7 +62,7 @@ export const deleteApi = (params: any, handleSuccess: any) => {
okText: '确认',
cancelText: '取消',
onOk: async () => {
await defHttp.post({ url: Api.delete, params });
await defHttp.post({ url: Api.delete, params }, { successNeedMessage: false });
handleSuccess();
},
});
@@ -64,7 +76,7 @@ export const batchDeleteApi = (params: any, handleSuccess: any) => {
okText: '确认',
cancelText: '取消',
onOk: async () => {
await defHttp.post({ url: Api.deleteBatch, params });
await defHttp.post({ url: Api.deleteBatch, params }, { successNeedMessage: false });
handleSuccess();
},
});