Commit ab9eacc33de6a49708a1bb40459d4ed6bc8c8bce

Authored by ww
2 parents 6a2b472b 70490d49

chore: 合并代码解决冲突

Showing 42 changed files with 899 additions and 702 deletions
... ... @@ -25,7 +25,7 @@ module.exports = defineConfig({
25 25 'plugin:jest/recommended',
26 26 ],
27 27 rules: {
28   - 'no-console': 'error',
  28 + 'no-console': 'off',
29 29 'vue/script-setup-uses-vars': 'error',
30 30 '@typescript-eslint/ban-ts-ignore': 'off',
31 31 '@typescript-eslint/explicit-function-return-type': 'off',
... ...
... ... @@ -126,10 +126,16 @@
126 126 }
127 127 }
128 128
129   - async function handleFetch() {
130   - if (!props.immediate && unref(isFirstLoad)) {
  129 + // async function handleFetch() {
  130 + // if (!props.immediate && unref(isFirstLoad)) {
  131 + // await fetch();
  132 + // isFirstLoad.value = false;
  133 + // }
  134 + // }
  135 +
  136 + async function handleFetch(open) {
  137 + if (open) {
131 138 await fetch();
132   - isFirstLoad.value = false;
133 139 }
134 140 }
135 141
... ...
... ... @@ -30,12 +30,14 @@
30 30 modalProps?: ExtractPropTypes<InstanceType<typeof BasicModal>['$props']>;
31 31 transferProps?: ExtractPropTypes<TransferType['$props']>;
32 32 buttonProps?: ExtractPropTypes<InstanceType<typeof Button>['$props']>;
  33 + disabled?: any;
33 34 }>(),
34 35 {
35 36 labelField: 'label',
36 37 valueField: 'value',
37 38 buttonName: '选择产品',
38 39 maxTagLength: 2,
  40 + disabled: false,
39 41 }
40 42 );
41 43
... ... @@ -146,6 +148,8 @@
146 148 );
147 149
148 150 const handleOpenModal = () => {
  151 + const { disabled } = props;
  152 + if (disabled) return;
149 153 openModal(true);
150 154 };
151 155
... ...
  1 +<script lang="ts" setup>
  2 + import { Button, Tabs, Badge, ButtonProps, Tag } from 'ant-design-vue';
  3 + import { get, isFunction, set, uniqBy } from 'lodash-es';
  4 + import { ExtractPropTypes, computed, unref, ref, nextTick, onMounted, watch } from 'vue';
  5 + import { DynamicProps } from '/#/utils';
  6 + import { BasicModal, useModal } from '/@/components/Modal';
  7 + import { BasicTable, BasicTableProps, TableRowSelection, useTable } from '/@/components/Table';
  8 + import { FETCH_SETTING } from '/@/components/Table/src/const';
  9 + import { useDesign } from '/@/hooks/web/useDesign';
  10 +
  11 + interface Options extends Recordable {
  12 + primaryKey?: string;
  13 + disabled?: boolean;
  14 + }
  15 +
  16 + enum Active {
  17 + PENDING = 'pending',
  18 + SELECTED = 'selected',
  19 + }
  20 +
  21 + interface ActionType {
  22 + setSelectedOptions: (options: Recordable[]) => void;
  23 + }
  24 +
  25 + const emit = defineEmits(['change', 'update:value']);
  26 +
  27 + const props = withDefaults(
  28 + defineProps<{
  29 + value?: string[];
  30 + labelField?: string;
  31 + valueField?: string;
  32 + primaryKey?: string;
  33 + params?: Recordable;
  34 + buttonName?: string;
  35 + pendingTableProps?: BasicTableProps;
  36 + selectedTableProps?: BasicTableProps;
  37 + maxTagLength?: number;
  38 + modalProps?: ExtractPropTypes<InstanceType<typeof BasicModal>['$props']>;
  39 + buttonProps?: ExtractPropTypes<InstanceType<typeof Button>['$props']>;
  40 + initSelectedOptions?: (actionType: ActionType) => Promise<Recordable[]>;
  41 + transformValue?: (selectedRowKeys: string[], selectedRows: Options[]) => any[];
  42 + onValueChange?: (selectedRowkeys: string[]) => any[];
  43 + onRemoveAfter?: (actionType: ActionType) => Promise<any>;
  44 + onSelectedAfter?: (actionType: ActionType) => Promise<any>;
  45 + disabled?: any;
  46 + }>(),
  47 + {
  48 + buttonName: '选择设备',
  49 + primaryKey: 'id',
  50 + maxTagLength: 2,
  51 + labelField: 'label',
  52 + valueField: 'value',
  53 + disabled: false,
  54 + }
  55 + );
  56 +
  57 + const { prefixCls } = useDesign('transfer-table-modal');
  58 +
  59 + const activeKey = ref<Active>(Active.PENDING);
  60 +
  61 + const selectedRows = ref<Options[]>([]);
  62 +
  63 + const selectedRowKeys = ref<string[]>(props.value || []);
  64 +
  65 + const pendingOptions = ref<Options[]>([]);
  66 +
  67 + const selectedConfirmQueue = ref<Options[]>([]);
  68 +
  69 + const pendingConfirmQueue = ref<Options[]>([]);
  70 +
  71 + const selectedTotal = ref(0);
  72 +
  73 + const pendingTotal = ref(0);
  74 +
  75 + const getFetchSetting = computed(() => {
  76 + const { pendingTableProps } = props;
  77 + return pendingTableProps?.fetchSetting || FETCH_SETTING;
  78 + });
  79 +
  80 + const getPendingRowSelection = computed<TableRowSelection>(() => {
  81 + const rowKeys = unref(selectedRowKeys);
  82 + return {
  83 + type: 'checkbox',
  84 + getCheckboxProps: (record: Recordable) => {
  85 + const { primaryKey } = props;
  86 + return {
  87 + ...record,
  88 + disabled: rowKeys.includes(record[primaryKey]),
  89 + };
  90 + },
  91 + onSelect: (_record: Recordable, _selected: boolean, selectedRows: Object[]) => {
  92 + pendingConfirmQueue.value = selectedRows;
  93 + },
  94 + onSelectAll: (_selected: boolean, selectedRows: Recordable[]) => {
  95 + pendingConfirmQueue.value = selectedRows;
  96 + },
  97 + };
  98 + });
  99 +
  100 + const getPendingTableBindProps = computed<Partial<DynamicProps<BasicTableProps>>>(() => {
  101 + const { pendingTableProps, primaryKey } = props;
  102 + return {
  103 + ...pendingTableProps,
  104 + rowKey: primaryKey,
  105 + api: handlePendingApiIntercept,
  106 + clickToRowSelect: false,
  107 + rowSelection: getPendingRowSelection,
  108 + };
  109 + });
  110 +
  111 + const getSelectedTableBindProps = computed<Partial<DynamicProps<BasicTableProps>>>(() => {
  112 + const { selectedTableProps, primaryKey } = props;
  113 + return {
  114 + ...selectedTableProps,
  115 + dataSource: selectedRows,
  116 + clickToRowSelect: false,
  117 + rowKey: primaryKey,
  118 + api: selectedTableProps!.api ? handleSelectedApiIntercept : undefined,
  119 + rowSelection: {
  120 + type: 'checkbox',
  121 + onSelect: (_record: Recordable, _selected: boolean, selectedRows: Object[]) => {
  122 + selectedConfirmQueue.value = selectedRows;
  123 + },
  124 + onSelectAll: (_selected: boolean, selectedRows: Recordable[]) => {
  125 + selectedConfirmQueue.value = selectedRows;
  126 + },
  127 + },
  128 + };
  129 + });
  130 +
  131 + const getModalBindProps = computed(() => {
  132 + const { modalProps = {} } = props;
  133 + return {
  134 + width: '60%',
  135 + title: '穿梭表格',
  136 + wrapClassName: prefixCls,
  137 + ...modalProps,
  138 + showOkBtn: false,
  139 + };
  140 + });
  141 +
  142 + const getBindButtonProps = computed<ButtonProps>(() => {
  143 + const { buttonProps = {} } = props;
  144 + return {
  145 + type: 'link',
  146 + ...buttonProps,
  147 + };
  148 + });
  149 +
  150 + const getShowTagOptions = computed(() => {
  151 + const { maxTagLength } = props;
  152 + return unref(selectedRows).slice(0, maxTagLength);
  153 + });
  154 +
  155 + const getSurplusOptionsLength = computed(() => {
  156 + const { maxTagLength } = props;
  157 + const surplusValue = unref(selectedRows).length - maxTagLength;
  158 + return surplusValue < 0 ? 0 : surplusValue;
  159 + });
  160 +
  161 + const [registerModal, { openModal }] = useModal();
  162 +
  163 + const [
  164 + regsterPendingTable,
  165 + {
  166 + getSelectRows: getPendingSelectRows,
  167 + getSelectRowKeys: getPendingSelectRowKeys,
  168 + reload: reloadPending,
  169 + clearSelectedRowKeys: clearPendingSelectedRowKeys,
  170 + },
  171 + ] = useTable(unref(getPendingTableBindProps));
  172 +
  173 + const [
  174 + registerSelectedTable,
  175 + { getSelectRowKeys, setProps, clearSelectedRowKeys, reload: reloadSelected },
  176 + ] = useTable(unref(getSelectedTableBindProps));
  177 +
  178 + async function handlePendingApiIntercept(params?: Recordable) {
  179 + try {
  180 + const { api } = props.pendingTableProps || {};
  181 + if (api && isFunction(api)) {
  182 + let options = await api(params);
  183 + pendingOptions.value = options;
  184 + const { totalField, listField } = unref(getFetchSetting);
  185 + const total = get(options, totalField!);
  186 + if (unref(selectedTotal) + unref(pendingTotal) !== total) {
  187 + pendingTotal.value = total;
  188 + }
  189 + let list: Recordable[] = get(options, listField!);
  190 + list = getSelectedRows(list);
  191 + options = set(options, listField!, list);
  192 + return options;
  193 + }
  194 + } catch (error) {
  195 + console.error(error);
  196 + return [];
  197 + }
  198 + return [];
  199 + }
  200 +
  201 + async function handleSelectedApiIntercept(params?: Recordable) {
  202 + try {
  203 + const { api } = props.selectedTableProps || {};
  204 + if (api && isFunction(api)) {
  205 + let options = await api(params);
  206 + pendingOptions.value = options;
  207 + const { totalField, listField } = unref(getFetchSetting);
  208 + selectedTotal.value = get(options, totalField!);
  209 + let list: Recordable[] = get(options, listField!);
  210 + list = getSelectedRows(list);
  211 + options = set(options, listField!, list);
  212 + return options;
  213 + }
  214 + } catch (error) {
  215 + console.error(error);
  216 + return [];
  217 + }
  218 + return [];
  219 + }
  220 +
  221 + const handleOpenModal = async () => {
  222 + const { disabled } = props;
  223 + if (disabled) return;
  224 + openModal(true);
  225 + await nextTick();
  226 + if (props.value && !props.value.length) {
  227 + activeKey.value = Active.PENDING;
  228 + reloadPending();
  229 + }
  230 + };
  231 +
  232 + const handleTriggerEmit = (selectedRowKeys: string[], selectedRows: Options[]) => {
  233 + const { transformValue } = props;
  234 + let value = selectedRowKeys;
  235 + if (transformValue && isFunction(transformValue)) {
  236 + value = transformValue(selectedRowKeys, selectedRows);
  237 + }
  238 + emit('change', unref(selectedRowKeys), unref(selectedRows));
  239 + emit('update:value', unref(value));
  240 + };
  241 +
  242 + const handleSelected = async () => {
  243 + const { onSelectedAfter } = props;
  244 + const currentPageSelectRows = getPendingSelectRows();
  245 + const currentPageSelectRowKeys = getPendingSelectRowKeys();
  246 + const { primaryKey } = props;
  247 + selectedRows.value = uniqBy([...unref(selectedRows), ...currentPageSelectRows], primaryKey);
  248 + selectedRowKeys.value = [...new Set([...unref(selectedRowKeys), ...currentPageSelectRowKeys])];
  249 + pendingConfirmQueue.value = [];
  250 + // selectedTotal.value = unref(selectedRowKeys).length;
  251 + pendingTotal.value = unref(pendingTotal) - currentPageSelectRows.length;
  252 + selectedTotal.value = unref(selectedTotal) + currentPageSelectRows.length;
  253 +
  254 + clearPendingSelectedRowKeys();
  255 + handleTriggerEmit(unref(selectedRowKeys), unref(selectedRows));
  256 +
  257 + if (onSelectedAfter && isFunction(onSelectedAfter)) {
  258 + await onSelectedAfter(actionType);
  259 + }
  260 + reloadPending();
  261 + };
  262 +
  263 + const handleRemoveSelected = async () => {
  264 + const { onRemoveAfter } = props;
  265 + const removeRowKeys = getSelectRowKeys();
  266 + selectedRowKeys.value = unref(selectedRowKeys).filter((key) => !removeRowKeys.includes(key));
  267 + selectedRows.value = unref(selectedRows).filter((item) => {
  268 + const { primaryKey } = props;
  269 + return unref(selectedRowKeys).includes(item[primaryKey]);
  270 + });
  271 + pendingTotal.value = unref(pendingTotal) + removeRowKeys.length;
  272 + selectedTotal.value = unref(selectedTotal) - removeRowKeys.length;
  273 +
  274 + clearSelectedRowKeys();
  275 + selectedConfirmQueue.value = [];
  276 + setProps({ dataSource: unref(selectedRows) });
  277 + handleTriggerEmit(unref(selectedRowKeys), unref(selectedRows));
  278 +
  279 + if (onRemoveAfter && isFunction(onRemoveAfter)) {
  280 + await onRemoveAfter(actionType);
  281 + }
  282 + };
  283 +
  284 + const actionType = {
  285 + setSelectedOptions,
  286 + setSelectedTotal,
  287 + reloadPending,
  288 + reloadSelected,
  289 + };
  290 +
  291 + const getSelectedRows = (options: Recordable[]) => {
  292 + const { labelField, valueField } = props;
  293 + return options.map((item) => ({ ...item, label: item[labelField], value: item[valueField] }));
  294 + };
  295 +
  296 + const getSelectedKeys = (options: Recordable[]) => {
  297 + const { primaryKey } = props;
  298 + return options.map((item) => item[primaryKey]);
  299 + };
  300 +
  301 + function setSelectedOptions(options: Recordable[]) {
  302 + selectedRows.value = getSelectedRows(options);
  303 + selectedRowKeys.value = getSelectedKeys(options);
  304 + }
  305 +
  306 + function setSelectedTotal(number: number) {
  307 + selectedTotal.value = number;
  308 + }
  309 +
  310 + const handleCheckoutPanel = async (keys: Active) => {
  311 + await nextTick();
  312 + if (keys === Active.PENDING) {
  313 + reloadPending();
  314 + } else {
  315 + reloadSelected();
  316 + setProps({
  317 + dataSource: unref(selectedRows),
  318 + });
  319 + }
  320 + };
  321 +
  322 + watch(
  323 + () => props.value,
  324 + () => {
  325 + if (props.value && !props.value.length) {
  326 + selectedRowKeys.value = [];
  327 + selectedRows.value = [];
  328 + // pendingTotal.value = 0;
  329 + selectedTotal.value = 0;
  330 + }
  331 + }
  332 + );
  333 +
  334 + onMounted(async () => {
  335 + const { initSelectedOptions } = props;
  336 + if (initSelectedOptions && isFunction(initSelectedOptions)) {
  337 + const options = await initSelectedOptions(actionType);
  338 + setSelectedOptions(options);
  339 + }
  340 + });
  341 +</script>
  342 +
  343 +<template>
  344 + <section>
  345 + <BasicModal @register="registerModal" v-bind="getModalBindProps">
  346 + <section class="bg-gray-100">
  347 + <Tabs v-model:active-key="activeKey" type="card" @change="handleCheckoutPanel">
  348 + <Tabs.TabPane :key="Active.PENDING">
  349 + <template #tab>
  350 + <div class="flex items-center justify-center">
  351 + <span>待选设备</span>
  352 + <Badge show-zero :count="pendingTotal" />
  353 + </div>
  354 + </template>
  355 + <BasicTable @register="regsterPendingTable">
  356 + <template #toolbar>
  357 + <section class="flex w-full justify-end items-center">
  358 + <!-- <Button type="primary">全选</Button> -->
  359 + <div class="text-blue-400">
  360 + <span class="mr-2">选择设备:</span>
  361 + <span>{{ pendingConfirmQueue.length }}</span>
  362 + </div>
  363 + </section>
  364 + </template>
  365 + </BasicTable>
  366 + <section class="flex justify-end px-4 pb-4">
  367 + <Button
  368 + type="primary"
  369 + @click="handleSelected"
  370 + :disabled="!pendingConfirmQueue.length"
  371 + >
  372 + <span>确定已选</span>
  373 + </Button>
  374 + </section>
  375 + </Tabs.TabPane>
  376 + <Tabs.TabPane :key="Active.SELECTED">
  377 + <template #tab>
  378 + <div class="flex items-center justify-center">
  379 + <span>已选设备</span>
  380 + <Badge show-zero :count="selectedTotal" />
  381 + </div>
  382 + </template>
  383 + <BasicTable @register="registerSelectedTable">
  384 + <template #toolbar>
  385 + <section class="flex w-full justify-end items-center">
  386 + <!-- <Button type="primary">全选</Button> -->
  387 + <div class="text-blue-400">
  388 + <span class="mr-2">选择设备:</span>
  389 + <span>{{ selectedConfirmQueue.length }}</span>
  390 + </div>
  391 + </section>
  392 + </template>
  393 + </BasicTable>
  394 + <section class="flex justify-end px-4 pb-4">
  395 + <Button
  396 + type="primary"
  397 + :disabled="!selectedConfirmQueue.length"
  398 + @click="handleRemoveSelected"
  399 + >
  400 + <span>移除已选</span>
  401 + </Button>
  402 + </section>
  403 + </Tabs.TabPane>
  404 + </Tabs>
  405 + </section>
  406 + </BasicModal>
  407 + <Button @click="handleOpenModal" v-bind="getBindButtonProps">
  408 + <span v-if="!selectedRowKeys.length">选择设备</span>
  409 + <div v-if="selectedRowKeys.length">
  410 + <Tag
  411 + class="!px-2 !py-1 !bg-gray-50 !border-gray-100"
  412 + v-for="item in getShowTagOptions"
  413 + :key="item.value"
  414 + >
  415 + <span>
  416 + {{ item.alias || item.name }}
  417 + </span>
  418 + </Tag>
  419 + <Tag class="!px-2 !py-1 !bg-gray-50 !border-gray-100" v-if="getSurplusOptionsLength">
  420 + <span> +{{ getSurplusOptionsLength }}... </span>
  421 + </Tag>
  422 + </div>
  423 + </Button>
  424 + </section>
  425 +</template>
  426 +
  427 +<style lang="less">
  428 + @prefix-cls: ~'@{namespace}-transfer-table-modal';
  429 +
  430 + .@{prefix-cls} {
  431 + .vben-basic-table {
  432 + padding-top: 0;
  433 + }
  434 +
  435 + .vben-basic-form > .ant-row {
  436 + width: 100%;
  437 + }
  438 +
  439 + .ant-tabs-top-bar {
  440 + background-color: #fff;
  441 + }
  442 + }
  443 +</style>
... ...
... ... @@ -70,11 +70,6 @@
70 70 emitChange();
71 71 };
72 72
73   - // const disabled = computed(() => {
74   - // const { disabled } = props || {};
75   - // return disabled;
76   - // });
77   -
78 73 //新增Input
79 74 const add = () => {
80 75 dynamicInput.params.push({
... ...
... ... @@ -39,7 +39,7 @@ export const formSchemas = (hasStructForm: boolean, isTcp = false): FormSchema[]
39 39 span: 18,
40 40 },
41 41 componentProps: {
42   - maxLength: 255,
  42 + maxLength: 32,
43 43 placeholder: '请输入功能名称',
44 44 },
45 45 },
... ... @@ -52,7 +52,7 @@ export const formSchemas = (hasStructForm: boolean, isTcp = false): FormSchema[]
52 52 span: 18,
53 53 },
54 54 componentProps: {
55   - maxLength: 255,
  55 + maxLength: 128,
56 56 placeholder: '请输入标识符',
57 57 },
58 58 },
... ...
... ... @@ -394,6 +394,13 @@
394 394 //.ant-table-tbody > tr.ant-table-row-selected td {
395 395 //background-color: fade(@primary-color, 8%) !important;
396 396 //}
  397 + .ant-table-placeholder {
  398 + display: flex;
  399 + align-items: center;
  400 + justify-content: center;
  401 + height: 670px;
  402 + max-height: 670px;
  403 + }
397 404 }
398 405
399 406 .ant-pagination {
... ...
... ... @@ -8,24 +8,6 @@
8 8 @ok="handleSubmit"
9 9 >
10 10 <BasicForm @register="registerForm">
11   - <template #iconSelect>
12   - <Upload
13   - name="avatar"
14   - accept=".png,.jpg,.jpeg,.gif"
15   - list-type="picture-card"
16   - class="avatar-uploader"
17   - :show-upload-list="false"
18   - :customRequest="customUpload"
19   - :before-upload="beforeUpload"
20   - >
21   - <img v-if="tenantLogo" :src="tenantLogo" alt="avatar" />
22   - <div v-else>
23   - <LoadingOutlined v-if="loading" />
24   - <plus-outlined v-else />
25   - <div class="ant-upload-text">上传</div>
26   - </div>
27   - </Upload>
28   - </template>
29 11 <template #videoPlatformIdSlot="{ model, field }">
30 12 <a-select
31 13 placeholder="请选择流媒体配置"
... ... @@ -52,23 +34,20 @@
52 34 import { formSchema } from './config.data';
53 35 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
54 36 import { createOrEditCameraManage } from '/@/api/camera/cameraManager';
55   - import { message, Upload } from 'ant-design-vue';
56 37 import { useMessage } from '/@/hooks/web/useMessage';
57   - import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
58   - import { upload } from '/@/api/oss/ossFileUploader';
59   - import { FileItem } from '/@/components/Upload/src/typing';
  38 + import { PlusOutlined } from '@ant-design/icons-vue';
60 39 import { getStreamingMediaList } from '/@/api/camera/cameraManager';
61 40 import SteramingDrawer from '../streaming/SteramingDrawer.vue';
62 41 import { useDrawer } from '/@/components/Drawer';
  42 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  43 + import { buildUUID } from '/@/utils/uuid';
63 44
64 45 export default defineComponent({
65 46 name: 'ContactDrawer',
66 47 components: {
67 48 BasicDrawer,
68 49 BasicForm,
69   - Upload,
70 50 PlusOutlined,
71   - LoadingOutlined,
72 51 SteramingDrawer,
73 52 VNodes: (_, { attrs }) => {
74 53 return attrs.vnodes;
... ... @@ -118,42 +97,19 @@
118 97 if (unref(isUpdate)) {
119 98 await nextTick();
120 99 editId.value = data.record.id;
121   - tenantLogo.value = data.record?.avatar;
122   - await setFieldsValue(data.record);
  100 + if (data.record.avatar) {
  101 + setFieldsValue({
  102 + avatar: [{ uid: buildUUID(), name: 'name', url: data.record.avatar } as FileItem],
  103 + });
  104 + }
  105 + const { avatar, ...params } = data.record;
  106 + console.log(avatar);
  107 + await setFieldsValue({ ...params });
123 108 } else {
124   - tenantLogo.value = '';
125 109 editId.value = '';
126 110 }
127 111 });
128 112
129   - const tenantLogo = ref('');
130   -
131   - async function customUpload({ file }) {
132   - if (beforeUpload(file)) {
133   - tenantLogo.value = '';
134   - loading.value = true;
135   - const formData = new FormData();
136   - formData.append('file', file);
137   - const response = await upload(formData);
138   - if (response.fileStaticUri) {
139   - tenantLogo.value = response.fileStaticUri;
140   - loading.value = false;
141   - }
142   - }
143   - }
144   -
145   - const beforeUpload = (file: FileItem) => {
146   - const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
147   - if (!isJpgOrPng) {
148   - message.error('只能上传图片文件!');
149   - }
150   - const isLt2M = (file.size as number) / 1024 / 1024 < 5;
151   - if (!isLt2M) {
152   - message.error('图片大小不能超过5MB!');
153   - }
154   - return isJpgOrPng && isLt2M;
155   - };
156   -
157 113 const getTitle = computed(() => (!unref(isUpdate) ? '新增视频配置' : '编辑视频配置'));
158 114
159 115 async function handleSubmit() {
... ... @@ -162,8 +118,9 @@
162 118 const { createMessage } = useMessage();
163 119 const values = await validate();
164 120 if (!values) return;
165   - if (tenantLogo.value !== '') {
166   - values.avatar = tenantLogo.value;
  121 + if (Reflect.has(values, 'avatar')) {
  122 + const file = (values.avatar || []).at(0) || {};
  123 + values.avatar = file.url || null;
167 124 }
168 125 if (editId.value !== '') {
169 126 values.id = editId.value;
... ... @@ -188,9 +145,6 @@
188 145 registerDrawer,
189 146 registerForm,
190 147 handleSubmit,
191   - customUpload,
192   - beforeUpload,
193   - tenantLogo,
194 148 loading,
195 149 streamConfigOptions,
196 150 registerSteramingDrawer,
... ...
... ... @@ -20,6 +20,12 @@
20 20 import { useFingerprint } from '/@/utils/useFingerprint';
21 21 import { GetResult } from '@fingerprintjs/fingerprintjs';
22 22
  23 + const props = defineProps({
  24 + mode: {
  25 + type: String,
  26 + default: PageMode.SPLIT_SCREEN_MODE,
  27 + },
  28 + });
23 29 type CameraRecordItem = CameraRecord & {
24 30 canPlay?: boolean;
25 31 isTransform?: boolean;
... ... @@ -252,7 +258,11 @@
252 258 </Authority>
253 259 </div>
254 260 <Space>
255   - <Button type="primary" @click="handleChangeMode(PageMode.SPLIT_SCREEN_MODE)">
  261 + <Button
  262 + v-if="props.mode !== PageMode.SPLIT_SCREEN_MODE"
  263 + type="primary"
  264 + @click="handleChangeMode(PageMode.SPLIT_SCREEN_MODE)"
  265 + >
256 266 分屏模式
257 267 </Button>
258 268 <Button type="primary" @click="handleChangeMode(PageMode.LIST_MODE)">
... ...
... ... @@ -5,6 +5,9 @@ import { CameraVideoUrl, CameraMaxLength } from '/@/utils/rules';
5 5 import { h } from 'vue';
6 6 import SnHelpMessage from './SnHelpMessage.vue';
7 7 import { OrgTreeSelect } from '../../common/OrgTreeSelect';
  8 +import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  9 +import { createImgPreview } from '/@/components/Preview';
  10 +import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
8 11
9 12 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
10 13
... ... @@ -102,8 +105,33 @@ export const formSchema: QFormSchema[] = [
102 105 {
103 106 field: 'avatar',
104 107 label: '视频封面',
105   - slot: 'iconSelect',
106   - component: 'Input',
  108 + component: 'ApiUpload',
  109 + changeEvent: 'update:fileList',
  110 + valueField: 'fileList',
  111 + componentProps: () => {
  112 + return {
  113 + listType: 'picture-card',
  114 + maxFileLimit: 1,
  115 + accept: '.png,.jpg,.jpeg,.gif',
  116 + api: async (file: File) => {
  117 + try {
  118 + const formData = new FormData();
  119 + formData.set('file', file);
  120 + const { fileStaticUri, fileName } = await uploadThumbnail(formData);
  121 + return {
  122 + uid: fileStaticUri,
  123 + name: fileName,
  124 + url: fileStaticUri,
  125 + } as FileItem;
  126 + } catch (error) {
  127 + return {};
  128 + }
  129 + },
  130 + onPreview: (fileList: FileItem) => {
  131 + createImgPreview({ imageList: [fileList.url!] });
  132 + },
  133 + };
  134 + },
107 135 },
108 136 {
109 137 field: 'name',
... ...
... ... @@ -12,7 +12,11 @@
12 12
13 13 <template>
14 14 <div>
15   - <SplitScreenMode v-if="mode == PageMode.SPLIT_SCREEN_MODE" @switchMode="handleSwitchMode" />
  15 + <SplitScreenMode
  16 + :mode="mode"
  17 + v-if="mode == PageMode.SPLIT_SCREEN_MODE"
  18 + @switchMode="handleSwitchMode"
  19 + />
16 20 <ListMode v-if="mode === PageMode.LIST_MODE" @switchMode="handleSwitchMode" />
17 21 </div>
18 22 </template>
... ...
... ... @@ -37,7 +37,6 @@
37 37 const getBindProps = computed<Recordable>(() => {
38 38 const { value, apiTreeSelectProps = {} } = props;
39 39 const { params = {} } = apiTreeSelectProps;
40   - console.log(props, 'props');
41 40 return {
42 41 replaceFields: { children: 'children', key: 'id', title: 'name', value: 'id' },
43 42 getPopupContainer: () => document.body,
... ...
... ... @@ -12,6 +12,9 @@ import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/compo
12 12 import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum';
13 13 import { TaskTypeEnum } from '/@/views/task/center/config';
14 14 import { AddressTypeEnum } from '/@/views/task/center/components/PollCommandInput';
  15 +import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  16 +import { createImgPreview } from '/@/components/Preview';
  17 +import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
15 18
16 19 useComponentRegister('JSONEditor', JSONEditor);
17 20 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm);
... ... @@ -29,8 +32,33 @@ export const step1Schemas: FormSchema[] = [
29 32 {
30 33 field: 'icon',
31 34 label: '设备图片',
32   - slot: 'iconSelect',
33   - component: 'Input',
  35 + component: 'ApiUpload',
  36 + changeEvent: 'update:fileList',
  37 + valueField: 'fileList',
  38 + componentProps: () => {
  39 + return {
  40 + listType: 'picture-card',
  41 + maxFileLimit: 1,
  42 + accept: '.png,.jpg,.jpeg,.gif',
  43 + api: async (file: File) => {
  44 + try {
  45 + const formData = new FormData();
  46 + formData.set('file', file);
  47 + const { fileStaticUri, fileName } = await uploadThumbnail(formData);
  48 + return {
  49 + uid: fileStaticUri,
  50 + name: fileName,
  51 + url: fileStaticUri,
  52 + } as FileItem;
  53 + } catch (error) {
  54 + return {};
  55 + }
  56 + },
  57 + onPreview: (fileList: FileItem) => {
  58 + createImgPreview({ imageList: [fileList.url!] });
  59 + },
  60 + };
  61 + },
34 62 },
35 63 {
36 64 field: 'alias',
... ...
... ... @@ -32,14 +32,14 @@ export const columns: BasicColumn[] = [
32 32 h(
33 33 'div',
34 34 {
35   - class: 'cursor-pointer',
  35 + class: 'cursor-pointer truncate',
36 36 },
37 37 `${record.alias}`
38 38 ),
39 39 h(
40 40 'div',
41 41 {
42   - class: 'cursor-pointer text-blue-500',
  42 + class: 'cursor-pointer text-blue-500 truncate',
43 43 onClick: () => {
44 44 handeleCopy(`${record.name}`);
45 45 },
... ...
... ... @@ -109,7 +109,8 @@ export const basicInfoForm: FormSchema[] = [
109 109 label: '所属组织',
110 110 component: 'ApiTreeSelect',
111 111 rules: [{ required: true, message: '所属组织为必填项' }],
112   - componentProps: () => {
  112 + componentProps: ({ formModel, formActionType }) => {
  113 + const { setFieldsValue } = formActionType;
113 114 return {
114 115 maxLength: 250,
115 116 placeholder: '请选择所属组织',
... ... @@ -119,6 +120,11 @@ export const basicInfoForm: FormSchema[] = [
119 120 return data;
120 121 },
121 122 getPopupContainer: () => document.body,
  123 + onChange(e) {
  124 + if (e != formModel?.[FieldsEnum.ORGANIZATION_ID]) {
  125 + setFieldsValue({ [FieldsEnum.GATEWAY_TB_DEVICE_ID]: null });
  126 + }
  127 + },
122 128 };
123 129 },
124 130 },
... ... @@ -193,7 +199,12 @@ export const basicInfoForm: FormSchema[] = [
193 199 deviceType: DeviceTypeEnum.GATEWAY,
194 200 organizationId,
195 201 });
196   - return result;
  202 + return result.map((item) => {
  203 + return {
  204 + ...item,
  205 + name: item.alias || item.name,
  206 + };
  207 + });
197 208 } catch (error) {
198 209 return [];
199 210 }
... ...
... ... @@ -5,7 +5,7 @@
5 5 @register="register"
6 6 destroyOnClose
7 7 @close="closeDrawer"
8   - :title="deviceDetail.alias || deviceDetail.name"
  8 + :title="drawerTitle"
9 9 width="80%"
10 10 >
11 11 <Tabs v-model:activeKey="activeKey" :size="size">
... ... @@ -58,7 +58,7 @@
58 58 </BasicDrawer>
59 59 </template>
60 60 <script lang="ts">
61   - import { defineComponent, ref } from 'vue';
  61 + import { defineComponent, ref, computed } from 'vue';
62 62 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
63 63
64 64 import { Tabs } from 'ant-design-vue';
... ... @@ -122,6 +122,13 @@
122 122 emit('openGatewayDeviceDetail', { id: data.gatewayId });
123 123 };
124 124
  125 + const drawerTitle = computed(() => {
  126 + return (
  127 + deviceDetail.value?.alias?.slice(0, 40) + '...' ||
  128 + deviceDetail.value?.name?.slice(0, 40) + '...'
  129 + );
  130 + });
  131 +
125 132 return {
126 133 size,
127 134 activeKey,
... ... @@ -132,6 +139,7 @@
132 139 tbDeviceId,
133 140 handleOpenTbDeviceDetail,
134 141 handleOpenGatewayDevice,
  142 + drawerTitle,
135 143 };
136 144 },
137 145 });
... ...
... ... @@ -157,7 +157,7 @@
157 157 sn: stepRecord.name,
158 158 customerId: currentDeviceData.customerId,
159 159 deviceInfo: {
160   - avatar: DeviceStep1Ref.value?.devicePic,
  160 + avatar: stepRecord?.icon,
161 161 ...DeviceStep1Ref.value?.devicePositionState,
162 162 },
163 163 };
... ... @@ -169,7 +169,7 @@
169 169 ...stepRecord,
170 170 sn: stepRecord.name,
171 171 deviceInfo: {
172   - avatar: DeviceStep1Ref.value?.devicePic,
  172 + avatar: stepRecord?.icon,
173 173 ...DeviceStep1Ref.value?.devicePositionState,
174 174 },
175 175 deviceToken:
... ...
... ... @@ -22,24 +22,6 @@
22 22 </div>
23 23 </div>
24 24 </template>
25   - <template #iconSelect>
26   - <Upload
27   - name="avatar"
28   - accept=".png,.jpg,.jpeg,.gif"
29   - :show-upload-list="false"
30   - list-type="picture-card"
31   - class="avatar-uploader"
32   - :customRequest="customUpload"
33   - :before-upload="beforeUpload"
34   - >
35   - <img v-if="devicePic" :src="devicePic" alt="avatar" />
36   - <div v-else>
37   - <LoadingOutlined v-if="loading" />
38   - <PlusOutlined v-else />
39   - <div class="ant-upload-text">图片上传</div>
40   - </div>
41   - </Upload>
42   - </template>
43 25 <template #snCode="{ model, field }">
44 26 <div class="flex">
45 27 <Input v-model:value="model[field]" placeholder="请输入设备名称" />
... ... @@ -115,10 +97,9 @@
115 97 import { BasicForm, useForm } from '/@/components/Form';
116 98 import { step1Schemas } from '../../config/data';
117 99 import { useScript } from '/@/hooks/web/useScript';
118   - import { Input, Upload, message, Modal, Form, Row, Col, AutoComplete } from 'ant-design-vue';
119   - import { EnvironmentTwoTone, PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
  100 + import { Input, message, Modal, Form, Row, Col, AutoComplete } from 'ant-design-vue';
  101 + import { EnvironmentTwoTone } from '@ant-design/icons-vue';
120 102 import { upload } from '/@/api/oss/ossFileUploader';
121   - import { FileItem } from '/@/components/Upload/src/typing';
122 103 import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
123 104 import { generateSNCode } from '/@/api/device/deviceManager';
124 105 import icon from '/@/assets/images/wz.png';
... ... @@ -130,21 +111,20 @@
130 111 import DeptDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
131 112 import { TaskTypeEnum } from '/@/views/task/center/config';
132 113 import { toRaw } from 'vue';
  114 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  115 + import { buildUUID } from '/@/utils/uuid';
133 116
134 117 export default defineComponent({
135 118 components: {
136 119 BasicForm,
137 120 Input,
138 121 AutoComplete,
139   - Upload,
140 122 EnvironmentTwoTone,
141   - PlusOutlined,
142 123 Modal,
143 124 Form,
144 125 FormItem: Form.Item,
145 126 Row,
146 127 Col,
147   - LoadingOutlined,
148 128 DeptDrawer,
149 129 },
150 130 props: {
... ... @@ -202,8 +182,11 @@
202 182 async function nextStep() {
203 183 try {
204 184 let values = await validate();
205   - values = { devicePic: devicePic.value, ...positionState, ...values };
206   - delete values.icon;
  185 + if (Reflect.has(values, 'icon')) {
  186 + const file = (unref(values.icon) || []).at(0) || {};
  187 + values.icon = file.url || null;
  188 + }
  189 + values = { ...positionState, ...values };
207 190 delete values.deviceAddress;
208 191 emit('next', values);
209 192 // 获取输入的数据
... ... @@ -405,6 +388,11 @@
405 388 positionState.address = deviceInfo.address;
406 389 devicePositionState.value = { ...toRaw(positionState) };
407 390 devicePic.value = deviceInfo.avatar;
  391 + if (deviceInfo.avatar) {
  392 + setFieldsValue({
  393 + icon: [{ uid: buildUUID(), name: 'name', url: deviceInfo.avatar } as FileItem],
  394 + });
  395 + }
408 396 setFieldsValue({
409 397 ...data,
410 398 code: data?.code,
... ... @@ -414,6 +402,10 @@
414 402 // 父组件调用获取字段值的方法
415 403 function parentGetFieldsValue() {
416 404 const value = getFieldsValue();
  405 + if (Reflect.has(value, 'icon')) {
  406 + const file = (value.icon || []).at(0) || {};
  407 + value.icon = file.url || null;
  408 + }
417 409 return {
418 410 ...value,
419 411 ...(value?.code || value?.addressCode
... ...
... ... @@ -47,7 +47,7 @@
47 47 </div>
48 48 </BasicModal>
49 49 </div>
50   - <Description @register="register" class="mt-4" :data="deviceDetail" />
  50 + <Description @register="register" class="mt-4" :data="deviceDetail" :contentStyle="CS" />
51 51 </div>
52 52 <div class="mt-4" v-if="!isCustomer">
53 53 <a-button type="primary" class="mr-4" @click="copyTbDeviceId">复制设备ID</a-button>
... ... @@ -116,6 +116,15 @@
116 116 column: 2,
117 117 });
118 118
  119 + const CS = {
  120 + 'max-width': '600px',
  121 + 'word-break': 'break-all',
  122 + overflow: 'hidden',
  123 + display: '-webkit-box',
  124 + '-webkit-line-clamp': 2,
  125 + '-webkit-box-orient': 'vertical',
  126 + };
  127 +
119 128 // 地图
120 129 const mapWrapRef = ref<HTMLDivElement>();
121 130
... ... @@ -217,6 +226,7 @@
217 226 remoteConnectiondGateway,
218 227 locationImage,
219 228 isCustomer,
  229 + CS,
220 230 };
221 231 },
222 232 });
... ...
... ... @@ -15,7 +15,7 @@
15 15 import { getDeviceAttrs } from '/@/api/device/deviceManager';
16 16 import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel';
17 17 import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
18   - import { isArray, isObject } from '/@/utils/is';
  18 + import { isArray, isNull, isObject } from '/@/utils/is';
19 19 import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';
20 20 import { useGlobSetting } from '/@/hooks/setting';
21 21 import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget';
... ... @@ -161,14 +161,14 @@
161 161
162 162 handleFilterChange();
163 163
164   - unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource);
  164 + unref(mode) === EnumTableCardMode.TABLE && setTableModeData();
165 165 } catch (error) {}
166 166 },
167 167 resetFunc: async () => {
168 168 try {
169 169 socketInfo.filterAttrKeys = [];
170 170 handleFilterChange();
171   - unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource);
  171 + unref(mode) === EnumTableCardMode.TABLE && setTableModeData();
172 172 } catch (error) {}
173 173 },
174 174 });
... ... @@ -200,7 +200,7 @@
200 200 const switchMode = async (value: EnumTableCardMode) => {
201 201 mode.value = value;
202 202 await nextTick();
203   - unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource);
  203 + unref(mode) === EnumTableCardMode.TABLE && setTableModeData();
204 204 socketInfo.filterAttrKeys = [];
205 205 };
206 206
... ... @@ -280,7 +280,7 @@
280 280
281 281 await nextTick();
282 282
283   - unref(mode) === EnumTableCardMode.TABLE && setTableData(socketInfo.dataSource);
  283 + unref(mode) === EnumTableCardMode.TABLE && setTableModeData();
284 284 }
285 285 } catch (error) {}
286 286 },
... ... @@ -292,6 +292,10 @@
292 292 },
293 293 });
294 294
  295 + function setTableModeData() {
  296 + setTableData(socketInfo.dataSource.filter((item) => !isNull(item.value)));
  297 + }
  298 +
295 299 const handleShowDetail = (record: DataSource) => {
296 300 const { key } = record;
297 301 socketInfo.attr = key;
... ... @@ -308,9 +312,11 @@
308 312
309 313 const formatValue = (item: DataSource) => {
310 314 return item.type === DataTypeEnum.IS_BOOL
311   - ? !!Number(item.value)
312   - ? item.boolOpen
313   - : item.boolClose
  315 + ? !isNull(item.value)
  316 + ? !!Number(item.value)
  317 + ? item.boolOpen
  318 + : item.boolClose
  319 + : '--'
314 320 : item.value || '--';
315 321 };
316 322
... ... @@ -406,7 +412,11 @@
406 412 </List>
407 413 </section>
408 414
409   - <BasicTable v-if="mode === EnumTableCardMode.TABLE" @register="registerTable">
  415 + <BasicTable
  416 + v-if="mode === EnumTableCardMode.TABLE"
  417 + @register="registerTable"
  418 + class="device-things-model-table-mode"
  419 + >
410 420 <template #toolbar>
411 421 <div
412 422 v-show="mode === EnumTableCardMode.TABLE"
... ... @@ -447,6 +457,10 @@
447 457 display: flex;
448 458 align-items: center;
449 459 }
  460 +
  461 + .device-things-model-table-mode:deep(.ant-table-placeholder) {
  462 + height: auto;
  463 + }
450 464 </style>
451 465
452 466 <style>
... ...
... ... @@ -12,6 +12,9 @@ import { EventType, EventTypeColor, EventTypeName } from '../list/cpns/tabs/Even
12 12
13 13 import { useClipboard } from '@vueuse/core';
14 14 import { useMessage } from '/@/hooks/web/useMessage';
  15 +import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  16 +import { createImgPreview } from '/@/components/Preview';
  17 +import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
15 18
16 19 export enum Mode {
17 20 CARD = 'card',
... ... @@ -78,11 +81,13 @@ export const physicalColumn: BasicColumn[] = [
78 81 title: '功能名称',
79 82 dataIndex: FormField.FUNCTION_NAME,
80 83 width: 90,
  84 + ellipsis: true,
81 85 },
82 86 {
83 87 title: '标识符',
84 88 dataIndex: FormField.IDENTIFIER,
85 89 width: 90,
  90 + ellipsis: true,
86 91 customRender: ({ text }: Record<'text', string>) => {
87 92 return h(Tooltip, { title: text }, () =>
88 93 h('span', { class: 'cursor-pointer', onClick: () => handleCopy(text) }, text)
... ... @@ -96,6 +101,7 @@ export const physicalColumn: BasicColumn[] = [
96 101 format: (text: string) => {
97 102 return text || '--';
98 103 },
  104 + ellipsis: true,
99 105 },
100 106 {
101 107 title: '事件类型',
... ... @@ -109,6 +115,7 @@ export const physicalColumn: BasicColumn[] = [
109 115 () => EventTypeName[text as EventType]
110 116 );
111 117 },
  118 + ellipsis: true,
112 119 },
113 120 {
114 121 title: '状态',
... ... @@ -158,8 +165,33 @@ export const step1Schemas: FormSchema[] = [
158 165 {
159 166 field: 'image',
160 167 label: '上传图片',
161   - component: 'Input',
162   - slot: 'imageSelect',
  168 + component: 'ApiUpload',
  169 + changeEvent: 'update:fileList',
  170 + valueField: 'fileList',
  171 + componentProps: () => {
  172 + return {
  173 + listType: 'picture-card',
  174 + maxFileLimit: 1,
  175 + accept: '.png,.jpg,.jpeg,.gif',
  176 + api: async (file: File) => {
  177 + try {
  178 + const formData = new FormData();
  179 + formData.set('file', file);
  180 + const { fileStaticUri, fileName } = await uploadThumbnail(formData);
  181 + return {
  182 + uid: fileStaticUri,
  183 + name: fileName,
  184 + url: fileStaticUri,
  185 + } as FileItem;
  186 + } catch (error) {
  187 + return {};
  188 + }
  189 + },
  190 + onPreview: (fileList: FileItem) => {
  191 + createImgPreview({ imageList: [fileList.url!] });
  192 + },
  193 + };
  194 + },
163 195 },
164 196 {
165 197 field: 'deviceType',
... ...
1 1 <template>
2 2 <div class="step1">
3   - <BasicForm @register="register">
4   - <template #imageSelect>
5   - <Upload
6   - style="width: 20vw"
7   - name="avatar"
8   - accept=".png,.jpg,.jpeg,.gif"
9   - list-type="picture-card"
10   - class="avatar-uploader"
11   - :show-upload-list="false"
12   - :customRequest="customUploadqrcodePic"
13   - :before-upload="beforeUploadqrcodePic"
14   - >
15   - <img
16   - v-if="deviceConfigPic"
17   - :src="deviceConfigPic"
18   - alt=""
19   - style="width: 6.25rem; height: 6.25rem"
20   - />
21   - <div v-else>
22   - <LoadingOutlined v-if="loading" />
23   - <PlusOutlined v-else />
24   - <div class="ant-upload-text">图片上传</div>
25   - </div>
26   - </Upload>
27   - </template>
28   - </BasicForm>
  3 + <BasicForm @register="register" />
29 4 </div>
30 5 </template>
31 6 <script lang="ts" setup>
32   - import { ref, nextTick } from 'vue';
  7 + import { nextTick } from 'vue';
33 8 import { BasicForm, useForm } from '/@/components/Form';
34 9 import { step1Schemas } from '../device.profile.data';
35   - import { uploadApi } from '/@/api/personal/index';
36   - import { Upload } from 'ant-design-vue';
37   - import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
38   - import { useMessage } from '/@/hooks/web/useMessage';
39   - import type { FileItem } from '/@/components/Upload/src/typing';
  10 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  11 + import { buildUUID } from '/@/utils/uuid';
40 12
41 13 const emits = defineEmits(['next', 'emitDeviceType']);
42   - const loading = ref(false);
43   - const { createMessage } = useMessage();
44   - const deviceConfigPic = ref('');
45 14 const props = defineProps({
46 15 ifShowBtn: { type: Boolean, default: true },
47 16 });
... ... @@ -66,32 +35,6 @@
66 35 disabled: nameStatus,
67 36 },
68 37 });
69   - const customUploadqrcodePic = async ({ file }) => {
70   - if (beforeUploadqrcodePic(file)) {
71   - deviceConfigPic.value = '';
72   - loading.value = true;
73   - const formData = new FormData();
74   - formData.append('file', file);
75   - const response = await uploadApi(formData);
76   - if (response.fileStaticUri) {
77   - deviceConfigPic.value = response.fileStaticUri;
78   - loading.value = false;
79   - }
80   - }
81   - };
82   - const beforeUploadqrcodePic = (file: FileItem) => {
83   - const isJpgOrPng =
84   - file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg';
85   - if (!isJpgOrPng) {
86   - createMessage.error('只能上传图片文件!');
87   - }
88   - const isLt2M = (file.size as number) / 1024 / 1024 < 5;
89   - if (!isLt2M) {
90   - createMessage.error('图片大小不能超过5MB!');
91   - }
92   - return isJpgOrPng && isLt2M;
93   - };
94   -
95 38 const setFieldsdefaultRuleChainId = async (id) => {
96 39 await nextTick();
97 40 setFieldsValue({ defaultRuleChainId: id });
... ... @@ -105,20 +48,28 @@
105 48 }
106 49 //回显数据
107 50 const setFormData = (v) => {
108   - setFieldsValue(v);
109   - deviceConfigPic.value = v.image;
  51 + if (v.image) {
  52 + setFieldsValue({
  53 + image: [{ uid: buildUUID(), name: 'name', url: v.image } as FileItem],
  54 + });
  55 + }
  56 + const { image, ...params } = v;
  57 + console.log(image);
  58 + setFieldsValue({ ...params });
110 59 };
111 60 //获取数据
112 61 async function getFormData() {
113 62 const values = await validate();
114 63 if (!values) return;
115   - Reflect.set(values, 'image', deviceConfigPic.value);
  64 + if (Reflect.has(values, 'image')) {
  65 + const file = (values.image || []).at(0) || {};
  66 + values.image = file.url || null;
  67 + }
116 68 return values;
117 69 }
118 70 //清空数据
119 71 const resetFormData = () => {
120 72 resetFields();
121   - deviceConfigPic.value = '';
122 73 };
123 74
124 75 const editOrAddDeviceTypeStatus = (status: boolean) => {
... ...
... ... @@ -77,7 +77,7 @@ export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => {
77 77 span: 18,
78 78 },
79 79 componentProps: {
80   - maxLength: 255,
  80 + maxLength: 32,
81 81 placeholder: '请输入功能名称',
82 82 },
83 83 },
... ... @@ -90,7 +90,7 @@ export const serviceSchemas = (tcpDeviceFlag: boolean): FormSchema[] => {
90 90 span: 18,
91 91 },
92 92 componentProps: {
93   - maxLength: 255,
  93 + maxLength: 128,
94 94 placeholder: '请输入标识符',
95 95 },
96 96 },
... ... @@ -175,7 +175,7 @@ export const eventSchemas: FormSchema[] = [
175 175 span: 18,
176 176 },
177 177 componentProps: {
178   - maxLength: 255,
  178 + maxLength: 32,
179 179 placeholder: '请输入功能名称',
180 180 },
181 181 },
... ... @@ -188,7 +188,7 @@ export const eventSchemas: FormSchema[] = [
188 188 span: 18,
189 189 },
190 190 componentProps: {
191   - maxLength: 255,
  191 + maxLength: 128,
192 192 placeholder: '请输入标识符',
193 193 },
194 194 },
... ...
... ... @@ -17,7 +17,7 @@
17 17 </Authority>
18 18 </template>
19 19 <template #config="{ record }">
20   - <Authority value="api:yt:message:get:config">
  20 + <Authority value="api:yt:template:get">
21 21 <a-button type="link" class="ml-2" @click="showData(record)"> 查看配置 </a-button>
22 22 </Authority>
23 23 </template>
... ...
... ... @@ -78,6 +78,27 @@
78 78 componentProps: {
79 79 placeholder: '示例:{"code":"3654"}',
80 80 },
  81 + dynamicRules: () => {
  82 + return [
  83 + {
  84 + required: true,
  85 + validator: (_, value) => {
  86 + try {
  87 + if (typeof value == 'object') {
  88 + return Promise.resolve();
  89 + } else {
  90 + if (typeof JSON.parse(value) == 'object') {
  91 + return Promise.resolve();
  92 + }
  93 + return Promise.reject('请输入JSON格式例如{"code":"123"}');
  94 + }
  95 + } catch {
  96 + return Promise.reject('请输入JSON格式例如{"code":"123"}');
  97 + }
  98 + },
  99 + },
  100 + ];
  101 + },
81 102 },
82 103 {
83 104 field: 'remark',
... ...
... ... @@ -131,14 +131,19 @@ export const formSchema: FormSchema[] = [
131 131 onChange: async (value) => {
132 132 const res = await findMessageConfig({});
133 133 let typeId: Nullable<string> = null;
134   - const options = res.map((item) => {
135   - if (item.messageType === value) typeId = item.id;
136   - return {
137   - disabled: item.messageType !== value,
138   - label: item.configName,
139   - value: item.id,
140   - };
141   - });
  134 + const options = res
  135 + .map((item) => {
  136 + if (item.messageType === value && item.status === 1) {
  137 + typeId = item.id;
  138 + }
  139 + return {
  140 + disabled: item.messageType !== value,
  141 + label: item.configName,
  142 + value: item.id,
  143 + status: item.status,
  144 + };
  145 + })
  146 + .filter((item) => item.status === 1);
142 147 await formActionType.setFieldsValue({ messageConfigId: typeId });
143 148 await formActionType.updateSchema({
144 149 field: 'messageConfigId',
... ... @@ -155,14 +160,25 @@ export const formSchema: FormSchema[] = [
155 160 label: '配置名称',
156 161 required: true,
157 162 component: 'ApiSelect',
158   - componentProps: {
159   - api: findMessageConfig,
160   - params: {
161   - messageType: ({ values }) => Reflect.get(values, 'messageType'),
162   - },
163   - immediate: true,
164   - labelField: 'configName',
165   - valueField: 'id',
  163 + componentProps: () => {
  164 + return {
  165 + api: async (params: Recordable) => {
  166 + try {
  167 + const record = await findMessageConfig(params);
  168 + return record.filter((item) => item.status === 1);
  169 + } catch (error) {
  170 + console.log(error);
  171 + return [];
  172 + }
  173 + },
  174 + params: {
  175 + messageType: ({ values }) => Reflect.get(values, 'messageType'),
  176 + },
  177 + immediate: true,
  178 + labelField: 'configName',
  179 + valueField: 'id',
  180 + getPopupContainer: () => document.body,
  181 + };
166 182 },
167 183 },
168 184 {
... ...
... ... @@ -465,14 +465,15 @@ export const formSchema: BFormSchema[] = [
465 465 },
466 466 {
467 467 field: 'dateGroupGap',
468   - label: '分组间隔',
  468 + label: '间隔时间',
469 469 component: 'Select',
470 470 colProps: { span: 24 },
471   - dynamicRules: ({ model }) => {
  471 + dynamicRules: () => {
472 472 return [
473 473 {
474   - required: model[SchemaFiled.AGG] !== AggregateDataEnum.NONE,
475   - message: '分组间隔为必填项',
  474 + // required: model[SchemaFiled.AGG] !== AggregateDataEnum.NONE,
  475 + required: true,
  476 + message: '间隔时间为必填项',
476 477 type: 'number',
477 478 },
478 479 ];
... ...
... ... @@ -16,8 +16,9 @@
16 16 import { BasicInfoFormField } from './enum';
17 17 import { BasicInfoRecord } from './types';
18 18 import { Button } from 'ant-design-vue';
  19 + import { ref, unref } from 'vue';
19 20
20   - const props = defineProps({
  21 + defineProps({
21 22 saveContent: {
22 23 type: Function,
23 24 },
... ... @@ -25,8 +26,9 @@
25 26
26 27 const emit = defineEmits(['currentDataFlowMethodEmitNext']);
27 28
  29 + const disabled = ref<boolean>(false);
28 30 const [register, { validateFields, setFieldsValue, resetFields, setProps }] = useForm({
29   - schemas: modeForm(props.saveContent),
  31 + schemas: modeForm(unref(disabled)),
30 32 ...modelFormPublicConfig,
31 33 });
32 34
... ... @@ -61,12 +63,21 @@
61 63 emit('currentDataFlowMethodEmitNext', getValue());
62 64 };
63 65
  66 + const setDisabledProps = (value) => {
  67 + setProps(value);
  68 + disabled.value = false;
  69 + };
  70 + const setCancelDisabled = () => {
  71 + disabled.value = false;
  72 + };
  73 +
64 74 const resetValue = () => resetFields();
65 75 defineExpose({
66 76 getValue,
67 77 setValue,
68 78 resetValue,
69   - setProps,
  79 + setDisabledProps,
  80 + setCancelDisabled,
70 81 });
71 82 </script>
72 83 <style lang="less" scoped>
... ...
... ... @@ -75,7 +75,9 @@
75 75 const { text, record } = data;
76 76 businessText.value = text;
77 77 if (businessText.value == BusinessDataFlowTextEnum.BUSINESS_MODAL_VIEW_TEXT) {
78   - dataFlowMethodRef.value?.setProps({ disabled: true });
  78 + dataFlowMethodRef.value?.setDisabledProps({ disabled: true });
  79 + } else {
  80 + dataFlowMethodRef.value?.setCancelDisabled();
79 81 }
80 82 restData.data = record;
81 83 setModalProps(modalProps(businessText.value));
... ...
... ... @@ -28,6 +28,7 @@
28 28 openModalValidate?: () => boolean;
29 29 primaryKey?: string;
30 30 transformValue?: (list: Recordable[]) => any;
  31 + disabled?: boolean;
31 32 }>(),
32 33 {
33 34 value: () => [],
... ... @@ -290,7 +291,7 @@
290 291 </Tabs>
291 292 </section>
292 293 </BasicModal>
293   - <Button @click="handleOpenModal" type="link">
  294 + <Button @click="handleOpenModal" type="link" :disabled="disabled">
294 295 <span v-if="!selectedTotalList.length">选择设备</span>
295 296 <div v-if="selectedTotalList.length">
296 297 <Tag
... ...
... ... @@ -33,7 +33,7 @@ export const modelFormPublicConfig = {
33 33 showSubmitButton: false,
34 34 };
35 35
36   -export const modeForm = (): FormSchema[] => {
  36 +export const modeForm = (disabled: boolean): FormSchema[] => {
37 37 return [
38 38 {
39 39 field: BasicInfoFormField.CONVERT_CONFIG_ID,
... ... @@ -70,6 +70,7 @@ export const modeForm = (): FormSchema[] => {
70 70 api: getDeviceProfile,
71 71 labelField: 'name',
72 72 valueField: 'tbProfileId',
  73 + disabled,
73 74 transferProps: {
74 75 listStyle: { height: '400px' },
75 76 showSearch: true,
... ... @@ -98,6 +99,7 @@ export const modeForm = (): FormSchema[] => {
98 99 componentProps: ({ formActionType }) => {
99 100 const { getFieldsValue } = formActionType;
100 101 return {
  102 + disabled,
101 103 getPendingTableParams: () => {
102 104 const values = getFieldsValue();
103 105 const convertConfigId = Reflect.get(values, BasicInfoFormField.CONVERT_CONFIG_ID);
... ...
1   -<template>
2   - <div class="transfer-config-mode">
3   - <BasicForm :showSubmitButton="false" @register="register">
4   - <template #uploadAdd1="{ field }">
5   - <span style="display: none">{{ field }}</span>
6   - <a-upload-dragger
7   - v-model:fileList="fileList1"
8   - name="file"
9   - :key="1"
10   - :multiple="false"
11   - @change="handleChange('T', $event)"
12   - :before-upload="() => false"
13   - >
14   - <p class="ant-upload-drag-icon">
15   - <InboxOutlined />
16   - </p>
17   - <p class="ant-upload-text">点击或将文件拖拽到这里上传</p>
18   - <p class="ant-upload-hint">
19   - 支持扩展名:.jpeg .png .jpg ...
20   - <br />
21   - 文件大小:最大支持5M
22   - </p>
23   - </a-upload-dragger>
24   - </template>
25   - <template #showImg1="{ field }">
26   - <span style="display: none">{{ field }}</span>
27   - <img
28   - v-if="showImg1"
29   - :src="showImg1Pic"
30   - alt="avatar"
31   - style="width: 6.25rem; height: 6.25rem"
32   - />
33   - </template>
34   - <div style="margin-top: 50px"></div>
35   - <template #uploadAdd2="{ field }">
36   - <span style="display: none">{{ field }}</span>
37   - <a-upload-dragger
38   - v-model:fileList="fileList2"
39   - name="file"
40   - :key="2"
41   - :multiple="false"
42   - @change="handleChange('F', $event)"
43   - :before-upload="() => false"
44   - >
45   - <p class="ant-upload-drag-icon">
46   - <InboxOutlined />
47   - </p>
48   - <p class="ant-upload-text">点击或将文件拖拽到这里上传</p>
49   - <p class="ant-upload-hint">
50   - 支持扩展名:.jpeg .png .jpg ...
51   - <br />
52   - 文件大小:最大支持5M
53   - </p>
54   - </a-upload-dragger>
55   - </template>
56   - <template #showImg2="{ field }">
57   - <span style="display: none">{{ field }}</span>
58   - <img
59   - v-if="showImg2"
60   - :src="showImg2Pic"
61   - alt="avatar"
62   - style="width: 6.25rem; height: 6.25rem"
63   - />
64   - </template>
65   - <div style="margin-top: 50px"></div>
66   - <template #uploadAdd3="{ field }">
67   - <span style="display: none">{{ field }}</span>
68   - <a-upload-dragger
69   - v-model:fileList="fileList3"
70   - name="file"
71   - :key="3"
72   - :multiple="false"
73   - @change="handleChange('C', $event)"
74   - :before-upload="() => false"
75   - >
76   - <p class="ant-upload-drag-icon">
77   - <InboxOutlined />
78   - </p>
79   - <p class="ant-upload-text">点击或将文件拖拽到这里上传</p>
80   - <p class="ant-upload-hint">
81   - 支持扩展名:.jpeg .png .jpg ...
82   - <br />
83   - 文件大小:最大支持5M
84   - </p>
85   - </a-upload-dragger>
86   - </template>
87   - <template #showImg3="{ field }">
88   - <span style="display: none">{{ field }}</span>
89   - <img
90   - v-if="showImg3"
91   - :src="showImg3Pic"
92   - alt="avatar"
93   - style="width: 6.25rem; height: 6.25rem"
94   - />
95   - </template>
96   - </BasicForm>
97   - </div>
98   -</template>
99   -<script lang="ts">
100   - import { defineComponent, ref, reactive, nextTick } from 'vue';
101   - import { BasicForm, useForm } from '/@/components/Form';
102   - import { CredentialsEnum, modeMqttForm } from '../config';
103   - import { InboxOutlined } from '@ant-design/icons-vue';
104   - import { Alert, Divider, Descriptions, Upload } from 'ant-design-vue';
105   - import { uploadApi } from '/@/api/personal/index';
106   - import { useMessage } from '/@/hooks/web/useMessage';
107   -
108   - export default defineComponent({
109   - components: {
110   - BasicForm,
111   - [Alert.name]: Alert,
112   - [Divider.name]: Divider,
113   - [Descriptions.name]: Descriptions,
114   - [Descriptions.Item.name]: Descriptions.Item,
115   - InboxOutlined,
116   - [Upload.Dragger.name]: Upload.Dragger,
117   - },
118   - emits: ['next', 'prev', 'register'],
119   - setup(_, { emit }) {
120   - const showImg1 = ref(false);
121   - const showImg1Pic = ref('');
122   - const showImg2 = ref(false);
123   - const showImg2Pic = ref('');
124   - const showImg3 = ref(false);
125   - const showImg3Pic = ref('');
126   - const { createMessage } = useMessage();
127   - let caCertFileName = ref('');
128   - let privateKeyFileName = ref('');
129   - let certFileName = ref('');
130   - let fileList1: any = ref<[]>([]);
131   - let fileList2: any = ref<[]>([]);
132   - let fileList3: any = ref<[]>([]);
133   - const credentialsV: any = reactive({
134   - credentials: {
135   - type: '',
136   - },
137   - });
138   - const sonValues: any = reactive({
139   - configuration: {},
140   - });
141   - const [register, { validate, setFieldsValue, resetFields: defineClearFunc }] = useForm({
142   - labelWidth: 120,
143   - schemas: modeMqttForm,
144   - actionColOptions: {
145   - span: 14,
146   - },
147   - resetButtonOptions: {
148   - text: '上一步',
149   - },
150   - resetFunc: customResetFunc,
151   - submitFunc: customSubmitFunc,
152   - });
153   -
154   - /**
155   - * 上传图片
156   - */
157   - const handleChange = async (e, { file }) => {
158   - if (file.status === 'removed') {
159   - if (e == 'T') {
160   - fileList1.value = [];
161   - showImg1.value = false;
162   - showImg1Pic.value = '';
163   - caCertFileName.value = '';
164   - } else if (e == 'F') {
165   - fileList2.value = [];
166   - showImg2.value = false;
167   - showImg2Pic.value = '';
168   - certFileName.value = '';
169   - } else {
170   - fileList3.value = [];
171   - showImg3.value = false;
172   - showImg3Pic.value = '';
173   - privateKeyFileName.value = '';
174   - }
175   - } else {
176   - const isLt5M = file.size / 1024 / 1024 < 5;
177   - if (!isLt5M) {
178   - createMessage.error('图片大小不能超过5MB!');
179   - } else {
180   - e == 'T'
181   - ? (fileList1.value = [file])
182   - : e == 'F'
183   - ? (fileList2.value = [file])
184   - : (fileList3.value = [file]);
185   - const formData = new FormData();
186   - formData.append('file', file);
187   - const response = await uploadApi(formData);
188   - if (response.fileStaticUri) {
189   - if (e == 'T') {
190   - caCertFileName.value = response.fileStaticUri;
191   - const iscaCertFileNamePic = caCertFileName.value.split('.').pop();
192   - if (
193   - iscaCertFileNamePic == 'jpg' ||
194   - iscaCertFileNamePic == 'png' ||
195   - iscaCertFileNamePic == 'jpeg' ||
196   - iscaCertFileNamePic == 'gif'
197   - ) {
198   - showImg1.value = true;
199   - showImg1Pic.value = response.fileStaticUri;
200   - } else {
201   - showImg1.value = false;
202   - }
203   - } else if (e == 'F') {
204   - certFileName.value = response.fileStaticUri;
205   - const iscertFileNamePic = certFileName.value.split('.').pop();
206   - if (
207   - iscertFileNamePic == 'jpg' ||
208   - iscertFileNamePic == 'png' ||
209   - iscertFileNamePic == 'jpeg' ||
210   - iscertFileNamePic == 'gif'
211   - ) {
212   - showImg2.value = true;
213   - showImg2Pic.value = response.fileStaticUri;
214   - } else {
215   - showImg2.value = false;
216   - }
217   - } else {
218   - privateKeyFileName.value = response.fileStaticUri;
219   - const isprivateKeyFileNamePic = privateKeyFileName.value.split('.').pop();
220   - if (
221   - isprivateKeyFileNamePic == 'jpg' ||
222   - isprivateKeyFileNamePic == 'png' ||
223   - isprivateKeyFileNamePic == 'jpeg' ||
224   - isprivateKeyFileNamePic == 'gif'
225   - ) {
226   - showImg3.value = true;
227   - showImg3Pic.value = response.fileStaticUri;
228   - } else {
229   - showImg3.value = false;
230   - }
231   - }
232   - }
233   - }
234   - }
235   - };
236   - const setStepTwoFieldsValueFunc = (v, v1, v2) => {
237   - setFieldsValue(v);
238   - setFieldsValue({
239   - name: v1,
240   - description: v2,
241   - });
242   - setFieldsValue({
243   - password: v.credentials?.password,
244   - username: v.credentials?.username,
245   - type: v.credentials?.type,
246   - });
247   - fileList1.value = [
248   - {
249   - name: v.credentials?.caCertFileName.slice(39),
250   - uid: '1',
251   - },
252   - ];
253   - fileList2.value = [
254   - {
255   - name: v.credentials?.certFileName.slice(39),
256   - uid: '2',
257   - },
258   - ];
259   - fileList3.value = [
260   - {
261   - name: v.credentials?.privateKeyFileName.slice(39),
262   - uid: '3',
263   - },
264   - ];
265   - caCertFileName.value = v.credentials?.caCertFileName;
266   - certFileName.value = v.credentials?.certFileName;
267   - privateKeyFileName.value = v.credentials?.privateKeyFileName;
268   - const iscaCertFileNamePic = v.credentials?.caCertFileName.split('.').pop();
269   - const iscertFileNamePic = v.credentials?.certFileName.split('.').pop();
270   - const isprivateKeyFileNamePic = v.credentials?.privateKeyFileName.split('.').pop();
271   - if (
272   - iscaCertFileNamePic == 'jpg' ||
273   - iscaCertFileNamePic == 'png' ||
274   - iscaCertFileNamePic == 'jpeg' ||
275   - iscaCertFileNamePic == 'gif'
276   - ) {
277   - showImg1.value = true;
278   - showImg1Pic.value = v.credentials?.caCertFileName;
279   - } else {
280   - showImg1.value = false;
281   - }
282   - if (
283   - iscertFileNamePic == 'jpg' ||
284   - iscertFileNamePic == 'png' ||
285   - iscertFileNamePic == 'jpeg' ||
286   - iscertFileNamePic == 'gif'
287   - ) {
288   - showImg2.value = true;
289   - showImg2Pic.value = v.credentials?.certFileName;
290   - } else {
291   - showImg2.value = false;
292   - }
293   - if (
294   - isprivateKeyFileNamePic == 'jpg' ||
295   - isprivateKeyFileNamePic == 'png' ||
296   - isprivateKeyFileNamePic == 'jpeg' ||
297   - isprivateKeyFileNamePic == 'gif'
298   - ) {
299   - showImg3.value = true;
300   - showImg3Pic.value = v.credentials?.privateKeyFileName;
301   - } else {
302   - showImg3.value = false;
303   - }
304   - };
305   - const customClearStepTwoValueFunc = async () => {
306   - nextTick(() => {
307   - defineClearFunc();
308   - fileList1.value = [];
309   - fileList2.value = [];
310   - fileList3.value = [];
311   - caCertFileName.value = '';
312   - privateKeyFileName.value = '';
313   - certFileName.value = '';
314   - showImg1.value = false;
315   - showImg1Pic.value = '';
316   - showImg2.value = false;
317   - showImg2Pic.value = '';
318   - showImg3.value = false;
319   - showImg3Pic.value = '';
320   - });
321   - };
322   - async function customResetFunc() {
323   - emit('prev');
324   - }
325   - async function customSubmitFunc() {
326   - try {
327   - const values = await validate();
328   - emit('next', values);
329   - } catch (error) {
330   - } finally {
331   - }
332   - }
333   - const getSonValueFunc = async () => {
334   - sonValues.configuration = await validate();
335   - credentialsV.credentials.type = sonValues.configuration.type;
336   - if (credentialsV.credentials.type == CredentialsEnum.IS_BASIC) {
337   - credentialsV.credentials.username = sonValues.configuration.username;
338   - credentialsV.credentials.password = sonValues.configuration.password;
339   - sonValues.configuration.username = undefined;
340   - sonValues.configuration.password = undefined;
341   - } else if (credentialsV.credentials.type == CredentialsEnum.IS_PEM) {
342   - credentialsV.credentials.caCertFileName = caCertFileName.value;
343   - credentialsV.credentials.certFileName = certFileName.value;
344   - credentialsV.credentials.privateKeyFileName = privateKeyFileName.value;
345   - }
346   - if (!sonValues.configuration.clientId) {
347   - sonValues.configuration.clientId = null;
348   - }
349   - Object.assign(sonValues.configuration, credentialsV);
350   - return sonValues;
351   - };
352   - return {
353   - getSonValueFunc,
354   - register,
355   - setStepTwoFieldsValueFunc,
356   - customClearStepTwoValueFunc,
357   - fileList1,
358   - fileList2,
359   - fileList3,
360   - handleChange,
361   - caCertFileName,
362   - privateKeyFileName,
363   - certFileName,
364   - showImg1,
365   - showImg1Pic,
366   - showImg2,
367   - showImg2Pic,
368   - showImg3,
369   - showImg3Pic,
370   - };
371   - },
372   - });
373   -</script>
374   -
375   -<style lang="less" scoped>
376   - :deep(.ant-col-24) {
377   - margin-bottom: 20px !important;
378   - }
379   -
380   - :deep(.ant-btn-default) {
381   - color: white;
382   - background: #377dff;
383   - }
384   -</style>
... ... @@ -201,14 +201,26 @@
201 201 ];
202 202 });
203 203
204   - const changeOutTarget = () => {
  204 + const changeOutTarget = (e) => {
  205 + if (!e) validateFields(['outTarget']);
  206 + else clearValidate('outTarget');
205 207 emit('getActionFormArr');
206 208 };
207   - const [registerAction, { getFieldsValue, resetFields, setFieldsValue, validate, setProps }] =
208   - useForm({
209   - schemas: actionSchema,
210   - showActionButtonGroup: false,
211   - });
  209 + const [
  210 + registerAction,
  211 + {
  212 + getFieldsValue,
  213 + resetFields,
  214 + setFieldsValue,
  215 + validate,
  216 + clearValidate,
  217 + validateFields,
  218 + setProps,
  219 + },
  220 + ] = useForm({
  221 + schemas: actionSchema,
  222 + showActionButtonGroup: false,
  223 + });
212 224
213 225 // 获取整个执行动作表单值
214 226 const getFieldsValueFunc = () => {
... ...
... ... @@ -114,7 +114,7 @@
114 114 </div>
115 115 </template>
116 116 <script setup lang="ts">
117   - import { ref, unref, reactive, onMounted, toRefs, computed } from 'vue';
  117 + import { ref, unref, reactive, onMounted, toRefs, computed, nextTick } from 'vue';
118 118 import ace from 'ace-builds';
119 119 import { Card, Button, Tooltip } from 'ant-design-vue';
120 120 import 'ace-builds/src-noconflict/theme-chrome'; // 默认设置的主题
... ... @@ -323,7 +323,19 @@
323 323 };
324 324
325 325 const setDisableRadio = (value) => {
326   - reportTypeOptions.scriptTypeOptions.forEach((item: any) => {
  326 + //查看和表格里面的测试点击禁用脚本类型
  327 + unref(reportTypeOptions.scriptTypeOptions).forEach((item: any) => {
  328 + if (item.value === value) item.disabled = false;
  329 + else item.disabled = true;
  330 + });
  331 + };
  332 +
  333 + const setDisableTestRadio = async (value) => {
  334 + //内部弹窗,使用上面的setDisableRadio无效
  335 + //新增里面的测试点击禁用脚本类型
  336 + await getScriptType();
  337 + await nextTick();
  338 + unref(reportTypeOptions.scriptTypeOptions).forEach((item: any) => {
327 339 if (item.value === value) item.disabled = false;
328 340 else item.disabled = true;
329 341 });
... ... @@ -336,6 +348,7 @@
336 348 setScriptOutputData,
337 349 setDefaultRadio,
338 350 setDisableRadio,
  351 + setDisableTestRadio,
339 352 });
340 353 </script>
341 354 <style lang="less" scoped>
... ...
... ... @@ -56,7 +56,15 @@
56 56 if (!data.innerTest) {
57 57 const rest = await getScriptManageDetail(data.record?.id);
58 58 converScriptFormRef.value?.setFormData(rest);
59   - } else converScriptFormRef.value?.setFormData(data.record);
  59 + if (data.text !== BusinessConvertScriptTextEnum.BUSINESS_EDIT_TEXT) {
  60 + //编辑是不能禁用脚本类型的
  61 + converScriptFormRef.value?.setDisableRadio(data.record.scriptType);
  62 + }
  63 + } else {
  64 + //从新增页面里点击的测试,禁用脚本类型
  65 + converScriptFormRef.value?.setFormData(data.record);
  66 + converScriptFormRef.value?.setDisableTestRadio(data.record.scriptType);
  67 + }
60 68 if (data.scriptType) {
61 69 converScriptFormRef.value?.setDisableRadio(data.scriptType);
62 70 }
... ...
... ... @@ -18,6 +18,7 @@
18 18 ref="basicTreeRef"
19 19 checkable
20 20 toolbar
  21 + @change="handleTreeSelect"
21 22 />
22 23 </template>
23 24 <template #roleSlot="{ model, field }">
... ... @@ -26,6 +27,7 @@
26 27 allowClear
27 28 placeholder="请选择角色"
28 29 v-model:value="model[field]"
  30 + @change="handleRoleSelect"
29 31 :options="roleOptions.map((item) => ({ value: item.value, label: item.label }))"
30 32 >
31 33 <template #dropdownRender="{ menuNode: menu }">
... ... @@ -108,12 +110,30 @@
108 110 isUpdate: false,
109 111 });
110 112 };
  113 + const clearValidateByField = (field: string) => {
  114 + clearValidate(field);
  115 + };
  116 + const handleRoleSelect = (e) => {
  117 + if (e?.length > 0) clearValidateByField('roleIds');
  118 + else validateFields(['roleIds']);
  119 + };
  120 + const handleTreeSelect = (e) => {
  121 + if (e) clearValidateByField('organizationIds');
  122 + };
111 123 const handleSuccess = async () => {
112 124 await getRoleList();
113 125 };
114 126 const [
115 127 registerForm,
116   - { setFieldsValue, updateSchema, resetFields, validate, getFieldsValue },
  128 + {
  129 + setFieldsValue,
  130 + updateSchema,
  131 + resetFields,
  132 + validate,
  133 + getFieldsValue,
  134 + clearValidate,
  135 + validateFields,
  136 + },
117 137 ] = useForm({
118 138 labelWidth: 100,
119 139 schemas: accountFormSchema,
... ... @@ -225,6 +245,8 @@
225 245 registerRoleDrawer,
226 246 handleOpenRole,
227 247 handleSuccess,
  248 + handleRoleSelect,
  249 + handleTreeSelect,
228 250 };
229 251 },
230 252 });
... ...
... ... @@ -35,6 +35,7 @@
35 35 const { proxy } = getCurrentInstance() as any;
36 36 const getChildData = ref(null);
37 37 const editGetId: any = ref('');
  38 + const isDefault = ref(false);
38 39 const createTime = ref<string>('');
39 40 const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
40 41 schemas: formSchema,
... ... @@ -55,6 +56,7 @@
55 56 parentSetData.value = { ...data.record.profileData.configuration };
56 57 proxy.$refs.getChildData.setFieldsValueFunc(parentSetData.value);
57 58 editGetId.value = data.record.id;
  59 + isDefault.value = data.record.default;
58 60 await setFieldsValue({
59 61 ...data.record,
60 62 });
... ... @@ -95,6 +97,10 @@
95 97 const createTime1 = {
96 98 createdTime: isUpdate ? unref(createTime) : Date.now(),
97 99 };
  100 +
  101 + const defaultInfo = {
  102 + default: unref(isUpdate) ? isDefault.value : false,
  103 + };
98 104 Object.assign(
99 105 postAllData,
100 106 {
... ... @@ -102,7 +108,8 @@
102 108 },
103 109 getValuesFormData,
104 110 id,
105   - createTime1
  111 + createTime1,
  112 + defaultInfo
106 113 );
107 114 if (!unref(isUpdate)) {
108 115 delete postAllData.id;
... ...
... ... @@ -43,11 +43,11 @@
43 43 },
44 44 {
45 45 field: 'username',
46   - label: '账号',
  46 + label: '用户名',
47 47 component: 'Input',
48 48 componentProps: {
49 49 maxLength: 64,
50   - placeholder: '请输入账号',
  50 + placeholder: '请输入用户名',
51 51 },
52 52 dynamicRules: ({ values }) => {
53 53 try {
... ... @@ -62,14 +62,13 @@
62 62 validator(_, value) {
63 63 return new Promise((resolve, reject) => {
64 64 if (value == '' || value === undefined) {
65   - reject('请输入账号');
  65 + reject('请输入用户名');
66 66 } else if (ChineseRegexp.test(value)) {
67   - reject('账号不能含有中文');
  67 + reject('用户名不能含有中文');
68 68 } else if (EmailRegexp.test(value)) {
69   - reject('账号不能为电子邮箱格式');
  69 + reject('用户名不能为电子邮箱格式');
70 70 } else if (findUserName && value == findUserName?.username) {
71   - console.log(1111111111);
72   - reject('账号已存在');
  71 + reject('用户名已存在');
73 72 return;
74 73 } else {
75 74 resolve();
... ...
... ... @@ -7,50 +7,25 @@
7 7 width="500px"
8 8 @ok="handleSubmit"
9 9 >
10   - <BasicForm @register="tenantForm">
11   - <template #iconSelect>
12   - <Upload
13   - name="avatar"
14   - accept=".png,.jpg,.jpeg,.gif"
15   - list-type="picture-card"
16   - class="avatar-uploader"
17   - :show-upload-list="false"
18   - :customRequest="customUpload"
19   - :before-upload="beforeUpload"
20   - >
21   - <img v-if="tenantLogo" :src="tenantLogo" alt="avatar" />
22   - <div v-else>
23   - <LoadingOutlined v-if="loading" />
24   - <plus-outlined v-else />
25   - <div class="ant-upload-text">上传</div>
26   - </div>
27   - </Upload>
28   - </template>
29   - </BasicForm>
  10 + <BasicForm @register="tenantForm" />
30 11 </BasicDrawer>
31 12 </template>
32 13 <script lang="ts">
33 14 import { defineComponent, ref, computed, unref } from 'vue';
34 15 import { BasicForm, useForm } from '/@/components/Form/index';
35 16 import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
36   - import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
37   - import { message, Upload } from 'ant-design-vue';
38   -
39 17 import { useI18n } from '/@/hooks/web/useI18n';
40 18 import { tenantFormSchema } from '/@/views/tenant/list/tenantBaseColumns';
41   - import { FileItem } from '/@/components/Upload/src/typing';
42   - import { upload } from '/@/api/oss/ossFileUploader';
43 19 import { getTenantRoles, updateOrCreateTenant } from '/@/api/tenant/tenantApi';
44 20 import { useMessage } from '/@/hooks/web/useMessage';
  21 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  22 + import { buildUUID } from '/@/utils/uuid';
45 23
46 24 export default defineComponent({
47 25 name: 'TenantDrawer',
48 26 components: {
49 27 BasicDrawer,
50 28 BasicForm,
51   - Upload,
52   - PlusOutlined,
53   - LoadingOutlined,
54 29 },
55 30 emits: ['success', 'register'],
56 31 setup(_, { emit }) {
... ... @@ -58,33 +33,7 @@
58 33 const { createMessage } = useMessage();
59 34
60 35 const isUpdate = ref(true);
61   - const tenantLogo = ref('');
62   -
63   - async function customUpload({ file }) {
64   - if (beforeUpload(file)) {
65   - tenantLogo.value = '';
66   - loading.value = true;
67   - const formData = new FormData();
68   - formData.append('file', file);
69   - const response = await upload(formData);
70   - if (response.fileStaticUri) {
71   - tenantLogo.value = response.fileStaticUri;
72   - loading.value = false;
73   - }
74   - }
75   - }
76 36
77   - const beforeUpload = (file: FileItem) => {
78   - const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
79   - if (!isJpgOrPng) {
80   - message.error('只能上传图片文件!');
81   - }
82   - const isLt2M = (file.size as number) / 1024 / 1024 < 5;
83   - if (!isLt2M) {
84   - message.error('图片大小不能超过5MB!');
85   - }
86   - return isJpgOrPng && isLt2M;
87   - };
88 37 const [tenantForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({
89 38 labelWidth: 100,
90 39 schemas: tenantFormSchema,
... ... @@ -96,7 +45,6 @@
96 45 //默认传递页面数据
97 46 const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
98 47 await resetFields();
99   - tenantLogo.value = '';
100 48 setDrawerProps({ confirmLoading: false });
101 49 isUpdate.value = !!data?.isUpdate;
102 50
... ... @@ -104,11 +52,19 @@
104 52 await updateSchema({ field: 'title', componentProps: { disabled: false } });
105 53 //如果是编辑操作,设置页面数据
106 54 if (unref(isUpdate)) {
  55 + if (data.record.icon) {
  56 + setFieldsValue({
  57 + icon: [{ uid: buildUUID(), name: 'name', url: data.record.icon } as FileItem],
  58 + });
  59 + }
107 60 getTenantRoles(data.record.tenantId).then((result) => {
108   - Reflect.set(data.record, 'roleIds', result);
  61 + const { icon, ...params } = data.record;
  62 + console.log(icon);
109 63 //为表单赋值
110   - setFieldsValue(data.record);
111   - tenantLogo.value = data.record.icon;
  64 + setFieldsValue({
  65 + ...params,
  66 + roleIds: result,
  67 + });
112 68 //编辑模式,菜单名称为不可用
113 69 updateSchema({ field: 'title', componentProps: { disabled: true } });
114 70 });
... ... @@ -126,9 +82,13 @@
126 82 setDrawerProps({ confirmLoading: true });
127 83 try {
128 84 const values = await validate();
  85 + if (Reflect.has(values, 'icon')) {
  86 + const file = (values.icon || []).at(0) || {};
  87 + values.icon = file.url || null;
  88 + }
129 89 const req = {
130 90 id: values.id,
131   - icon: tenantLogo.value,
  91 + icon: values.icon,
132 92 name: values.name,
133 93 enabled: values.enabled,
134 94 description: values.description,
... ... @@ -161,9 +121,6 @@
161 121 tenantForm,
162 122 getTitle,
163 123 handleSubmit,
164   - tenantLogo,
165   - beforeUpload,
166   - customUpload,
167 124 loading,
168 125 };
169 126 },
... ...
... ... @@ -3,6 +3,9 @@ import { FormSchema } from '/@/components/Form';
3 3 import { getAllRoleList } from '/@/api/system/system';
4 4 import { getTableTenantProfileApi, QueryTenantProfilesParam } from '/@/api/tenant/tenantApi';
5 5 import { RoleEnum } from '/@/enums/roleEnum';
  6 +import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  7 +import { createImgPreview } from '/@/components/Preview';
  8 +import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
6 9
7 10 export function getBasicColumns(): BasicColumn[] {
8 11 return [
... ... @@ -77,23 +80,32 @@ export const tenantFormSchema: FormSchema[] = [
77 80 {
78 81 field: 'icon',
79 82 label: '租户图标',
80   - slot: 'iconSelect',
81   - component: 'Input',
82   - componentProps: {
83   - maxLength: 255,
84   - },
85   - dynamicRules: () => {
86   - return [
87   - {
88   - required: false,
89   - validator: (_, value) => {
90   - if (String(value).length > 255) {
91   - return Promise.reject('字数不超过255个字');
92   - }
93   - return Promise.resolve();
94   - },
  83 + component: 'ApiUpload',
  84 + changeEvent: 'update:fileList',
  85 + valueField: 'fileList',
  86 + componentProps: () => {
  87 + return {
  88 + listType: 'picture-card',
  89 + maxFileLimit: 1,
  90 + accept: '.png,.jpg,.jpeg,.gif',
  91 + api: async (file: File) => {
  92 + try {
  93 + const formData = new FormData();
  94 + formData.set('file', file);
  95 + const { fileStaticUri, fileName } = await uploadThumbnail(formData);
  96 + return {
  97 + uid: fileStaticUri,
  98 + name: fileName,
  99 + url: fileStaticUri,
  100 + } as FileItem;
  101 + } catch (error) {
  102 + return {};
  103 + }
95 104 },
96   - ];
  105 + onPreview: (fileList: FileItem) => {
  106 + createImgPreview({ imageList: [fileList.url!] });
  107 + },
  108 + };
97 109 },
98 110 },
99 111 {
... ...
... ... @@ -169,8 +169,6 @@
169 169 }
170 170 );
171 171
172   - const { containerEl } = useSort(emit, getFormValues);
173   -
174 172 const handleSettingOk = (data: DataSourceType) => {
175 173 const { uuid } = data;
176 174 const _dataSource = cloneDeep(getFormValues());
... ... @@ -182,6 +180,8 @@
182 180 emit('update:dataSource', _dataSource);
183 181 };
184 182
  183 + const { containerEl } = useSort(emit, getFormValues);
  184 +
185 185 defineExpose({
186 186 getFormValues,
187 187 validate,
... ...
... ... @@ -267,6 +267,7 @@
267 267 <BasicModal
268 268 title="自定义组件"
269 269 width="70%"
  270 + :destroy-on-close="true"
270 271 @register="registerModal"
271 272 @ok="handleSubmit"
272 273 :ok-button-props="{ loading }"
... ...