数据同步-服务指令同步

This commit is contained in:
1378012178@qq.com 2025-04-18 10:02:38 +08:00
parent 8118cc5639
commit 3962fc6536
3 changed files with 552 additions and 15 deletions

View File

@ -9,32 +9,32 @@ export const columns: BasicColumn[] = [
title: '服务类别',
align: 'center',
dataIndex: 'categoryId_dictText',
customCell: (record, index, column) => {
if (record.categoryRowSpan != null) {
return { rowSpan: record.categoryRowSpan };
}
},
// customCell: (record, index, column) => {
// if (record.categoryRowSpan != null) {
// return { rowSpan: record.categoryRowSpan };
// }
// },
},
{
title: '服务类型',
align: 'center',
dataIndex: 'typeId_dictText',
customCell: (record, index, column) => {
if (record.typeRowSpan != null) {
return { rowSpan: record.typeRowSpan };
}
},
// customCell: (record, index, column) => {
// if (record.typeRowSpan != null) {
// return { rowSpan: record.typeRowSpan };
// }
// },
},
{
title: '分类标签',
align: 'center',
dataIndex: 'instructionTagId_dictText',
width: 100,
customCell: (record, index, column) => {
if (record.instructionRowSpan != null) {
return { rowSpan: record.instructionRowSpan };
}
},
// customCell: (record, index, column) => {
// if (record.instructionRowSpan != null) {
// return { rowSpan: record.instructionRowSpan };
// }
// },
},
{
title: '服务指令名称',

View File

@ -0,0 +1,268 @@
<template>
<div class="p-2">
<BasicTable
@register="registerTable"
:scroll="{ y: '55vh' }"
:rowClassName="getRowClassName"
>
<template #tableTitle></template>
<template #action="{ record }">
<TableAction
:actions="getTableAction(record)"
:dropDownActions="getDropDownAction(record)"
/>
</template>
<template v-slot:bodyCell="{ column, record, index, text }">
<template v-if="column.dataIndex === 'mp3File'">
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
<audio controls v-else style="width: 100%; max-width: 300px; height: 40px;">
<source :src="getFileAccessHttpUrl(text)">
</audio>
</template>
<template v-if="column.dataIndex === 'mp4File'">
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
<template v-else>
<a-button type="primary" @click="openVideoModal(text)">播放视频</a-button>
</template>
</template>
</template>
</BasicTable>
<ConfigServiceDirectiveModal ref="registerModal" @success="handleSuccess"></ConfigServiceDirectiveModal>
<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>
</div>
</template>
<script lang="ts" name="serviceDirective-configServiceDirective" setup>
import { ref, reactive, watch } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, superQuerySchema } from './ConfigServiceDirective.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './ConfigServiceDirective.api';
import ConfigServiceDirectiveModal from './components/ConfigServiceDirectiveModal.vue'
import { cloneDeep } from "lodash-es";
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
const emit = defineEmits(['select-change']);
const queryParam = reactive<any>({});
const registerModal = ref();
const selectedRowIds = ref<Set<string | number>>(new Set());
const allSelectedItems = ref<Map<string | number, any>>(new Map());
//
const getRowClassName = (record) => {
return selectedRowIds.value.has(record.id) ? 'selected-row' : '';
};
//
const toggleSelect = (record) => {
if (selectedRowIds.value.has(record.id)) {
selectedRowIds.value.delete(record.id);
allSelectedItems.value.delete(record.id);
} else {
selectedRowIds.value.add(record.id);
allSelectedItems.value.set(record.id, record);
}
updateSelectedItems();
};
//
const removeSelected = (record) => {
selectedRowIds.value.delete(record.id);
allSelectedItems.value.delete(record.id);
updateSelectedItems();
};
//
const removeSelectedItem = (id: string | number) => {
selectedRowIds.value.delete(id);
allSelectedItems.value.delete(id);
updateSelectedItems();
};
//
const updateSelectedItems = () => {
emit('select-change', new Map(allSelectedItems.value));
};
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
//
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '服务指令',
api: list,
columns,
size: 'small',
showTableSetting: false,
canResize: false,
useSearchForm: false,
showIndexColumn: true,
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: async (params) => {
params.column = 'createTime'
params.order = 'desc'
let rangerQuery = await setRangeQuery();
return Object.assign(params, rangerQuery);
},
},
exportConfig: {
name: "服务指令",
url: getExportUrl,
params: queryParam,
},
importConfig: {
url: getImportUrl,
success: handleSuccess
},
});
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
function handleDetail(record: Recordable) {
registerModal.value.disableSubmit = true;
registerModal.value.edit(record);
}
function getTableAction(record) {
const actions = [
{
label: '详情',
onClick: handleDetail.bind(null, record),
}
];
if (selectedRowIds.value.has(record.id)) {
actions.push({
label: '移除',
color: 'error',
onClick: () => {
removeSelected(record);
}
});
} else {
actions.push({
label: '选择',
onClick: () => {
toggleSelect(record);
}
});
}
return actions;
}
function getDropDownAction(record) {
return [];
}
let rangeField = 'tollPrice,comPrice,';
async function setRangeQuery() {
let queryParamClone = cloneDeep(queryParam);
if (rangeField) {
let fieldsValue = rangeField.split(',');
fieldsValue.forEach(item => {
if (queryParamClone[item]) {
let range = queryParamClone[item];
queryParamClone[item + '_begin'] = range[0];
queryParamClone[item + '_end'] = range[1];
delete queryParamClone[item];
} else {
queryParamClone[item + '_begin'] = '';
queryParamClone[item + '_end'] = '';
}
})
}
return queryParamClone;
}
const showVideoModal = ref(false);
const videoUrl = ref('');
const openVideoModal = (url) => {
videoUrl.value = getFileAccessHttpUrl(url);
showVideoModal.value = true;
};
const closeVideoModal = () => {
showVideoModal.value = false;
videoUrl.value = '';
};
defineExpose({
removeSelectedItem
});
</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%;
}
}
audio::-webkit-media-controls-timeline {
display: none;
}
audio::-webkit-media-controls-current-time-display,
audio::-webkit-media-controls-time-remaining-display {
display: none;
}
:deep(.ant-table-title) {
display: none !important;
}
:deep(.selected-row) {
background-color: #e6f7ff !important;
&:hover td {
background-color: #e6f7ff !important;
}
}
</style>

View File

@ -0,0 +1,269 @@
<template>
<div class="p-2">
<a-card @click="toggleSearchStatus = true" :class="{ 'base-con-class': toggleSearchStatus == false }">
<a-row>
<a-col :span="23">
<span>机构信息</span>
</a-col>
<a-divider v-show="toggleSearchStatus" />
<a-col :span="24" v-show="toggleSearchStatus">
<span>筛选条件及功能操作按钮</span>
<a-form></a-form>
</a-col>
</a-row>
</a-card>
</div>
<div class="p-2">
<a-card>
<a-row>
<a-col :span="5" :push="19">
<a-radio-group v-model:value="splitVal" button-style="solid" size="small"
@change="splitScreenChanged">
<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-col>
<a-col :span="1" :push="18">
<a @click="toggleSearchStatus = !toggleSearchStatus"
style="margin-left: 20px; display: inline-flex; align-items: center; gap: 4px">
{{ toggleSearchStatus ? '拉伸' : '压缩' }}
<Icon
:icon="toggleSearchStatus ? 'humbleicons:align-objects-top' : 'humbleicons:align-objects-bottom'" />
</a>
</a-col>
</a-row>
<a-row>
<a-col :span="sourceScreenSpan">
<ConfigServiceDirectiveListCom @select-change="handleSelectChange" ref="listComRef" />
</a-col>
<a-col :span="selectedScreenSpan">
<a-card size="small" :bordered="false" class="selected-list-container">
<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-button type="link" danger size="small" @click="handleRemoveFromRight(record.id)">
移除
</a-button>
</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 === 'tagList'">
<span v-if="!record.tagList || record.tagList.length === 0"
style="font-size: 12px;font-style: italic;">-</span>
<template v-else>
{{record.tagList.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>
</a-card>
</a-col>
</a-row>
</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, reactive } from 'vue';
import ConfigServiceDirectiveListCom from '@/views/serviceDirective/serviceDirective/ConfigServiceDirectiveListCom.vue'
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { render } from '/@/utils/common/renderUtils';
const toggleSearchStatus = ref<boolean>(true);
const splitVal = ref<string>('sc1sed1');
const sourceScreenSpan = ref(12);
const selectedScreenSpan = ref(12);
function 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 selectedItems = ref(new Map<string | number, any>());
const listComRef = ref();
const selectedColumns = [
{
title: '服务类别',
align: 'center',
dataIndex: 'categoryId_dictText',
width: 100 //
},
{
title: '服务类型',
align: 'center',
dataIndex: 'typeId_dictText',
width: 120 //
},
{
title: '分类标签',
align: 'center',
dataIndex: 'instructionTagId_dictText',
width: 90,
},
{
title: '服务指令名称',
align: 'center',
dataIndex: 'directiveName',
width: 150 //
},
{
title: '指令标签',
align: 'center',
dataIndex: 'tagList',
width: 150,
ellipsis: true //
},
{
title: '周期类型',
align: 'center',
dataIndex: 'cycleType_dictText',
width: 100 //
},
{
title: '服务时长(分钟)',
align: 'center',
dataIndex: 'serviceDuration',
width: 135,
},
{
title: '是否启用',
align: 'center',
dataIndex: 'izEnabled_dictText',
width: 100,
},
{
title: '语音文件',
align: 'center',
dataIndex: 'mp3File',
width: 200 //
},
{
title: '视频文件',
align: 'center',
dataIndex: 'mp4File',
width: 120 //
},
{
title: '预览图片',
align: 'center',
dataIndex: 'previewFile',
width: 120 //
},
{
title: '即时指令图片',
align: 'center',
dataIndex: 'immediateFile',
width: 150 //
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 60,
fixed: 'right'
}
];
const handleSelectChange = (items: Map<string | number, any>) => {
selectedItems.value = new Map(items);
};
const handleRemoveFromRight = (key: string | number) => {
console.log("🌊 ~ handleRemoveFromRight ~ selectedItems.value:", selectedItems.value)
selectedItems.value.delete(key);
listComRef.value?.removeSelectedItem?.(key); //
};
const showVideoModal = ref(false);
const videoUrl = ref('');
const openVideoModal = (url: string) => {
videoUrl.value = getFileAccessHttpUrl(url);
showVideoModal.value = true;
};
const closeVideoModal = () => {
showVideoModal.value = false;
videoUrl.value = '';
};
</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: 100%;
overflow: hidden;
:deep(.ant-table-small) {
font-size: 12px;
}
:deep(.ant-table-cell) {
padding: 8px !important;
}
}
</style>