parent
2f7ecb60a0
commit
b1581fe3b5
|
@ -2,7 +2,7 @@
|
|||
VITE_USE_MOCK = true
|
||||
|
||||
# 发布路径
|
||||
VITE_PUBLIC_PATH = /
|
||||
VITE_PUBLIC_PATH = /nu
|
||||
|
||||
|
||||
# 跨域代理,您可以配置多个 ,请注意,没有换行符
|
||||
|
|
|
@ -3,6 +3,7 @@ import { message } from 'ant-design-vue';
|
|||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
const globSetting = useGlobSetting();
|
||||
const baseUploadUrl = globSetting.uploadUrl;
|
||||
const baseApiUrl = globSetting.apiUrl;
|
||||
enum Api {
|
||||
positionList = '/sys/position/list',
|
||||
userList = '/sys/user/list',
|
||||
|
@ -28,7 +29,7 @@ export const getNuList = (params) => {
|
|||
/**
|
||||
* 上传父路径
|
||||
*/
|
||||
export const uploadUrl = `${baseUploadUrl}/sys/common/upload`;
|
||||
export const uploadUrl = `${baseApiUrl}/sys/common/upload`;
|
||||
|
||||
/**
|
||||
* 职务列表
|
||||
|
|
|
@ -0,0 +1,815 @@
|
|||
<template>
|
||||
<div class="p-2 base-class">
|
||||
<a-card @click="toggleSearchStatus = true" :class="{ 'base-con-class': toggleSearchStatus == false }">
|
||||
<a-row v-show="!toggleSearchStatus && !orgSelectedCon">
|
||||
<a-col :span="10">
|
||||
<span style="font-size: 15px;font-weight: bold;color: #333;">机构信息:</span>
|
||||
<span v-if="!orgData" class="base-info-dn" style="color: red;"> 请先选择机构</span>
|
||||
<template v-else>
|
||||
<span class="base-info-dn"
|
||||
style="margin-left: 10px;font-size: 15px;font-weight: 400;color: #333;"> {{
|
||||
orgData?.departName }}</span>
|
||||
<a-button type="primary" size="small" @click.stop="handleResetOrg" style="margin-left: 10px;">
|
||||
<span class="base-info-dn">重新选择</span>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-col>
|
||||
<a-col :span="5">
|
||||
<span v-if="hasSearchConditions" style="font-size: 16px;font-weight: bold;color:#E86A6A">
|
||||
有筛选条件未重置
|
||||
</span>
|
||||
<span style="font-size: 16px;font-weight: bold;color:#ABACAD">点击展开更多操作区域</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="org-info-card" v-show="(!toggleSearchStatus && orgSelectedCon) || toggleSearchStatus">
|
||||
<div class="org-info-header">
|
||||
<div class="org-title">
|
||||
<span class="base-info-label">机构信息:</span>
|
||||
<span v-if="!orgData" class="base-info-dn" style="color: #ff4d4f;"> 请先选择机构</span>
|
||||
<span v-else class="org-name">{{ orgData?.departName }}</span>
|
||||
</div>
|
||||
<a-button v-if="!orgSelectedCon" class="reset-btn" type="primary" @click.stop="handleResetOrg">
|
||||
<span>重新选择</span>
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="org-info-content">
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="label">机构编码:</span>
|
||||
<span class="value">{{ orgData?.orgCode || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">运营时间:</span>
|
||||
<span class="value">
|
||||
{{ orgData?.operationStartTime?.substring(0, 10) || '-' }} 至
|
||||
{{ orgData?.operationEndTime?.substring(0, 10) || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">合同时间:</span>
|
||||
<span class="value">
|
||||
{{ orgData?.contractStartTime?.substring(0, 10) || '-' }} 至
|
||||
{{ orgData?.contractEndTime?.substring(0, 10) || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="label">电话:</span>
|
||||
<span class="value">{{ orgData?.mobile || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">传真:</span>
|
||||
<span class="value">{{ orgData?.fax || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">地址:</span>
|
||||
<span class="value">{{ orgData?.address || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider v-show="toggleSearchStatus && orgData && !orgSelectedCon" style="margin: 0px;margin-top: 8px;" />
|
||||
<a-col :span="24" v-show="toggleSearchStatus && orgData && !orgSelectedCon">
|
||||
<div class="jeecg-basic-table-form-container">
|
||||
<slot name="searchFormSlot"></slot>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-card>
|
||||
</div>
|
||||
<div class="p-2 con-class" style="margin-top: -10px;">
|
||||
<a-card>
|
||||
<a-card v-show="orgSelectedCon" :bordered="false" class="org-container">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :span="8" v-for="org in orgTable" :key="org.id">
|
||||
<div class="org-card" :class="{ 'active': org.id == orgData?.id }"
|
||||
@click="handleOrgSelected(org)">
|
||||
<div class="org-header">
|
||||
<a-tag color="blue" v-if="org.id == orgData?.id">当前选择</a-tag>
|
||||
<h3 class="org-title">{{ org.departName }}</h3>
|
||||
</div>
|
||||
|
||||
<a-descriptions size="default" :column="2" bordered>
|
||||
<a-descriptions-item label="机构编码">{{ org.orgCode || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="运营时间">
|
||||
{{ org.operationStartTime?.substring(0, 10) || '-' }} 至
|
||||
{{ org.operationEndTime?.substring(0, 10) || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="电话">
|
||||
<div class="contact-line">
|
||||
<span><phone-outlined /> {{ org.mobile || '-' }}</span>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="传真">
|
||||
<div class="contact-line">
|
||||
<span><printer-outlined /> {{ org.fax || '-' }}</span>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="地址">
|
||||
<ellipsis :length="26" tooltip>{{ org.address || '-' }}</ellipsis>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<a-card v-show="orgSelectedCon && (!orgTable || orgTable?.length == 0)">
|
||||
<a-empty description="暂无数据" :image="simpleImage" />
|
||||
</a-card>
|
||||
<div class="tab-header-container" v-show="!orgSelectedCon">
|
||||
<a-tabs v-model:activeKey="tabActiveKey" class="tabs-container">
|
||||
<a-tab-pane key="dataAsync" tab="数据同步">
|
||||
<a-card>
|
||||
<a-row>
|
||||
<a-col :span="sourceScreenSpan">
|
||||
<div style="height: 16px;margin-left: 15px;">
|
||||
<a-tag color="#87d068">源数据</a-tag>
|
||||
<a-radio-group v-model:value="viewType" size="small" style="margin-left: 10px;"
|
||||
@change="emit('viewTypeChanged', viewType)">
|
||||
<a-radio-button value="all">全部</a-radio-button>
|
||||
<a-radio-button value="selected">已选择</a-radio-button>
|
||||
<a-radio-button value="unselected">未选择</a-radio-button>
|
||||
</a-radio-group>
|
||||
<slot name="sourceTableSlot"></slot>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="selectedScreenSpan">
|
||||
<div style="height: 31px;margin-left: 4px;">
|
||||
<a-tag color="#2db7f5">
|
||||
已选择<span style="font-size: 13px;font-weight: bold;">{{ ' ' +
|
||||
Array.from(selectedItems.values()).length + ' ' }}</span>条
|
||||
</a-tag>
|
||||
</div>
|
||||
<a-card size="small" :bordered="false" class="selected-list-container">
|
||||
<slot name="businessTableSlot">111</slot>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="asyncHistory" tab="同步历史">
|
||||
<AsyncListComponent ref="asyncHistoryRef"></AsyncListComponent>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div class="toggle-button-container">
|
||||
<a-button type="primary" size="small" @click.stop="refreshHistory()" style="margin-right: 10px;"
|
||||
v-show="tabActiveKey == 'asyncHistory'">
|
||||
<span>刷新</span>
|
||||
</a-button>
|
||||
<a-button type="warning" size="small" @click.stop="handleAsync()"
|
||||
style="margin-left: 5px;margin-right: 5px;" v-show="tabActiveKey == 'dataAsync'">
|
||||
<span>同步数据</span>
|
||||
</a-button>
|
||||
<a-divider type="vertical" style="background-color: #CDCDCF" />
|
||||
<a-button type="primary" size="small" @click="handleSelectAll" v-show="tabActiveKey == 'dataAsync'"
|
||||
style="margin-left: 8px">全部添加</a-button>
|
||||
<a-divider type="vertical" style="background-color: #CDCDCF" v-show="tabActiveKey == 'dataAsync'" />
|
||||
<a-button type="error" size="small" @click="handleRemoveAll" v-show="tabActiveKey == 'dataAsync'"
|
||||
style="margin-left: 8px">全部移除</a-button>
|
||||
<a-divider type="vertical" style="background-color: #CDCDCF" v-show="tabActiveKey == 'dataAsync'" />
|
||||
<a-button type="primary" size="small" @click.stop="handleRestore"
|
||||
v-show="tabActiveKey == 'dataAsync'" style="margin-left: 8px">
|
||||
<span>还原数据</span>
|
||||
</a-button>
|
||||
<a-divider type="vertical" style="background-color: #CDCDCF" v-show="tabActiveKey == 'dataAsync'" />
|
||||
<a-radio-group v-model:value="splitVal" button-style="solid" size="small"
|
||||
@change="splitScreenChanged" style="margin-left: 5px;margin-right: 5px;"
|
||||
v-show="tabActiveKey == 'dataAsync'">
|
||||
<a-radio-button value="sc">源数据</a-radio-button>
|
||||
<a-radio-button value="sc2sed1">分屏1</a-radio-button>
|
||||
<a-radio-button value="sc1sed1">分屏2</a-radio-button>
|
||||
<a-radio-button value="sc1sed2">分屏3</a-radio-button>
|
||||
<a-radio-button value="sed">已选择</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-divider type="vertical" style="background-color: #CDCDCF" v-show="tabActiveKey == 'dataAsync'" />
|
||||
<a @click="toggleSearchStatus = !toggleSearchStatus" class="toggle-button"
|
||||
style="margin-left: 5px;margin-right: 5px;">
|
||||
{{ toggleSearchStatus ? '拉伸' : '压缩' }}
|
||||
<Icon
|
||||
:icon="toggleSearchStatus ? 'humbleicons:align-objects-top' : 'humbleicons:align-objects-bottom'" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<a-modal v-model:visible="showVideoModal" title="视频播放" :footer="null" @cancel="closeVideoModal"
|
||||
:bodyStyle="{ padding: '0', maxHeight: '80vh', overflow: 'auto' }">
|
||||
<video controls style="width: 100%; max-height: '70vh'; display: block; margin: 0 auto;">
|
||||
<source :src="videoUrl">
|
||||
您的浏览器不支持视频播放。
|
||||
</video>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="synchronization-directive" setup>
|
||||
import { ref, onMounted, computed, onBeforeMount, reactive } from 'vue';
|
||||
import { initDictOptions } from '/@/utils/dict';
|
||||
import { list, asyncFunc } from '@/views/services/serviceDirective/ConfigServiceDirective.api';
|
||||
import { queryDepartTreeSync } from '/@/api/common/api';
|
||||
import { Empty } from 'ant-design-vue';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { useMessage } from "/@/hooks/web/useMessage";
|
||||
import AsyncListComponent from '@/components/dataAsync/AsyncMainList.vue'
|
||||
|
||||
const props = defineProps({
|
||||
// showJMCom: { type: Boolean, default: false },
|
||||
});
|
||||
const emit = defineEmits(['changeShowJMCom', 'viewTypeChanged','sourceOrgCodeChanged']);
|
||||
|
||||
interface OrganizationData {
|
||||
id?: string;
|
||||
departName: string;
|
||||
orgCode: string;
|
||||
operationStartTime?: string;
|
||||
operationEndTime?: string;
|
||||
contractStartTime?: string;
|
||||
contractEndTime?: string;
|
||||
mobile?: string;
|
||||
fax?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const dictData = await initDictOptions('org_code')
|
||||
emit('sourceOrgCodeChanged',dictData.filter(d => d.text == 'shi_yan_tian')[0].value)
|
||||
emit('changeShowJMCom', true)
|
||||
})
|
||||
const initialDataIds = ref<Array<string | number>>([]);
|
||||
const { createMessage } = useMessage();
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
const toggleSearchStatus = ref<boolean>(true);
|
||||
const splitVal = ref<string>('sc1sed1');
|
||||
const sourceScreenSpan = ref(12);
|
||||
const selectedScreenSpan = ref(12);
|
||||
const orgTable = ref<OrganizationData[]>([]);
|
||||
const orgData = ref<OrganizationData | null>(null);
|
||||
const showVideoModal = ref(false);
|
||||
const videoUrl = ref('');
|
||||
const selectedItems = ref(new Map<string | number, any>());
|
||||
const listComRef = ref();
|
||||
const orgSelectedCon = ref(true)
|
||||
const tabActiveKey = ref('dataAsync')
|
||||
const asyncHistoryRef = ref(null)
|
||||
|
||||
const splitScreenChanged = (val) => {
|
||||
let v_ = val.target.value;
|
||||
if (v_ == 'sc') {
|
||||
sourceScreenSpan.value = 24;
|
||||
selectedScreenSpan.value = 0;
|
||||
}
|
||||
if (v_ == 'sc2sed1') {
|
||||
sourceScreenSpan.value = 16;
|
||||
selectedScreenSpan.value = 8;
|
||||
}
|
||||
if (v_ == 'sc1sed1') {
|
||||
sourceScreenSpan.value = 12;
|
||||
selectedScreenSpan.value = 12;
|
||||
}
|
||||
if (v_ == 'sc1sed2') {
|
||||
sourceScreenSpan.value = 8;
|
||||
selectedScreenSpan.value = 16;
|
||||
}
|
||||
if (v_ == 'sed') {
|
||||
sourceScreenSpan.value = 0;
|
||||
selectedScreenSpan.value = 24;
|
||||
}
|
||||
};
|
||||
|
||||
const closeVideoModal = () => {
|
||||
showVideoModal.value = false;
|
||||
videoUrl.value = '';
|
||||
};
|
||||
|
||||
const handleOrgSelected = async (org) => {
|
||||
if (!!orgData.value && orgData.value.id != org.id) {
|
||||
Modal.confirm({
|
||||
title: '变更机构提醒',
|
||||
content: '是否变更机构!',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await loadOrgData(org);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await loadOrgData(org);
|
||||
}
|
||||
};
|
||||
|
||||
const loadOrgData = async (org) => {
|
||||
try {
|
||||
// 清空当前选择
|
||||
selectedItems.value.clear();
|
||||
// 清空初始化数据
|
||||
initialDataIds.value = [];
|
||||
|
||||
// 加载新机构数据
|
||||
const res = await list({ dataSourceCode: org.orgCode, pageNo: 1, pageSize: -1 });
|
||||
|
||||
// 更新机构数据
|
||||
orgData.value = org;
|
||||
|
||||
// 如果有数据,更新右侧列表
|
||||
if (res.records && res.records.length > 0) {
|
||||
const newRecords = res.records;
|
||||
newRecords.forEach(record => {
|
||||
selectedItems.value.set(record.id, record);
|
||||
if (!orgData.value?.operationStartTime || new Date() >= new Date(orgData.value?.operationStartTime)) {
|
||||
initialDataIds.value.push(record.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 更新左侧列表的选中状态
|
||||
listComRef.value?.updateSelection?.(newRecords);
|
||||
} else {
|
||||
// 没有数据时也确保清空
|
||||
selectedItems.value.clear();
|
||||
listComRef.value?.updateSelection?.([]);
|
||||
}
|
||||
|
||||
// 切换视图
|
||||
orgSelectedCon.value = false;
|
||||
} catch (err) {
|
||||
console.error('机构数据查询失败:', err);
|
||||
createMessage.error('机构数据查询失败');
|
||||
// 失败时也确保清空状态
|
||||
selectedItems.value.clear();
|
||||
listComRef.value?.updateSelection?.([]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleResetOrg = () => {
|
||||
orgSelectedCon.value = true
|
||||
}
|
||||
|
||||
//数据同步
|
||||
const handleAsync = () => {
|
||||
Modal.confirm({
|
||||
title: '数据同步',
|
||||
content: '是否确认同步',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
let idStr = Array.from(selectedItems.value.keys()).join(',')
|
||||
if (!idStr) {
|
||||
createMessage.warning("未选择数据!");
|
||||
return
|
||||
}
|
||||
let izInc = true
|
||||
if (!!orgData.value?.operationStartTime) izInc = new Date() >= new Date(orgData.value?.operationStartTime)
|
||||
let params = {
|
||||
orgCode: orgData.value?.orgCode,
|
||||
izInc,
|
||||
idStr
|
||||
}
|
||||
await asyncFunc(params)
|
||||
|
||||
//处理右侧列表数据移除是否可用 如果已到运营时间则禁用
|
||||
if (!orgData.value?.operationStartTime || new Date() >= new Date(orgData.value?.operationStartTime)) {
|
||||
// 获取当前同步的ID数组
|
||||
const syncedIds = Array.from(selectedItems.value.keys());
|
||||
// 只添加initialDataIds中不存在的ID
|
||||
syncedIds.forEach(id => {
|
||||
if (!initialDataIds.value.includes(id)) {
|
||||
initialDataIds.value.push(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//刷新历史数据
|
||||
refreshHistory()
|
||||
createMessage.success("同步结果请在同步历史中查看!");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
//刷新同步历史数据
|
||||
const refreshHistory = () => {
|
||||
}
|
||||
|
||||
|
||||
//源数据查询参数
|
||||
const viewType = ref('all')
|
||||
|
||||
// 全部选择功能
|
||||
const handleSelectAll = async () => {
|
||||
// if (!orgData.value) {
|
||||
// createMessage.warning("请先选择机构");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// // 调用子组件的方法获取所有数据(不分页)
|
||||
// const allData = await listComRef.value?.getAllData?.(queryParam.value);
|
||||
|
||||
// if (allData && allData.length > 0) {
|
||||
// // 过滤掉已经存在的项
|
||||
// const newItems = allData.filter(item => !selectedItems.value.has(item.id));
|
||||
|
||||
// if (newItems.length === 0) {
|
||||
// createMessage.info("没有新的数据可以添加");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // 添加新项到已选择列表
|
||||
// newItems.forEach(item => {
|
||||
// selectedItems.value.set(item.id, item);
|
||||
// });
|
||||
|
||||
// // 更新子组件的选中状态
|
||||
// listComRef.value?.updateSelection?.(Array.from(selectedItems.value.values()));
|
||||
|
||||
// createMessage.success(`已添加 ${newItems.length} 条数据`);
|
||||
// } else {
|
||||
// createMessage.info("没有查询到数据");
|
||||
// }
|
||||
// } catch (error) {
|
||||
// createMessage.error("获取全部数据失败");
|
||||
// console.error("获取全部数据失败:", error);
|
||||
// }
|
||||
};
|
||||
|
||||
// 全部移除功能
|
||||
const handleRemoveAll = () => {
|
||||
if (selectedItems.value.size === 0) {
|
||||
createMessage.info("当前没有已选数据");
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认移除',
|
||||
content: '确定要移除所有已选数据吗?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
// 清空已选择的数据
|
||||
if (!orgData.value?.operationStartTime || new Date() >= new Date(orgData.value?.operationStartTime)) {
|
||||
// 创建新的Map只保留初始数据
|
||||
const newSelectedItems = new Map();
|
||||
// 遍历当前选择项,只保留初始数据
|
||||
selectedItems.value.forEach((value, key) => {
|
||||
if (initialDataIds.value.includes(key)) {
|
||||
newSelectedItems.set(key, value);
|
||||
}
|
||||
});
|
||||
selectedItems.value = newSelectedItems;
|
||||
listComRef.value?.updateSelection?.(Array.from(newSelectedItems.values()));
|
||||
} else {
|
||||
selectedItems.value.clear();
|
||||
listComRef.value?.updateSelection?.([]);
|
||||
}
|
||||
|
||||
|
||||
createMessage.success("已移除所有已选数据");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 还原功能
|
||||
const handleRestore = async () => {
|
||||
if (!orgData.value) {
|
||||
createMessage.warning("请先选择机构");
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认还原',
|
||||
content: '确定要将已选数据还原为机构初始状态吗?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
// 调用子组件方法重新加载数据
|
||||
await loadOrgData(orgData.value);
|
||||
createMessage.success("已成功还原数据");
|
||||
} catch (error) {
|
||||
console.error("还原数据失败:", error);
|
||||
createMessage.error("还原数据失败");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 计算当前搜索条件的函数
|
||||
const hasSearchConditions = computed(() => {
|
||||
return true
|
||||
// return Object.keys(queryParam.value).some(key =>
|
||||
// key !== 'viewType' && queryParam.value[key] !== undefined && queryParam.value[key] !== ''
|
||||
// );
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
queryDepartTreeSync({ platType: 'ywjg' }).then(res => {
|
||||
orgTable.value = res;
|
||||
});
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.base-con-class {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-2px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.selected-list-container {
|
||||
height: calc(100vh - 350px);
|
||||
overflow: auto;
|
||||
|
||||
:deep(.ant-table) {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 新增的机构卡片样式 */
|
||||
.org-cards-container {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.org-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
background: radial-gradient(circle at center, #c7e6ff 0%, #d4eeff 70%, #e4f0ff 100%);
|
||||
}
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 0 16px;
|
||||
min-height: 48px;
|
||||
|
||||
.ant-card-head-title {
|
||||
padding: 12px 0;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.org-info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
min-width: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
word-break: break-word;
|
||||
flex: 1;
|
||||
|
||||
&.address-text {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
position: relative;
|
||||
border: 1px solid #1890ff;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("@/assets/icons/success.svg") no-repeat center;
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-header-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
width: 100%;
|
||||
|
||||
:deep(.ant-tabs-content) {
|
||||
height: calc(100% - 40px); // 减去tab标题高度
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-button-container {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.con-class {
|
||||
:deep(.ant-card .ant-card-body) {
|
||||
// padding-left: 10px !important;
|
||||
// padding-right: 10px !important;
|
||||
// padding-bottom: 0px !important;
|
||||
// padding-bottom: 0px !important;
|
||||
padding: 3px 10px 3px 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.base-class {
|
||||
:deep(.ant-card .ant-card-body) {
|
||||
padding-top: 10px !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.con-class {
|
||||
:deep(.css-dev-only-do-not-override-9m98ij .ant-table-wrapper .ant-table-pagination.ant-pagination) {
|
||||
margin: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.org-info-card {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #d0eaff 100%);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
border: 1px solid #e8e8e8;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// &:hover {
|
||||
// box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
// transform: translateY(-1px);
|
||||
// }
|
||||
}
|
||||
|
||||
.org-info-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.org-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.base-info-label {
|
||||
font-weight: 600;
|
||||
color: #595959;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.org-name {
|
||||
margin-left: 10px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background-color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
|
||||
// &:hover {
|
||||
// background-color: #40a9ff;
|
||||
// border-color: #40a9ff;
|
||||
// }
|
||||
}
|
||||
|
||||
.org-info-content {
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex: 1;
|
||||
padding: 0 8px;
|
||||
|
||||
.label {
|
||||
color: #8c8c8c;
|
||||
font-size: 13px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #262626;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.org-container {
|
||||
background: transparent;
|
||||
|
||||
.org-card {
|
||||
padding: 16px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
background: white;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 2px 12px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border: 1px solid #1890ff;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
|
||||
.org-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.org-title {
|
||||
margin: 0 0 0 8px;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
>span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.anticon {
|
||||
margin-right: 4px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions) {
|
||||
.ant-descriptions-item-label {
|
||||
width: 80px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="media-detail">
|
||||
<a-descriptions bordered :column="1">
|
||||
<a-descriptions-item label="资源名称">{{ record.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="文件类型">{{ record.fileType }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注">{{ record.descr || '无' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="预览">
|
||||
<PreviewComponent
|
||||
:file-type="record.fileType"
|
||||
:file-path="record.filePath"
|
||||
/>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ record.createTime }}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">{{ record.updateTime }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineProps } from 'vue';
|
||||
import PreviewComponent from './PreviewPickerComponent.vue';
|
||||
|
||||
const props = defineProps({
|
||||
record: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const { record } = props;
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.media-detail {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,72 @@
|
|||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from "/@/hooks/web/useMessage";
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
enum Api {
|
||||
list = '/mediamanage/mediaManage/list',
|
||||
save='/mediamanage/mediaManage/add',
|
||||
edit='/mediamanage/mediaManage/edit',
|
||||
deleteOne = '/mediamanage/mediaManage/delete',
|
||||
deleteBatch = '/mediamanage/mediaManage/deleteBatch',
|
||||
importExcel = '/mediamanage/mediaManage/importExcel',
|
||||
exportXls = '/mediamanage/mediaManage/exportXls',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出api
|
||||
* @param params
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
/**
|
||||
* 导入api
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 删除单个
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const deleteOne = (params,handleSuccess) => {
|
||||
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const batchDelete = (params, handleSuccess) => {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或者更新
|
||||
* @param params
|
||||
* @param isUpdate
|
||||
*/
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params }, { isTransformResponse: false });
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:width="1000"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="resource-picker-container">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-area">
|
||||
<a-form layout="inline" :model="searchParams">
|
||||
<a-form-item label="资源名称">
|
||||
<a-input v-model:value="searchParams.name" placeholder="请输入资源名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="文件类型">
|
||||
<j-dict-select-tag
|
||||
v-model:value="searchParams.fileType"
|
||||
dictCode="file_type"
|
||||
placeholder="请选择文件类型"
|
||||
allowClear
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-area">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
rowKey="id"
|
||||
@change="handleTableChange"
|
||||
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'filePath'">
|
||||
<PreviewComponent
|
||||
:file-type="record.fileType"
|
||||
:file-path="record.filePath"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button type="link" @click="handleViewDetail(record)">详情</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<div class="footer-area">
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" @click="handleConfirm" :disabled="!selectedRowKeys.length" style="margin-left: 8px">
|
||||
确定
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="detailVisible"
|
||||
:title="detailTitle"
|
||||
:width="800"
|
||||
:footer="null"
|
||||
>
|
||||
<MediaDetail :record="currentRecord" />
|
||||
</a-modal>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, defineProps, defineEmits, watch } from 'vue';
|
||||
import { list } from './MediaManagePicker.api';
|
||||
import PreviewComponent from './PreviewComponent.vue';
|
||||
import MediaDetail from './MediaDetail.vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '选择媒体资源'
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'confirm']);
|
||||
|
||||
const visible = ref(props.visible);
|
||||
const loading = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const selectedRowKeys = ref([]);
|
||||
const selectedRows = ref([]);
|
||||
const detailVisible = ref(false);
|
||||
const currentRecord = ref({});
|
||||
const detailTitle = ref('资源详情');
|
||||
|
||||
const searchParams = reactive({
|
||||
name: '',
|
||||
fileType: ''
|
||||
});
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
pageSizeOptions: ['10', '20', '30', '50'],
|
||||
showTotal: total => `共 ${total} 条`
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '资源名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '文件类型',
|
||||
dataIndex: 'fileType',
|
||||
key: 'fileType'
|
||||
},
|
||||
{
|
||||
title: '预览',
|
||||
key: 'filePath'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action'
|
||||
}
|
||||
];
|
||||
|
||||
// 监听visible变化
|
||||
watch(() => props.visible, (val) => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
fetchData();
|
||||
}
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
...searchParams,
|
||||
pageNo: pagination.current,
|
||||
pageSize: pagination.pageSize
|
||||
};
|
||||
const res = await list(params);
|
||||
dataSource.value = res.records || [];
|
||||
pagination.total = res.total || 0;
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 表格变化
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
searchParams.name = '';
|
||||
searchParams.fileType = '';
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
// 选择变化
|
||||
const onSelectChange = (selectedKeys, selectedRows) => {
|
||||
selectedRowKeys.value = selectedKeys;
|
||||
selectedRows.value = selectedRows;
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (record) => {
|
||||
currentRecord.value = record;
|
||||
detailTitle.value = `资源详情 - ${record.name}`;
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', props.multiple ? selectedRows.value : selectedRows.value[0]);
|
||||
handleCancel();
|
||||
};
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
fetchData
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.resource-picker-container {
|
||||
padding: 10px;
|
||||
}
|
||||
.search-area {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.footer-area {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div class="preview-container">
|
||||
<!-- 图片预览 -->
|
||||
<div v-if="fileType === 'image' && filePath" class="preview-item">
|
||||
<a-image :width="200" :src="getFullPath(filePath)" />
|
||||
</div>
|
||||
|
||||
<!-- 视频预览 -->
|
||||
<div v-if="fileType === 'video' && filePath" class="preview-item">
|
||||
<video controls :src="getFullPath(filePath)" style="width: 200px; height: auto;"></video>
|
||||
</div>
|
||||
|
||||
<!-- 音频预览 -->
|
||||
<div v-if="fileType === 'audio' && filePath" class="preview-item">
|
||||
<audio controls :src="getFullPath(filePath)"></audio>
|
||||
</div>
|
||||
|
||||
<!-- 文档预览 -->
|
||||
<div v-if="fileType === 'document' && filePath" class="preview-item">
|
||||
<a-button @click="handlePreviewDocument">预览文档</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
|
||||
const props = defineProps({
|
||||
fileType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
filePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const getFullPath = (path) => {
|
||||
return getFileAccessHttpUrl(path);
|
||||
};
|
||||
|
||||
const handlePreviewDocument = () => {
|
||||
window.open(getFullPath(props.filePath), '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preview-container {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,72 @@
|
|||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from "/@/hooks/web/useMessage";
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
enum Api {
|
||||
list = '/mediamanage/mediaManage/list',
|
||||
save='/mediamanage/mediaManage/add',
|
||||
edit='/mediamanage/mediaManage/edit',
|
||||
deleteOne = '/mediamanage/mediaManage/delete',
|
||||
deleteBatch = '/mediamanage/mediaManage/deleteBatch',
|
||||
importExcel = '/mediamanage/mediaManage/importExcel',
|
||||
exportXls = '/mediamanage/mediaManage/exportXls',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出api
|
||||
* @param params
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
/**
|
||||
* 导入api
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 删除单个
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const deleteOne = (params,handleSuccess) => {
|
||||
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const batchDelete = (params, handleSuccess) => {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或者更新
|
||||
* @param params
|
||||
* @param isUpdate
|
||||
*/
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params }, { isTransformResponse: false });
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import {BasicColumn} from '/@/components/Table';
|
||||
import {FormSchema} from '/@/components/Table';
|
||||
import { rules} from '/@/utils/helper/validator';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
import { getWeekMonthQuarterYear } from '/@/utils';
|
||||
//列表数据
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '名称',
|
||||
align: "center",
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
align: "center",
|
||||
dataIndex: 'descr'
|
||||
},
|
||||
{
|
||||
title: '文件类型',
|
||||
align: "center",
|
||||
dataIndex: 'fileType_dictText'
|
||||
},
|
||||
{
|
||||
title: '文件预览',
|
||||
align: "center",
|
||||
dataIndex: 'filePath'
|
||||
},
|
||||
];
|
||||
|
||||
// 高级查询数据
|
||||
export const superQuerySchema = {
|
||||
name: {title: '名称',order: 0,view: 'text', type: 'string',},
|
||||
descr: {title: '备注',order: 1,view: 'textarea', type: 'string',},
|
||||
fileType: {title: '文件类型',order: 2,view: 'text', type: 'string',},
|
||||
filePath: {title: '文件预览',order: 3,view: 'text', type: 'string',},
|
||||
};
|
|
@ -0,0 +1,264 @@
|
|||
<template>
|
||||
<div class="p-2">
|
||||
<!--查询区域-->
|
||||
<div class="jeecg-basic-table-form-container">
|
||||
<a-form ref="formRef" @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol"
|
||||
:wrapper-col="wrapperCol">
|
||||
<a-row :gutter="24">
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="name">
|
||||
<template #label><span title="名称">名称</span></template>
|
||||
<JInput v-model:value="queryParam.name" placeholder="请输入名称"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="fileType">
|
||||
<template #label><span title="文件类型">文件类型</span></template>
|
||||
<j-dict-select-tag type='list' v-model:value="queryParam.fileType" dictCode="file_type"
|
||||
placeholder="请选择文件类型" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xl="6" :lg="7" :md="8" :sm="24">
|
||||
<span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
|
||||
<a-col :lg="6">
|
||||
<a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset"
|
||||
style="margin-left: 8px">重置</a-button>
|
||||
</a-col>
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" v-auth="'mediamanage:nu_media_manage:add'" @click="handleAdd"
|
||||
preIcon="ant-design:plus-outlined"> 新增</a-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button v-auth="'mediamanage:nu_media_manage:deleteBatch'">批量操作
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
<template v-slot:bodyCell="{ column, record, index, text }">
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!-- 表单区域 -->
|
||||
<MediaManageModal ref="registerModal" @success="handleSuccess"></MediaManageModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="mediamanage-mediaManage" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns, superQuerySchema } from './MediaManage.data';
|
||||
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './MediaManage.api';
|
||||
import { downloadFile } from '/@/utils/common/renderUtils';
|
||||
import MediaManageModal from './components/MediaManageModal.vue'
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
|
||||
import JSelectMultiple from '/@/components/Form/src/jeecg/components/JSelectMultiple.vue';
|
||||
import JInput from "/@/components/Form/src/jeecg/components/JInput.vue";
|
||||
|
||||
const formRef = ref();
|
||||
const queryParam = reactive<any>({});
|
||||
const toggleSearchStatus = ref<boolean>(false);
|
||||
const registerModal = ref();
|
||||
const userStore = useUserStore();
|
||||
//注册table数据
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
tableProps: {
|
||||
title: '媒体资源管理',
|
||||
api: list,
|
||||
columns,
|
||||
canResize: false,
|
||||
useSearchForm: false,
|
||||
actionColumn: {
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
},
|
||||
beforeFetch: async (params) => {
|
||||
return Object.assign(params, queryParam);
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: "媒体资源管理",
|
||||
url: getExportUrl,
|
||||
params: queryParam,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
success: handleSuccess
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
const labelCol = reactive({
|
||||
xs: 24,
|
||||
sm: 4,
|
||||
xl: 6,
|
||||
xxl: 4
|
||||
});
|
||||
const wrapperCol = reactive({
|
||||
xs: 24,
|
||||
sm: 20,
|
||||
});
|
||||
|
||||
// 高级查询配置
|
||||
const superQueryConfig = reactive(superQuerySchema);
|
||||
|
||||
/**
|
||||
* 高级查询事件
|
||||
*/
|
||||
function handleSuperQuery(params) {
|
||||
Object.keys(params).map((k) => {
|
||||
queryParam[k] = params[k];
|
||||
});
|
||||
searchQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
registerModal.value.disableSubmit = false;
|
||||
registerModal.value.add();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record: Recordable) {
|
||||
registerModal.value.disableSubmit = false;
|
||||
registerModal.value.edit(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
function handleDetail(record: Recordable) {
|
||||
registerModal.value.disableSubmit = true;
|
||||
registerModal.value.edit(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteOne({ id: record.id }, handleSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
function handleSuccess() {
|
||||
(selectedRowKeys.value = []) && reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: 'mediamanage:nu_media_manage:edit'
|
||||
}, {
|
||||
label: '详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
}, {
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
placement: 'topLeft',
|
||||
},
|
||||
auth: 'mediamanage:nu_media_manage:delete'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
function searchQuery() {
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function searchReset() {
|
||||
formRef.value.resetFields();
|
||||
selectedRowKeys.value = [];
|
||||
//刷新数据
|
||||
reload();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.jeecg-basic-table-form-container {
|
||||
padding: 0;
|
||||
|
||||
.table-page-search-submitButtons {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.query-group-cust {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
|
||||
.query-group-split-cust {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.ant-form-item:not(.ant-form-item-with-help) {
|
||||
margin-bottom: 16px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
:deep(.ant-picker),
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,237 @@
|
|||
<template>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<JFormContainer :disabled="disabled">
|
||||
<template #detail>
|
||||
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol"
|
||||
name="MediaManageForm">
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="名称" v-bind="validateInfos.name" id="MediaManageForm-name" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入名称" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="备注" v-bind="validateInfos.descr" id="MediaManageForm-descr" name="descr">
|
||||
<a-textarea v-model:value="formData.descr" :rows="4" placeholder="请输入备注" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="文件类型" v-bind="validateInfos.fileType" id="MediaManageForm-fileType" name="fileType">
|
||||
<j-dict-select-tag type='list' v-model:value="formData.fileType" dictCode="file_type"
|
||||
placeholder="请选择文件类型" allowClear @change="handleFileTypeChange" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="文件上传" v-bind="validateInfos.filePath" id="MediaManageForm-filePath">
|
||||
<!-- 未选择文件类型时的提示 -->
|
||||
<div v-if="!formData.fileType" class="upload-tip">
|
||||
<span style="color: red; line-height: 32px;">请先选择文件类型</span>
|
||||
</div>
|
||||
|
||||
<!-- 图片上传组件 -->
|
||||
<j-image-upload v-if="formData.fileType === 'image'" v-model:value="formData.filePath" :number="1"
|
||||
multiple="1" :fileSize="10" accept=".png,.jpg,.jpeg" @change="handleFileChange">
|
||||
</j-image-upload>
|
||||
|
||||
<!-- 视频上传组件 -->
|
||||
<template v-if="formData.fileType === 'video'">
|
||||
<j-upload v-model:value="formData.filePath" :fileSize="100" :multiple="false" :maxCount="1"
|
||||
accept=".mp4" @change="handleFileChange">
|
||||
</j-upload>
|
||||
<!-- 视频预览 -->
|
||||
<div v-if="formData.filePath" class="media-preview">
|
||||
<video controls style="max-width: 100%; max-height: 300px;">
|
||||
<source :src="getFileAccessHttpUrl(formData.filePath)" type="video/mp4">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 音频上传组件 -->
|
||||
<template v-if="formData.fileType === 'audio'">
|
||||
<j-upload v-model:value="formData.filePath" :fileSize="50" :multiple="false" :maxCount="1"
|
||||
accept=".mp3" @change="handleFileChange">
|
||||
</j-upload>
|
||||
<!-- 音频预览 -->
|
||||
<div v-if="formData.filePath" class="media-preview">
|
||||
<audio controls style="width: 100%;">
|
||||
<source :src="getFileAccessHttpUrl(formData.filePath)" type="audio/mpeg">
|
||||
您的浏览器不支持音频播放
|
||||
</audio>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 文档上传组件 -->
|
||||
<j-upload v-if="formData.fileType === 'document'" v-model:value="formData.filePath" :fileSize="20"
|
||||
:multiple="false" :maxCount="1" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt"
|
||||
@change="handleFileChange">
|
||||
</j-upload>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
</JFormContainer>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, defineExpose, nextTick, defineProps, computed } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getValueType } from '/@/utils';
|
||||
import { saveOrUpdate } from '../MediaManage.api';
|
||||
import { Form } from 'ant-design-vue';
|
||||
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
|
||||
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
|
||||
import JImageUpload from '/@/components/Form/src/jeecg/components/JImageUpload.vue';
|
||||
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
|
||||
const props = defineProps({
|
||||
formDisabled: { type: Boolean, default: false },
|
||||
formData: { type: Object, default: () => ({}) },
|
||||
formBpm: { type: Boolean, default: true }
|
||||
});
|
||||
const formRef = ref();
|
||||
const useForm = Form.useForm;
|
||||
const emit = defineEmits(['register', 'ok']);
|
||||
const formData = reactive<Record<string, any>>({
|
||||
id: '',
|
||||
name: '',
|
||||
descr: '',
|
||||
fileType: '',
|
||||
filePath: '',
|
||||
});
|
||||
const { createMessage } = useMessage();
|
||||
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
|
||||
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
//表单验证
|
||||
const validatorRules = reactive({
|
||||
fileType: [{ required: true, message: '请输入文件类型!' },],
|
||||
filePath: [{ required: true, message: '请输入文件预览!' },],
|
||||
});
|
||||
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
|
||||
|
||||
// 文件类型变化处理
|
||||
const handleFileTypeChange = () => {
|
||||
// 切换文件类型时清空已上传的文件
|
||||
formData.filePath = '';
|
||||
};
|
||||
|
||||
// 文件上传变化处理
|
||||
const handleFileChange = (value) => {
|
||||
formData.filePath = value;
|
||||
};
|
||||
|
||||
// 表单禁用
|
||||
const disabled = computed(() => {
|
||||
if (props.formBpm === true) {
|
||||
if (props.formData.disabled === false) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return props.formDisabled;
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function add() {
|
||||
edit({});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function edit(record) {
|
||||
nextTick(() => {
|
||||
resetFields();
|
||||
const tmpData = {};
|
||||
Object.keys(formData).forEach((key) => {
|
||||
if (record.hasOwnProperty(key)) {
|
||||
tmpData[key] = record[key]
|
||||
}
|
||||
})
|
||||
//赋值
|
||||
Object.assign(formData, tmpData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交数据
|
||||
*/
|
||||
async function submitForm() {
|
||||
try {
|
||||
// 触发表单验证
|
||||
await validate();
|
||||
} catch ({ errorFields }) {
|
||||
if (errorFields) {
|
||||
const firstField = errorFields[0];
|
||||
if (firstField) {
|
||||
formRef.value.scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
return Promise.reject(errorFields);
|
||||
}
|
||||
confirmLoading.value = true;
|
||||
const isUpdate = ref<boolean>(false);
|
||||
//时间格式化
|
||||
let model = formData;
|
||||
if (model.id) {
|
||||
isUpdate.value = true;
|
||||
}
|
||||
//循环数据
|
||||
for (let data in model) {
|
||||
//如果该数据是数组并且是字符串类型
|
||||
if (model[data] instanceof Array) {
|
||||
let valueType = getValueType(formRef.value.getProps, data);
|
||||
//如果是字符串类型的需要变成以逗号分割的字符串
|
||||
if (valueType === 'string') {
|
||||
model[data] = model[data].join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
await saveOrUpdate(model, isUpdate.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
createMessage.success(res.message);
|
||||
emit('ok');
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
add,
|
||||
edit,
|
||||
submitForm,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.antd-modal-form {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.media-preview {
|
||||
margin-top: 16px;
|
||||
padding: 8px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 4px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<j-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
|
||||
<MediaManageForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></MediaManageForm>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick, defineExpose } from 'vue';
|
||||
import MediaManageForm from './MediaManageForm.vue'
|
||||
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
|
||||
|
||||
const title = ref<string>('');
|
||||
const width = ref<number>(800);
|
||||
const visible = ref<boolean>(false);
|
||||
const disableSubmit = ref<boolean>(false);
|
||||
const registerForm = ref();
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function add() {
|
||||
title.value = '新增';
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
registerForm.value.add();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @param record
|
||||
*/
|
||||
function edit(record) {
|
||||
title.value = disableSubmit.value ? '详情' : '编辑';
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
registerForm.value.edit(record);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定按钮点击事件
|
||||
*/
|
||||
function handleOk() {
|
||||
registerForm.value.submitForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* form保存回调事件
|
||||
*/
|
||||
function submitCallback() {
|
||||
handleCancel();
|
||||
emit('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消按钮回调事件
|
||||
*/
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
add,
|
||||
edit,
|
||||
disableSubmit,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
/**隐藏样式-modal确定按钮 */
|
||||
.jee-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div class="preview-container">
|
||||
<!-- 图片预览 -->
|
||||
<div v-if="fileType === 'image' && filePath" class="preview-item">
|
||||
<a-image :width="200" :src="getFullPath(filePath)" />
|
||||
</div>
|
||||
|
||||
<!-- 视频预览 -->
|
||||
<div v-if="fileType === 'video' && filePath" class="preview-item">
|
||||
<video controls :src="getFullPath(filePath)" style="width: 200px; height: auto;"></video>
|
||||
</div>
|
||||
|
||||
<!-- 音频预览 -->
|
||||
<div v-if="fileType === 'audio' && filePath" class="preview-item">
|
||||
<audio controls :src="getFullPath(filePath)"></audio>
|
||||
</div>
|
||||
|
||||
<!-- 文档预览 -->
|
||||
<div v-if="fileType === 'document' && filePath" class="preview-item">
|
||||
<a-button @click="handlePreviewDocument">预览文档</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
|
||||
const props = defineProps({
|
||||
fileType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
filePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const getFullPath = (path) => {
|
||||
return getFileAccessHttpUrl(path);
|
||||
};
|
||||
|
||||
const handlePreviewDocument = () => {
|
||||
window.open(getFullPath(props.filePath), '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preview-container {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
|
@ -12,7 +12,7 @@
|
|||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="键名" v-bind="validateInfos.configKey" id="SysConfigForm-configKey" name="configKey">
|
||||
<a-input v-model:value="formData.configKey" placeholder="请输入键名" allow-clear></a-input>
|
||||
<a-input v-model:value="formData.configKey" placeholder="请输入键名" allow-clear :disabled="!!formData.id"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</a-col>
|
||||
<a-col :span="5">
|
||||
<span v-if="hasSearchConditions" style="font-size: 16px;font-weight: bold;color:#E86A6A">
|
||||
有筛选条件未重置,{{ activeSearchConditions }}
|
||||
有筛选条件未重置
|
||||
</span>
|
||||
<span style="font-size: 16px;font-weight: bold;color:#ABACAD">点击展开更多操作区域</span>
|
||||
</a-col>
|
||||
|
|
|
@ -0,0 +1,343 @@
|
|||
<template>
|
||||
<SyncComponent ref="syncComRef" @changeShowJMCom="changeShowJMCom" @sourceOrgCodeChanged="sourceOrgCodeChanged"
|
||||
@viewTypeChanged="viewTypeChanged">
|
||||
<!-- 表单 -->
|
||||
<template #searchFormSlot>
|
||||
<a-form ref="formRef" @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol"
|
||||
:wrapper-col="wrapperCol">
|
||||
<a-row :gutter="24">
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="categoryId">
|
||||
<template #label><span title="服务类别">服务类别</span></template>
|
||||
<j-dict-select-tag type="list" v-model:value="queryParam.categoryId" v-if="showJMCom"
|
||||
:orgCode="sourceOrgCode"
|
||||
:dictCode="`nu_config_service_category,category_name,id,del_flag = 0 order by sort asc`"
|
||||
:ignoreDisabled="true" placeholder="请选择服务类别" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="typeId">
|
||||
<template #label><span title="服务类型">服务类型</span></template>
|
||||
<j-dict-select-tag type="list" v-model:value="queryParam.typeId" v-if="showJMCom"
|
||||
:orgCode="sourceOrgCode"
|
||||
:dictCode="`nu_config_service_type,type_name,id,del_flag = 0 order by sort asc`"
|
||||
placeholder="请选择服务类型" :ignoreDisabled="true" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- <template v-if="toggleSearchStatus"> -->
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="directiveName">
|
||||
<template #label><span title="服务指令">服务指令</span></template>
|
||||
<JInput v-model:value="queryParam.directiveName" placeholder="请输入服务指令名称" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="instructionTagId">
|
||||
<template #label><span title="分类标签">分类标签</span></template>
|
||||
<j-dict-select-tag v-model:value="queryParam.instructionTagId" v-if="showJMCom"
|
||||
:orgCode="sourceOrgCode" dictCode="instruction_tag" :ignoreDisabled="true"
|
||||
placeholder="请选分类标签" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="bodyTags">
|
||||
<template #label><span title="体型标签">体型标签</span></template>
|
||||
<JSelectMultiple type="list" v-model:value="queryParam.bodyTags" v-if="showJMCom"
|
||||
:orgCode="sourceOrgCode"
|
||||
:dictCode="`nu_config_body_tag,tag_name,id,del_flag = '0' order by sort asc`"
|
||||
:ignoreDisabled="true" placeholder="请选择体型标签" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6">
|
||||
<a-form-item name="emotionTags">
|
||||
<template #label><span title="情绪标签">情绪标签</span></template>
|
||||
<JSelectMultiple type="list" v-model:value="queryParam.emotionTags" v-if="showJMCom"
|
||||
:orgCode="sourceOrgCode"
|
||||
:dictCode="`nu_config_emotion_tag,tag_name,id,del_flag = '0' order by sort asc`"
|
||||
:ignoreDisabled="true" placeholder="请选择情绪标签" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xl="6" :lg="7" :md="8" :sm="24">
|
||||
<span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
|
||||
<a-col :lg="6">
|
||||
<a-button type="primary" preIcon="ant-design:search-outlined"
|
||||
@click="searchQuery">查询</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset"
|
||||
style="margin-left: 8px">重置</a-button>
|
||||
</a-col>
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 试验田数据列表 -->
|
||||
<template #sourceTableSlot>
|
||||
<ConfigServiceDirectiveList @select-change="handleSelectChange" ref="listComRef" :queryParams="queryParam"
|
||||
:initialDataIds="initialDataIds" />
|
||||
</template>
|
||||
|
||||
<!-- 需要同步的机构的数据列表 -->
|
||||
<template #businessTableSlot>
|
||||
<a-table size="small" :columns="selectedColumns" :dataSource="Array.from(selectedItems.values())"
|
||||
:pagination="false" :scroll="{ y: '55vh' }">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-popconfirm placement="left" ok-text="确认" cancel-text="取消"
|
||||
@confirm="handleRemoveFromRight(record.id)">
|
||||
<template #title>
|
||||
<span>是否确认移除</span>
|
||||
</template>
|
||||
<a-button type="link" danger size="small"
|
||||
:disabled="initialDataIds.includes(record.id)">移除</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'mp3File'">
|
||||
<span v-if="!record.mp3File" style="font-size: 12px;font-style: italic;">无文件</span>
|
||||
<audio controls v-else style="width: 100%; max-width: 300px; height: 40px;">
|
||||
<source :src="getFileAccessHttpUrl(record.mp3File)">
|
||||
</audio>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'mp4File'">
|
||||
<span v-if="!record.mp4File" style="font-size: 12px;font-style: italic;">无文件</span>
|
||||
<a-button v-else type="primary" size="small" @click="openVideoModal(record.mp4File)">
|
||||
播放视频
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'bodyTagList'">
|
||||
<span v-if="!record.bodyTagList || record.bodyTagList.length === 0"
|
||||
style="font-size: 12px;font-style: italic;">-</span>
|
||||
<template v-else>
|
||||
{{record.bodyTagList.map(item => item.tagName).join('、')}}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'emotionTagList'">
|
||||
<span v-if="!record.emotionTagList || record.emotionTagList.length === 0"
|
||||
style="font-size: 12px;font-style: italic;">-</span>
|
||||
<template v-else>
|
||||
{{record.emotionTagList.map(item => item.tagName).join('、')}}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'previewFile'">
|
||||
<span v-if="!record.previewFile" style="font-size: 12px;font-style: italic;">无图片</span>
|
||||
<img v-else :src="getFileAccessHttpUrl(record.previewFile)"
|
||||
style="max-width: 100px; max-height: 60px;" />
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'immediateFile'">
|
||||
<span v-if="!record.immediateFile" style="font-size: 12px;font-style: italic;">无图片</span>
|
||||
<img v-else :src="getFileAccessHttpUrl(record.immediateFile)"
|
||||
style="max-width: 100px; max-height: 60px;" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
</SyncComponent>
|
||||
|
||||
<a-modal v-model:visible="showVideoModal" title="视频播放" :footer="null" @cancel="closeVideoModal"
|
||||
:bodyStyle="{ padding: '0', maxHeight: '80vh', overflow: 'auto' }">
|
||||
<video controls style="width: 100%; max-height: '70vh'; display: block; margin: 0 auto;">
|
||||
<source :src="videoUrl">
|
||||
您的浏览器不支持视频播放。
|
||||
</video>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="synchronization-directive2" lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import SyncComponent from '/@/components/Sync/SyncComponent.vue'
|
||||
import ConfigServiceDirectiveList from '@/views/services/serviceDirective/ConfigServiceDirectiveList.vue'
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'
|
||||
import JInput from "/@/components/Form/src/jeecg/components/JInput.vue";
|
||||
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
|
||||
import JSelectMultiple from '/@/components/Form/src/jeecg/components/JSelectMultiple.vue';
|
||||
|
||||
// 表格列定义
|
||||
const selectedColumns = [
|
||||
{
|
||||
title: '服务指令',
|
||||
dataIndex: 'directiveName',
|
||||
key: 'directiveName',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '服务类别',
|
||||
dataIndex: 'categoryName',
|
||||
key: 'categoryName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '服务类型',
|
||||
dataIndex: 'typeName',
|
||||
key: 'typeName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '分类标签',
|
||||
dataIndex: 'instructionTagName',
|
||||
key: 'instructionTagName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '体型标签',
|
||||
dataIndex: 'bodyTagList',
|
||||
key: 'bodyTagList',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '情绪标签',
|
||||
dataIndex: 'emotionTagList',
|
||||
key: 'emotionTagList',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '预览图',
|
||||
dataIndex: 'previewFile',
|
||||
key: 'previewFile',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '即时图',
|
||||
dataIndex: 'immediateFile',
|
||||
key: 'immediateFile',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '音频',
|
||||
dataIndex: 'mp3File',
|
||||
key: 'mp3File',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '视频',
|
||||
dataIndex: 'mp4File',
|
||||
key: 'mp4File',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 80,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const labelCol = reactive({
|
||||
xs: 24,
|
||||
sm: 4,
|
||||
xl: 6,
|
||||
xxl: 4
|
||||
});
|
||||
const wrapperCol = reactive({
|
||||
xs: 24,
|
||||
sm: 20,
|
||||
});
|
||||
const sourceOrgCode = ref('')
|
||||
const syncComRef = ref(null)
|
||||
const listComRef = ref();
|
||||
const selectedItems = ref(new Map<string | number, any>());
|
||||
const queryParam = ref({ viewType: 'all' })//源数据查询参数
|
||||
const initialDataIds = ref<string[]>([]);
|
||||
const showJMCom = ref(false)
|
||||
|
||||
/**
|
||||
* 获取到了试验田的机构编码
|
||||
* @param v_
|
||||
*/
|
||||
const sourceOrgCodeChanged = (v_) => {
|
||||
sourceOrgCode.value = v_
|
||||
}
|
||||
/**
|
||||
* 是否展示设计dict的cj框架封装的组件(因为需要获取的是对应试验田的数据字典,当接口数据请求未返回时如果展示组件会报错)
|
||||
* 跟sourceOrgCodeChanged方法相配合
|
||||
* @param v_
|
||||
*/
|
||||
const changeShowJMCom = (v_) => {
|
||||
showJMCom.value = v_
|
||||
}
|
||||
|
||||
/**
|
||||
* 源数据的全部、已选择、未选择变更时触发
|
||||
* @param v_ all、selected、unselected
|
||||
*/
|
||||
const viewTypeChanged = (v_) => {
|
||||
queryParam.value.viewType = v_
|
||||
//组件内部监听了queryParam值的变化,自动刷新
|
||||
}
|
||||
|
||||
|
||||
const showVideoModal = ref(false);
|
||||
const videoUrl = ref('');
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
function searchQuery() {
|
||||
listComRef.value.reload();
|
||||
}
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function searchReset() {
|
||||
let vt = queryParam.value.viewType
|
||||
queryParam.value = { viewType: vt }
|
||||
listComRef.value?.reload();
|
||||
}
|
||||
|
||||
const handleSelectChange = (items: Map<string | number, any>) => {
|
||||
selectedItems.value = new Map(items);
|
||||
};
|
||||
|
||||
const handleRemoveFromRight = (key: string | number) => {
|
||||
selectedItems.value.delete(key);
|
||||
listComRef.value?.removeSelectedItem?.(key);
|
||||
};
|
||||
|
||||
const openVideoModal = (url: string) => {
|
||||
videoUrl.value = getFileAccessHttpUrl(url);
|
||||
showVideoModal.value = true;
|
||||
};
|
||||
|
||||
const closeVideoModal = () => {
|
||||
showVideoModal.value = false;
|
||||
videoUrl.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.jeecg-basic-table-form-container {
|
||||
padding: 0;
|
||||
|
||||
.table-page-search-submitButtons {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.query-group-cust {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
|
||||
.query-group-split-cust {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.ant-form-item:not(.ant-form-item-with-help) {
|
||||
margin-bottom: 16px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
:deep(.ant-picker),
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-list-container {
|
||||
:deep(.ant-table) {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -37,6 +37,7 @@
|
|||
<a-modal v-model:open="showAsyncResult" title="同步结果" @ok="showAsyncResult = false" width="70vw">
|
||||
<a-tabs v-model:activeKey="activeTabKey" style="margin-left: 20px;margin-right: 20px;margin-bottom: 20px;">
|
||||
<template #rightExtra>
|
||||
<span style="color:red;font-weight: bold;margin-right: 10px;">同步为增量同步,不会修改、删除业务系统数据</span>
|
||||
<a-button type="primary" @click="AsyncResultFunc({ id: opeDictId })" size="small"
|
||||
preIcon="ant-design:sync-outlined" style="margin-right: 10px;">刷新</a-button>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue