Compare commits

..

7 Commits

10 changed files with 1080 additions and 229 deletions

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1743498900165" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="32026" width="256" height="256" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M491.290329 814.099433 172.772972 485.118769l81.634252-66.460668 184.189999 146.68992c75.488275-91.145883 243.049548-271.999904 474.234885-415.849126l19.469424 45.569872C720.102618 393.022235 546.356482 671.686932 491.290329 814.099433L491.290329 814.099433 491.290329 814.099433 491.290329 814.099433zM893.493667 454.874955c3.400446 20.849864 5.203512 42.24515 5.203512 64.047712 0 217.08213-175.971827 393.063167-393.06419 393.063167-217.055524 0-393.063167-175.981037-393.063167-393.063167 0-217.065757 176.007643-393.059074 393.063167-393.059074 52.423977 0 102.419647 10.275018 148.147108 28.90533L653.780097 76.133981c-47.586808-15.926738-97.265254-23.992437-148.147108-23.992437-62.9671 0-124.107599 12.342096-181.694155 36.7019-55.605436 23.538089-105.513102 57.198723-148.365072 100.008738-42.836621 42.850947-76.481905 92.748381-100.010785 148.365072C51.220568 394.814044 38.85289 455.934077 38.85289 518.922666c0 62.978357 12.367679 124.113739 36.710087 181.720761 23.528879 55.584969 57.173141 105.497753 100.010785 148.343583 42.850947 42.841737 92.759637 76.481905 148.365072 100.010785 57.586556 24.343431 118.732172 36.699853 181.694155 36.699853 62.989613 0 124.145461-12.356422 181.730994-36.699853 55.585993-23.528879 105.488543-57.162908 148.334373-100.010785 42.850947-42.845831 76.501348-92.759637 100.010785-148.343583 24.373107-57.605999 36.70497-118.742405 36.70497-181.720761 0-21.583574-1.436722-42.944068-4.328585-64.047712L893.493667 454.874955 893.493667 454.874955 893.493667 454.874955zM893.493667 454.874955" fill="#81C784" p-id="32027"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,13 +1,6 @@
<template>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
:title="getTitle"
:width="adaptiveWidth"
@ok="handleSubmit"
:showFooter="showFooter"
destroyOnClose
>
<BasicDrawer v-bind="$attrs" @register="registerDrawer" :title="getTitle" :width="adaptiveWidth" @ok="handleSubmit"
:showFooter="showFooter" destroyOnClose>
<BasicForm @register="registerForm">
<template #pwd="{ model, field }">
<a-row :gutter="8">
@ -19,11 +12,12 @@
</a-col>
</a-row>
</template>
<template #departSelect ="{model,field}">
<a-select v-model:value="model[field]" :disabled="model['id']!=null" @change="(value,option) => handleChange(value,model)">
<template #departSelect="{ model, field }">
<a-select v-model:value="model[field]" :disabled="model['id'] != null"
@change="(value, option) => handleChange(value, model)" :allowClear="true">
<template v-for="item in departOptions" :key="`${item.code}`">
<a-select-option :value="item.code" :label="item.departName">
{{item.departName}}
{{ item.departName }}
</a-select-option>
</template>
</a-select>
@ -33,112 +27,110 @@
</template>
<script lang="ts" setup>
import { defineComponent, ref, computed, unref, useAttrs } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
import { formSchema } from './datasource.data';
import { saveOrUpdateDataSource, getDataSourceById, testConnection, queryDeparts } from './datasource.api';
import {useMessage} from "@/hooks/web/useMessage";
const { createMessage } = useMessage();
// Emits
const emit = defineEmits(['success', 'register']);
const attrs = useAttrs();
const isUpdate = ref(true);
const rowId = ref('');
let isFormDepartUser = false;
//
const [registerForm, { setProps, resetFields, validateFields, getFieldsValue, setFieldsValue, validate, updateSchema }] = useForm({
labelWidth: 90,
schemas: formSchema,
showActionButtonGroup: false,
});
const showFooter = ref(true);
const departOptions = ref<any[]>([]);
//
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
await resetFields();
showFooter.value = data?.showFooter ?? true;
setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)){
await getDepartOptions('');
}else{
await getDepartOptions('1');
}
if (unref(isUpdate)) {
//
data.record = await getDataSourceById({ id: data.record.id });
//
await setFieldsValue({
...data.record,
});
}
//
setProps({ disabled: !showFooter.value });
});
//
const getTitle = computed(() => {
if (!unref(isUpdate)) {
return '新增数据源';
} else {
return unref(showFooter) ? '编辑数据源' : '数据源详情';
}
});
const { adaptiveWidth } = useDrawerAdaptiveWidth();
async function getDepartOptions(addFLag){
departOptions.value = [];
const data = await queryDeparts({ addFLag : addFLag });
Object.assign(departOptions.value, data);
import { defineComponent, ref, computed, unref, useAttrs } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
import { formSchema } from './datasource.data';
import { saveOrUpdateDataSource, getDataSourceById, testConnection, queryDeparts } from './datasource.api';
import { useMessage } from "@/hooks/web/useMessage";
const { createMessage } = useMessage();
// Emits
const emit = defineEmits(['success', 'register']);
const attrs = useAttrs();
const isUpdate = ref(true);
const rowId = ref('');
let isFormDepartUser = false;
//
const [registerForm, { setProps, resetFields, validateFields, getFieldsValue, setFieldsValue, validate, updateSchema }] = useForm({
labelWidth: 90,
schemas: formSchema,
showActionButtonGroup: false,
});
const showFooter = ref(true);
const departOptions = ref<any[]>([]);
//
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
await resetFields();
showFooter.value = data?.showFooter ?? true;
setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
await getDepartOptions('');
} else {
await getDepartOptions('1');
}
async function handleChange(value,formData){
if(value == null){
formData["code"] = "";
}else{
formData["code"] = value;
}
}
if (unref(isUpdate)) {
async function handleTest() {
let keys = ['dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword'];
//
let fieldsValues = getFieldsValue(keys);
let setFields = {};
keys.forEach((key) => (setFields[key] = { value: fieldsValues[key], errors: null }));
await validateFields(keys).then((values) => {
let loading = createMessage.loading('连接中....', 0);
testConnection(values)
.then((data) => {
if (data.success) {
createMessage.success('连接成功');
}
})
.catch((error) => {})
.finally(() => loading());
//
data.record = await getDataSourceById({ id: data.record.id });
//
await setFieldsValue({
...data.record,
});
}
//
async function handleSubmit() {
try {
let values = await validate();
setDrawerProps({ confirmLoading: true });
await saveOrUpdateDataSource(values, isUpdate.value);
//
closeDrawer();
//
emit('success');
} finally {
setDrawerProps({ confirmLoading: false });
}
//
setProps({ disabled: !showFooter.value });
});
//
const getTitle = computed(() => {
if (!unref(isUpdate)) {
return '新增数据源';
} else {
return unref(showFooter) ? '编辑数据源' : '数据源详情';
}
});
const { adaptiveWidth } = useDrawerAdaptiveWidth();
async function getDepartOptions(addFLag) {
departOptions.value = [];
const data = await queryDeparts({ addFLag: addFLag });
Object.assign(departOptions.value, data);
}
async function handleChange(value, formData) {
if (value == null) {
formData["code"] = "";
} else {
formData["code"] = value;
}
}
async function handleTest() {
let keys = ['dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword'];
//
let fieldsValues = getFieldsValue(keys);
let setFields = {};
keys.forEach((key) => (setFields[key] = { value: fieldsValues[key], errors: null }));
await validateFields(keys).then((values) => {
let loading = createMessage.loading('连接中....', 0);
testConnection(values)
.then((data) => {
if (data.success) {
createMessage.success('连接成功');
}
})
.catch((error) => { })
.finally(() => loading());
});
}
//
async function handleSubmit() {
try {
let values = await validate();
setDrawerProps({ confirmLoading: true });
await saveOrUpdateDataSource(values, isUpdate.value);
//
closeDrawer();
//
emit('success');
} finally {
setDrawerProps({ confirmLoading: false });
}
}
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -11,11 +11,12 @@
</a-col>
</a-row>
</template>
<template #departSelect ="{model,field}">
<a-select v-model:value="model[field]" :disabled="model['id']!=null" @change="(value,option) => handleChange(value,model)">
<template #departSelect="{ model, field }">
<a-select v-model:value="model[field]" :disabled="model['id'] != null"
@change="(value, option) => handleChange(value, model)" :allowClear="true">
<template v-for="item in departOptions" :key="`${item.code}`">
<a-select-option :value="item.code" :label="item.departName">
{{item.departName}}
{{ item.departName }}
</a-select-option>
</template>
</a-select>
@ -24,95 +25,95 @@
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from './datasource.data';
import { saveOrUpdateDataSource, getDataSourceById, testConnection, queryDeparts } from './datasource.api';
import { useMessage } from '/@/hooks/web/useMessage';
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from './datasource.data';
import { saveOrUpdateDataSource, getDataSourceById, testConnection, queryDeparts } from './datasource.api';
import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage } = useMessage();
// Emits
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const departOptions = ref<any[]>([]);
//
const [registerForm, { getFieldsValue, resetFields, validateFields, setFieldsValue, validate }] = useForm({
// labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
});
//
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//
await resetFields();
const { createMessage } = useMessage();
// Emits
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const departOptions = ref<any[]>([]);
//
const [registerForm, { getFieldsValue, resetFields, validateFields, setFieldsValue, validate }] = useForm({
// labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
});
//
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//
await resetFields();
setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)){
await getDepartOptions('');
}else{
await getDepartOptions('1');
}
if (unref(isUpdate)) {
//
data.record = await getDataSourceById({ id: data.record.id });
//
await setFieldsValue({
...data.record,
});
}
});
//
const title = computed(() => (!unref(isUpdate) ? '新增数据源' : '编辑数据源'));
async function getDepartOptions(addFLag){
departOptions.value = [];
const data = await queryDeparts({ addFLag : addFLag });
console.log(data);
Object.assign(departOptions.value, data);
setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
await getDepartOptions('');
} else {
await getDepartOptions('1');
}
async function handleChange(value,formData){
if(value == null){
formData["code"] = "";
}else{
formData["code"] = value;
}
}
async function handleTest() {
let keys = ['dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword'];
//
let fieldsValues = getFieldsValue(keys);
let setFields = {};
keys.forEach((key) => (setFields[key] = { value: fieldsValues[key], errors: null }));
await validateFields(keys).then((values) => {
let loading = createMessage.loading('连接中....', 0);
testConnection(values)
.then((data) => {
if (data.success) {
createMessage.success('连接成功');
}
})
.catch((error) => {})
.finally(() => loading());
if (unref(isUpdate)) {
//
data.record = await getDataSourceById({ id: data.record.id });
//
await setFieldsValue({
...data.record,
});
}
});
//
const title = computed(() => (!unref(isUpdate) ? '新增数据源' : '编辑数据源'));
//
async function handleSubmit(v) {
try {
let values = await validate();
setModalProps({ confirmLoading: true });
//
await saveOrUpdateDataSource(values, isUpdate.value);
//
closeModal();
//
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
async function getDepartOptions(addFLag) {
departOptions.value = [];
const data = await queryDeparts({ addFLag: addFLag });
console.log(data);
Object.assign(departOptions.value, data);
}
async function handleChange(value, formData) {
if (value == null) {
formData["code"] = "";
} else {
formData["code"] = value;
}
}
async function handleTest() {
let keys = ['dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword'];
//
let fieldsValues = getFieldsValue(keys);
let setFields = {};
keys.forEach((key) => (setFields[key] = { value: fieldsValues[key], errors: null }));
await validateFields(keys).then((values) => {
let loading = createMessage.loading('连接中....', 0);
testConnection(values)
.then((data) => {
if (data.success) {
createMessage.success('连接成功');
}
})
.catch((error) => { })
.finally(() => loading());
});
}
//
async function handleSubmit(v) {
try {
let values = await validate();
setModalProps({ confirmLoading: true });
//
await saveOrUpdateDataSource(values, isUpdate.value);
//
closeModal();
//
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>

View File

@ -13,6 +13,7 @@ enum Api {
getByCode = '/sys/dataSource/queryBySysOrgCode',
// exportXlsUrl = 'sys/dataSource/exportXls',
// importExcelUrl = 'sys/dataSource/importExcel',
validCode = '/sys/dataSource/validCode',
}
// /**
// * 导出api
@ -99,3 +100,8 @@ export const queryDeparts = (params) => {
export const getDataSourceByCode = (params) => {
return defHttp.get({ url: Api.getByCode, params });
};
export const validCode = (params) => {
return defHttp.get({ url: Api.validCode, params });
};

View File

@ -1,4 +1,5 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { validCode } from './datasource.api'
const dbDriverMap = {
// MySQL 数据库
@ -69,6 +70,12 @@ const dbUrlMap = {
};
export const columns: BasicColumn[] = [
{
title: '数据源名称',
dataIndex: 'name',
width: 150,
align: 'left',
},
{
title: '所属机构',
dataIndex: 'sysOrgCode_dictText',
@ -81,12 +88,6 @@ export const columns: BasicColumn[] = [
width: 150,
align: 'left',
},
{
title: '数据源名称',
dataIndex: 'name',
width: 150,
align: 'left',
},
{
title: '数据库类型',
dataIndex: 'dbType_dictText',
@ -143,11 +144,17 @@ export const formSchema: FormSchema[] = [
component: 'Input',
show: false,
},
{
field: 'name',
label: '数据源名称',
component: 'Input',
required: true,
},
{
field: 'sysOrgCode',
label: '所属机构',
component: 'Input',
required: true,
required: false,
slot: 'departSelect',
},
{
@ -155,13 +162,24 @@ export const formSchema: FormSchema[] = [
label: '数据源编码',
component: 'Input',
required: true,
dynamicDisabled: true
},
{
field: 'name',
label: '数据源名称',
component: 'Input',
required: true,
dynamicDisabled: (val) => {
return !!val.model.sysOrgCode;
},
// dynamicDisabled:true,
// rules: [
// {
// validator: async (_, value) => {
// if (!value) return Promise.resolve();
// const res = await validCode({code:value});
// if (res == 'exist') {
// return Promise.reject('编码已存在!');
// }
// return Promise.resolve();
// },
// trigger: 'blur',
// }
// ]
},
{
field: 'dbType',
@ -230,7 +248,7 @@ export function useDataSourceFormSchema() {
label: '数据源编码',
component: 'Input',
// required: true,
dynamicDisabled: true
dynamicDisabled: true,
},
{
field: 'name',

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,286 @@
<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 = '';
};
//
const updateSelection = (selectedRecords: any[]) => {
selectedRowIds.value.clear();
allSelectedItems.value.clear();
selectedRecords.forEach(record => {
selectedRowIds.value.add(record.id);
allSelectedItems.value.set(record.id, record);
});
//
if (registerTable) {
registerTable.toggleRowSelection?.(Array.from(selectedRowIds.value), true);
}
};
//
defineExpose({
removeSelectedItem,
updateSelection
});
</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,530 @@
<template>
<div class="p-2">
<a-card @click="toggleSearchStatus = true" :class="{ 'base-con-class': toggleSearchStatus == false }">
<a-row>
<a-col :span="24">
<span class="base-info-label">机构信息:</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;"> {{ 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-row>
<a-row>
<a-col :span="24" v-show="toggleSearchStatus && !!orgData">
<a-row :gutter="16">
<a-col :span="8">
<div class="org-info-item">
<span class="label">机构编码</span>
<span class="value">{{ orgData?.orgCode || '-' }}</span>
</div>
</a-col>
<a-col :span="8">
<div class="org-info-item">
<span class="label">运营时间</span>
<span class="value">
{{ orgData?.operationStartTime || '-' }}
{{ orgData?.operationEndTime || '-' }}
</span>
</div>
</a-col>
<a-col :span="8">
<div class="org-info-item">
<span class="label">合同时间</span>
<span class="value">
{{ orgData?.contractStartTime || '-' }}
{{ orgData?.contractEndTime || '-' }}
</span>
</div>
</a-col>
<a-col :span="8">
<div class="org-info-item">
<span class="label">电话</span>
<span class="value">{{ orgData?.mobile || '-' }}</span>
</div>
</a-col>
<a-col :span="8">
<div class="org-info-item">
<span class="label">传真</span>
<span class="value">{{ orgData?.fax || '-' }}</span>
</div>
</a-col>
<a-col :span="8">
<div class="org-info-item">
<span class="label">地址</span>
<span class="value">{{ orgData?.address || '-' }}</span>
</div>
</a-col>
</a-row>
</a-col>
</a-row>
<a-divider v-show="toggleSearchStatus" />
<a-col :span="24" v-show="toggleSearchStatus">
<span>筛选条件及功能操作按钮</span>
<a-form></a-form>
</a-col>
</a-card>
</div>
<div class="p-2" style="margin-top: -10px;">
<a-card v-show="!orgSelectedCon">
<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>
<a-card v-show="orgSelectedCon">
<div class="org-cards-container">
<a-row :gutter="16">
<a-col :span="8" v-for="org in orgTable" :key="org.id">
<a-card :title="org.departName" class="org-card" :class="{ 'selected': org.id == orgData?.id }"
hoverable @click="handleOrgSelected(org)">
<div class="org-info-item">
<span class="label">机构编码</span>
<span class="value">{{ org.orgCode || '-' }}</span>
</div>
<div class="org-info-item">
<span class="label">运营时间</span>
<span class="value">{{ org.operationStartTime || '-' }} {{ org.operationEndTime || '-'
}}</span>
</div>
<div class="org-info-item">
<span class="label">合同时间</span>
<span class="value">{{ org.contractStartTime || '-' }} {{ org.contractEndTime || '-'
}}</span>
</div>
<div class="org-info-item">
<span class="label">电话</span>
<span class="value">{{ org.mobile || '-' }}</span>
</div>
<div class="org-info-item">
<span class="label">传真</span>
<span class="value">{{ org.fax || '-' }}</span>
</div>
<div class="org-info-item">
<span class="label">地址</span>
<span class="value">{{ org.address || '-' }}</span>
</div>
</a-card>
</a-col>
</a-row>
</div>
</a-card>
<a-card v-show="orgSelectedCon && (!orgTable || orgTable?.length == 0)">
<a-empty description="暂无数据" :image="simpleImage" />
</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, onMounted } from 'vue';
import ConfigServiceDirectiveListCom from '@/views/serviceDirective/serviceDirective/ConfigServiceDirectiveListCom.vue'
import { list } from '@/views/serviceDirective/serviceDirective/ConfigServiceDirective.api';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { queryDepartTreeSync } from '/@/api/common/api';
import { Empty } from 'ant-design-vue';
import { Modal } from 'ant-design-vue';
import { useMessage } from "/@/hooks/web/useMessage";
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([]);
const orgData = ref(null);
const showVideoModal = ref(false);
const videoUrl = ref('');
const selectedItems = ref(new Map<string | number, any>());
const listComRef = ref();
const orgSelectedCon = ref(true)
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 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 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 = '';
};
const handleOrgSelected = async (org) => {
if (!!orgData.value && orgData.value.id != org.id) {
Modal.confirm({
title: '变更机构',
content: '是否变更机构',
okText: '确认',
cancelText: '取消',
onOk: async () => {
try {
const res = await list({ dataSourceCode: org.orgCode });
console.log("🌊 ~ list ~ res:", res);
//
const selectedIds = new Set(selectedItems.value.keys());
//
const newRecords = res.records.filter(record => !selectedIds.has(record.id));
//
selectedItems.value.clear(); //
newRecords.forEach(record => {
selectedItems.value.set(record.id, record); //
});
//
orgData.value = org;
orgSelectedCon.value = false;
//
updateLeftListSelection(newRecords);
} catch (err) {
createMessage.error('机构数据查询失败');
}
},
});
} else {
try {
const res = await list({ dataSourceCode: org.orgCode });
console.log("🌊 ~ list ~ res:", res);
//
const selectedIds = new Set(selectedItems.value.keys());
//
const newRecords = res.records.filter(record => !selectedIds.has(record.id));
//
selectedItems.value.clear(); //
newRecords.forEach(record => {
selectedItems.value.set(record.id, record); //
});
//
orgData.value = org;
orgSelectedCon.value = false;
//
updateLeftListSelection(newRecords);
} catch (err) {
createMessage.error('机构数据查询失败');
}
}
};
//
const updateLeftListSelection = (newRecords: any[]) => {
listComRef.value?.updateSelection?.(Array.from(selectedItems.value.values()));
};
const handleResetOrg = () => {
orgSelectedCon.value = true
}
onMounted(() => {
queryDepartTreeSync({platType:'ywjg'}).then(res => {
orgTable.value = res;
});
});
</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;
}
}
/* 新增的机构卡片样式 */
.org-cards-container {
max-height: 66vh;
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;
&:hover {
transform: translateY(-4px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
background: radial-gradient(circle at center,
#a7d7ff 0%,
#b8e1ff 70%,
#d1e5ff 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;
}
}
:deep(.ant-card-body) {
padding: 16px;
}
}
.org-info-item {
display: flex;
margin-bottom: 12px;
font-size: 13px;
&:last-child {
margin-bottom: 0;
}
.label {
font-weight: bold;
font-size: 13px;
}
.value {
color: #333;
word-break: break-all;
}
}
.base-info-label,
.base-info-dn {
font-weight: bold;
font-size: 15px;
}
.org-card-choosed {
background-color: red;
}
.selected {
background: url("@/assets/icons/success.svg") no-repeat;
background-position: calc(100% - 5px) calc(100% - 5px);
background-size: auto 40%;
transition: none !important;
}
</style>

View File

@ -48,6 +48,10 @@
treeData: props.rootTreeData,
},
},
{
field: 'platType',
show: !isChild
},
{
field: 'orgCode',
show: false,

View File

@ -36,6 +36,19 @@ export function useBasicFormSchema() {
component: 'RadioButtonGroup',
componentProps: { options: [] },
},
{
field: 'platType',
label: '业务平台类型',
defaultValue:'ywjg',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'iz_test_site'
},
rules:[{
required: true,
message: '请选择平台类型'
}]
},
{
field: 'departOrder',
label: '排序',