活动介绍

<script setup lang="ts"> import { onMounted, reactive, ref, watch } from "vue"; import { $t } from "@/locales"; import { UploadFileInfo, UploadInst } from "naive-ui"; import { exportXlsx, SheetData } from "@/utils/export-excel"; import { fetchAddInterlock, fetchUpdateInterlock, fetchUploadInterlockLedger} from "@/service/api"; import { useEquipment } from "@/hooks/common/useEquipment"; defineOptions({ name: "LedgerUploadDrawer", }); interface Props { /** the type of operation */ } // 引入设备钩子 const { loading: equipLoading, filteredequipOptions, handleequipSearch, loadEquipMap, getequip, nameToequipMap, resetEquipSearch } = useEquipment(); onMounted(() => { loadEquipMap(); }); const props = defineProps<Props>(); const fileListLengthRef = ref(0); const uploadRef = ref<UploadInst | null>(null); // 定义符合实际接口返回的数据结构 interface UploadResult { total_files: number; // 总文件数(数字类型) success_files: number; // 成功文件数(数字类型) total_records: number; // 导入记录数(数字类型) details: FileDetail[]; // 文件详情 } interface FileDetail { filename: string; // 文件名 status: "success" | "partial" | "fail"; // 状态 imported: number; // 导入记录数(数字类型) errors: string[]; // 错误信息 } const uploadResult = ref<UploadResult | null>(null); const loading = ref(false); const fileList = ref<UploadFileInfo[]>([]); // 使用单独的ref管理文件列表 const fileDetail = ref<FileDetail | null>(null); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); //const model: any = reactive(createDefaultModel()); const model = reactive({ // 不再需要file属性 }); function createDefaultModel(): any { return { file: null, }; } function handleInitModel() { Object.assign(model, createDefaultModel()); } function closeDrawer() { visible.value = false; } async function handleSubmit() { // request if (fileList.value.length === 0) { window.$message?.error("没有待上传文件"); return; } loading.value = true; uploadResult.value = null; // 重置结果 try { const formData = new FormData(); // 添加所有文件到FormData fileList.value.forEach((fileInfo) => { if (fileInfo.file) { formData.append("files", fileInfo.file); } }); const { data, error } = await fetchUploadInterlockLedger(formData); console.log(data) if (!error && data) { // 使用实际接口返回的数据结构 uploadResult.value = { total_files: data.total_files || fileList.value.length, success_files: data.success_files || 0, total_records: data.total_records || 0, details: data.details }; // 显示详细上传结果 // if (uploadResult.value?.success_files <= uploadResult.value?.total_files){ // window.$message?.warning( // `${uploadResult.value?.success_files}/${uploadResult.value?.total_files}个文件上传成功` // ); // } let allErrors: any[] = []; // 存储所有错误信息 if (uploadResult.value.details.length > 0) { for (var val in uploadResult.value.details) { if (uploadResult.value.details[val].errors.length > 0) { // 将当前元素的errors数组拼接到总错误列表 allErrors = allErrors.concat(uploadResult.value.details[val].errors); window.$message?.error(`${uploadResult.value.details[val].filename}:${allErrors.join('\n')}`); } } // 统一显示所有错误(用换行符分隔) // if (allErrors.length > 0) { // window.$message?.warning(allErrors.join('\n')); // } } console.log(allErrors) if (allErrors.length == 0) { window.$message?.success("上传成功"); } } } catch (err) { window.$message?.error("网络错误"); } finally { loading.value = false; } closeDrawer(); emit("submitted"); } function handleChange(data: { fileList: UploadFileInfo[] }) { fileList.value = data.fileList; // 更新文件列表 uploadResult.value = null; // 重置结果 } //sheet={} function downloadTemplate() { const equipList = Object.keys(nameToequipMap.value); // 创建主工作表 const mainSheet: SheetData = { sheetName: "互锁台账列表", data: [ // 表头行 [ "报警名称", "触发器", "满足条件", "渠道号", "报警级别", "报警描述", "触发器渠道", "报警动作", "报警动作Data通道", "报警动作描述", "互锁类型", "所属模块", "模块类型", "所属产品", // 需要设置下拉选择的列 "所属部门", "备注" ], ], // 优化数据验证:使用动态范围引用 dataValidations: [{ type: 'list', formula1: `设备选项说明!$A$2:$A$${equipList.length + 1}`, // 引用另一工作表的数据 ref: "N2:N1000" // 应用范围:N列第2行到1000行 }] }; // 创建设备选项说明表 const helpSheet: SheetData = { sheetName: "设备选项说明", data: [ ["设备选项列表"], ...equipList.map(item => [item]) ], dataValidations:[] }; exportXlsx([mainSheet, helpSheet], "互锁台账导入模板"); } // watch(visible, () => { // if (visible.value) { // handleInitModel(); // } // }); function clearFiles() { fileList.value = []; if (uploadRef.value) { uploadRef.value.clear(); } uploadResult.value = null; } watch(visible, (newVal) => { if (newVal) { clearFiles(); // 每次打开时清空文件 } }); </script> <template> <NModal v-model:show="visible" preset="dialog" :title="$t('common.upload')"> <NPopover trigger="hover"> <template #trigger> <NButton @click="downloadTemplate" text type="primary">{{ $t("common.templateDownload") }}</NButton> </template> <span>点此下载导入模板</span> </NPopover> <NUpload accept=".csv,.xls,.xlsx" ref="uploadRef" :default-upload="false" :max="10" multiple :file-list="fileList" @change="handleChange" > <NUploadDragger @click="clearFiles"> <div style="margin-bottom: 12px"> <icon-mdi-tray-upload class="text-icon" /> </div> <NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText> <NP depth="3" style="margin: 8px 0 0 0"> 请参考上传模板来提供数据,支持csv,xls,xlsx格式,单次最多上传10个文件 </NP> </NUploadDragger> </NUpload> <template #action> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit" :loading="loading" :disabled="fileList.length === 0" > {{ $t("common.confirm") }}</NButton> </NSpace> </template> </NModal> </template> <style scoped></style> 用户打开模板后,"所属产品"列会有下拉箭头 点击下拉箭头会显示设备选项列表 用户只能选择列表中的设备,不能手动输入

时间: 2025-07-26 14:21:18 浏览: 16
<think>根据用户描述,当前代码已经实现了在Excel模板的“所属产品”列设置下拉选择框,并且用户只能选择列表中的设备,不能手动输入。这通过数据验证(dataValidation)中的列表(list)类型实现,且引用另一个工作表(设备选项说明)中的设备列表。 但是,用户可能希望进一步优化或了解相关实现细节。下面提供一些关键点说明: 1. **数据验证设置**: - 在`mainSheet`的`dataValidations`中,我们设置了类型为`list`的验证,并引用了另一个工作表(设备选项说明)中的单元格范围(A2到A列最后一个设备名)。 - `ref: "N2:N1000"`表示这个验证应用于主工作表的N列(第2行到第1000行)。 2. **设备列表来源**: - 设备列表来自`nameToequipMap.value`的键(即设备名称),通过`Object.keys`获取。 - 在帮助表(设备选项说明)中,我们将设备列表放在A列,从第2行开始(第1行是标题)。 3. **禁止手动输入**: - 设置数据验证为列表(list)后,Excel默认只允许从下拉列表中选择,不允许手动输入不在列表中的值。 4. **模板使用提示**: - 用户下载模板后,在“所属产品”列(N列)的单元格中会有一个下拉箭头,点击即可选择设备。 - 如果用户尝试输入不在列表中的值,Excel会弹出错误提示。 ### 注意事项 - 确保`nameToequipMap.value`已经正确加载了设备数据,否则设备列表可能为空。 - 设备列表长度变化时,动态计算引用范围(`设备选项说明!$A$2:$A$${equipList.length + 1}`)可以确保所有设备都被包含。 ### 可能的问题及优化 - **性能问题**:如果设备数量非常大(比如超过1万),在Excel中打开模板可能会变慢。可以考虑在模板中只放置常用设备,或者提供搜索功能(但Excel本身对大量下拉列表支持有限)。 - **模板更新**:当设备列表更新后,用户需要重新下载模板才能获取最新的设备列表。 ### 代码确认 当前`downloadTemplate`函数已经按照上述逻辑实现,用户反馈的功能已经满足。 §§相关问题§§ 1. 如果设备列表过长(例如超过1万条),如何优化Excel模板的性能? 2. 如何实现设备列表的分页显示或搜索功能(在Excel的下拉列表中)? 3. 当设备列表发生变化时,如何让已下载的模板自动更新设备选项? 4. 除了设备列表,如何为其他列设置类似的下拉选择(如报警级别、互锁类型等)? 5. 如何防止用户修改“设备选项说明”工作表中的设备列表?</think>### 实现Excel下拉选择功能的关键代码分析 在您提供的代码中,"所属产品"列的下拉选择功能是通过以下关键部分实现的: ```typescript function downloadTemplate() { const equipList = Object.keys(nameToequipMap.value); const mainSheet: SheetData = { // ...其他配置 dataValidations: [{ type: 'list', formula1: `设备选项说明!$A$2:$A$${equipList.length + 1}`, ref: "N2:N1000" // N列对应"所属产品" }] }; const helpSheet: SheetData = { sheetName: "设备选项说明", data: [ ["设备选项列表"], ...equipList.map(item => [item]) ] }; exportXlsx([mainSheet, helpSheet], "互锁台账导入模板"); } ``` ### 实现原理说明 1. **数据验证设置**: - `type: 'list'`:指定为下拉列表类型 - `formula1`: 指向"设备选项说明"工作表中的设备列表范围 - `ref: "N2:N1000"`:将验证规则应用到N列("所属产品"列)的第2-1000行 2. **设备选项来源**: - 创建单独的"设备选项说明"工作表 - 将设备名称列表填充到该工作表的A列 - 主表通过跨工作表引用获取选项数据 3. **禁止手动输入**: - Excel默认会强制用户只能从下拉列表中选择 - 如果用户尝试手动输入非列表值,Excel会拒绝并提示错误 ### 使用效果验证 当用户打开模板后: 1. "所属产品"列(N列)每个单元格右侧会显示下拉箭头 2. 点击箭头会显示所有设备名称列表 3. 用户只能从列表中选择,无法手动输入其他值 ### 可能的问题排查 如果下拉功能未生效,请检查: 1. `equipList`是否包含有效的设备名称 2. Excel文件是否包含两个工作表(主表和设备选项说明表) 3. 在Excel中查看N列的数据验证设置是否正确 4. 导出的XLSX文件是否被正确生成(无报错)
阅读全文

相关推荐

<script setup lang="ts"> import { reactive, ref, watch } from "vue"; import { $ t } from "@/locales"; import { UploadFileInfo, UploadInst } from "naive-ui"; import { exportXlsx, SheetData } from "@/utils/export-excel"; defineOptions({ name: "LedgerUploadDrawer", }); interface Props { /** the type of operation */ } const props = defineProps(); const fileListLengthRef = ref(0); const uploadRef = ref<UploadInst | null>(null); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); const model: any = reactive(createDefaultModel()); function createDefaultModel(): any { return { file: null, }; } function handleInitModel() { Object.assign(model, createDefaultModel()); } function closeDrawer() { visible.value = false; } async function handleSubmit() { // request uploadRef.value?.submit(); closeDrawer(); emit("submitted"); } function handleChange(options: { fileList: UploadFileInfo[] }) { fileListLengthRef.value = options.fileList.length; } function downloadTemplate() { const sheets: SheetData[] = [ { sheetName: "互锁台账列表", data: [ [ "报警名称", "互锁类型", "所属模块", "模块类型", "所属产品", "触发器", "触发器渠道", "报警描述", "报警级别", "报警动作", "报警动作Data通道", "报警动作描述", "满足条件", "备注", ], ], }, ]; exportXlsx(sheets, "互锁台账导入模板"); } watch(visible, () => { if (visible.value) { handleInitModel(); } }); </script> <template> <NModal v-model:show="visible" preset="dialog" :title="$ t('common.upload')"> <NPopover trigger="hover"> <template #trigger> <NButton @click="downloadTemplate" text type="primary">{{ $ t("common.templateDownload") }}</NButton> </template> 点此下载导入模板 </NPopover> <NUpload accept=".csv,.xls,.xlsx" ref="upload" action="/https/wenku.csdn.net/api/v1/interlock/ledger/lock" :default-upload="false" :max="10" multiple @change="handleChange" > <NUploadDragger> <icon-mdi-tray-upload class="text-icon" /> <NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText> <NP depth="3" style="margin: 8px 0 0 0"> 请参考上传模板来提供数据,支持csv,xls,xlsx格式,单次最多上传10个文件 </NP> </NUploadDragger> </NUpload> <template #action> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $ t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $ t("common.confirm") }}</NButton> </NSpace> </template> </NModal> </template> <style scoped></style> 希望下载的模版互锁类型是枚举型的

<script lang="ts" setup> import { Select,Spin} from 'ant-design-vue'; import { reactive, } from 'vue'; import { combobox } from '#/api/order/information'; import { ref } from 'vue'; const props = defineProps<{ modelValue: string | null; // 父组件绑定值 type: string; }>(); const emit = defineEmits<{ (e: 'update:modelValue', value: string | null): void; }>(); const pageNo = ref(1); const pageSize = 10; const total = ref(0); const options = ref([]); const loading = ref(false); const keywords = ref(''); const angetId = ref(props.modelValue); let searchTimeout: ReturnType<typeof setTimeout> | null = null; async function loadData(reset = false) { if (reset) { pageNo.value = 1; options.value = []; } loading.value = true; try { const res = await combobox({ keywords: keywords.value, pageNo: pageNo.value, pagesSize: pageSize, }); if (reset) { options.value = res.list; } else { options.value = options.value.concat(res.list); } total.value = res.total; } finally { loading.value = false; } } function onSearch(val: string) { keywords.value = val; if (searchTimeout) clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { loadData(true); }, 300); } function onPopupScroll(e: Event) { const target = e.target as HTMLElement; if (target.scrollTop + target.clientHeight >= target.scrollHeight - 10) { if (!loading.value && options.value.length < total.value) { pageNo.value++; loadData(); } } } if (props.type === 'angetPage') { loadData(true); } </script> <template> <Select style="width:100%" v-model:value="angetId" show-search :filter-option="false" :loading=loading :options="options.map(item => ({ label: item.agentName, value: item.agentId }))" @search="onSearch" @popupScroll="onPopupScroll" placeholder="11" allowClear > <template #notFoundContent> <a-spin v-if="loading" size="small" /> 无匹配项 </template> </Select> </template> <style lang="scss" scoped> $font-size: 12px; .seo_iten{ width:100%; } </style> 这是我的组件 <script lang="ts" setup> import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { InformationApi } from '#/api/order/information'; import { Page, useVbenModal } from '@vben/common-ui'; import { Button, message,Tabs } from 'ant-design-vue'; import { Download, Plus } from '@vben/icons'; import Form from './modules/form.vue'; import ItemDome from '#/components/TableSetOf/itemDome/index.vue'; import seoList from '#/components/TableSetOf/seo/index.vue'; import { ref, h } from 'vue'; import { $t } from '#/locales'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { getInformationPage, deleteInformation, exportInformation } from '#/api/order/information'; import { downloadByData } from '#/utils/download'; import { useGridColumns, useGridFormSchema } from './data'; const [FormModal, formModalApi] = useVbenModal({ connectedComponent: Form, destroyOnClose: true, }); /** 刷新表格 */ function onRefresh() { gridApi.query(); } /** 创建订单信息 */ function onCreate() { formModalApi.setData({}).open(); } /** 编辑订单信息 */ function onEdit(row: InformationApi.Information) { formModalApi.setData(row).open(); } /** 删除订单信息 */ async function onDelete(row: InformationApi.Information) { const hideLoading = message.loading({ content: $t('ui.actionMessage.deleting', [row.orderId]), duration: 0, key: 'action_process_msg', }); try { await deleteInformation(row.orderId as number); message.success({ content: $t('ui.actionMessage.deleteSuccess', [row.orderId]), key: 'action_process_msg', }); onRefresh(); } catch { hideLoading(); } } /** 导出表格 */ async function onExport() { const data = await exportInformation(await gridApi.formApi.getValues()); downloadByData(data, '订单信息.xls'); } /** 表格操作按钮的回调函数 */ function onActionClick({ code, row, }: OnActionClickParams<InformationApi.Information>) { switch (code) { case 'edit': { onEdit(row); break; } case 'delete': { onDelete(row); break; } } } const [Grid, gridApi] = useVbenVxeGrid({ formOptions: { schema: useGridFormSchema(), }, gridOptions: { columns: useGridColumns(onActionClick), height: 'auto', pagerConfig: { enabled: true, }, showOverflow: false, proxyConfig: { ajax: { query: async ({ page }, formValues) => { return await getInformationPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues, }); }, }, }, rowConfig: { keyField: 'orderId', isHover: true, }, toolbarConfig: { refresh: { code: 'query' }, search: true, }, } as VxeTableGridOptions<InformationApi.Information>, }); </script> <template> <FormModal @success="onRefresh" /> <Grid table-title="订单信息列表"> <template #form-agentId="{ model, field }"> <seoList type="angetPage" ></seoList> </template> <template #storeInfo="{ row }"> <ItemDome type="storeInfo" :rowData="row.storeInfo" ></ItemDome> </template> <template #appUserInfo="{ row }"> <ItemDome type="appUserInfo" :rowData="row.appUserInfo" ></ItemDome> </template> <template #goodsList="{ row }"> <ItemDome type="goodsList" :rowData="row.goodsList" ></ItemDome> </template> <template #payAmount="{ row }"> ¥{{row.payAmount}} </template> <template #toolbar-tools> <Button :icon="h(Plus)" type="primary" @click="onCreate" v-access:code="['order:information:create']"> {{ $t('ui.actionTitle.create', ['订单信息']) }} </Button> <Button :icon="h(Download)" type="primary" class="ml-2" @click="onExport" v-access:code="['order:information:export']" > {{ $t('ui.actionTitle.export') }} </Button> </template> </Grid> </template> <style lang="scss" scoped> .payAmount { color: #FF3D57; font-size: 14px; text-align: center; width: 100%; } </style> 这是我得父页面 然后我想要的是点击搜索和选中的时候 父组件搜索全局列表 angetI能有效 现在是无效的

@router.post(path=‘/ledgers/upload’, description=‘互锁台账上传’) async def _(files: List[UploadFile] = File(…)): print(‘互锁台账上传逻辑’) for file in files: print(file.filename) return Success(msg=“Upload Successfully”, data={}) 写一个互锁台账上传逻辑:输入一个excel文件,列值是中文名称,但是在数据表中的字段名是英文,将每行数据解析保存到数据库的对应字段中,<script setup lang="ts"> import { reactive, ref, watch } from "vue"; import { $t } from "@/locales"; import { UploadFileInfo, UploadInst } from "naive-ui"; import { exportXlsx, SheetData } from "@/utils/export-excel"; defineOptions({ name: "LedgerUploadDrawer", }); interface Props { /** the type of operation */ } const props = defineProps(); const fileListLengthRef = ref(0); const uploadRef = ref<UploadInst | null>(null); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); const model: any = reactive(createDefaultModel()); function createDefaultModel(): any { return { file: null, }; } function handleInitModel() { Object.assign(model, createDefaultModel()); } function closeDrawer() { visible.value = false; } async function handleSubmit() { // request uploadRef.value?.submit(); closeDrawer(); emit("submitted"); } function handleChange(options: { fileList: UploadFileInfo[] }) { fileListLengthRef.value = options.fileList.length; } //sheet={} function downloadTemplate() { const sheets: SheetData[] = [ { sheetName: "互锁台账列表", data: [ [ "报警名称", "互锁类型", "所属模块", "模块类型", "所属产品", "触发器", "触发器渠道", "报警描述", "报警级别", "报警动作", "报警动作Data通道", "报警动作描述", "满足条件", "备注", ], ], }, ]; exportXlsx(sheets, "互锁台账导入模板"); } watch(visible, () => { if (visible.value) { handleInitModel(); } }); </script> <template> <NModal v-model:show="visible" preset="dialog" :title="$t('common.upload')"> <NPopover trigger="hover"> <template #trigger> <NButton @click="downloadTemplate" text type="primary">{{ $t("common.templateDownload") }}</NButton> </template> 点此下载导入模板 </NPopover> <NUpload accept=".csv,.xls,.xlsx" ref="upload" action="/https/wenku.csdn.net/api/v1/interlock/ledger/lock" :default-upload="false" :max="10" multiple @change="handleChange" > <NUploadDragger> <icon-mdi-tray-upload class="text-icon" /> <NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText> <NP depth="3" style="margin: 8px 0 0 0"> 请参考上传模板来提供数据,支持csv,xls,xlsx格式,单次最多上传10个文件 </NP> </NUploadDragger> </NUpload> <template #action> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $t("common.confirm") }}</NButton> </NSpace> </template> </NModal> </template> <style scoped></style> 这是已有的前端代码

后端接口写好了,前端的api fetchUploadInterlockLedgera是这样 export function fetchUploadInterlockLedger(data: any) { return request<Api.InterLock.LedgerList, “json”>({ url: /interlock/ledgers/upload, method: “post”, headers: { “Content-Type”: “multipart/form-data” }, data, }); } 那么在前端界面的响应怎么写,以下是当前的代码 <script setup lang="ts"> import { reactive, ref, watch } from "vue"; import { $t } from "@/locales"; import { UploadFileInfo, UploadInst } from "naive-ui"; import { exportXlsx, SheetData } from "@/utils/export-excel"; import { fetchAddInterlock, fetchUpdateInterlock, fetchUploadInterlockLedger} from "@/service/api"; defineOptions({ name: "LedgerUploadDrawer", }); interface Props { /** the type of operation */ } const props = defineProps(); const fileListLengthRef = ref(0); const uploadRef = ref<UploadInst | null>(null); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); const model: any = reactive(createDefaultModel()); function createDefaultModel(): any { return { file: null, }; } function handleInitModel() { Object.assign(model, createDefaultModel()); } function closeDrawer() { visible.value = false; } async function handleSubmit() { // request const { error } = await fetchUploadInterlockLedger(model); if (!error) { window.$message?.success($t("common.updateSuccess")); } uploadRef.value?.submit(); closeDrawer(); emit("submitted"); } function handleChange(options: { fileList: UploadFileInfo[] }) { fileListLengthRef.value = options.fileList.length; // 重置结果 uploadResult.value = null; } //sheet={} function downloadTemplate() { const sheets: SheetData[] = [ { sheetName: "互锁台账列表", data: [ [ "报警名称", "互锁类型", "所属模块", "模块类型", "所属产品", "触发器", "渠道号", "触发器渠道", "报警描述", "报警级别", "报警动作", "报警动作Data通道", "报警动作描述", "满足条件", "备注", ], ], }, ]; exportXlsx(sheets, "互锁台账导入模板"); } watch(visible, () => { if (visible.value) { handleInitModel(); } }); </script> <template> <NModal v-model:show="visible" preset="dialog" :title="$t('common.upload')"> <NPopover trigger="hover"> <template #trigger> <NButton @click="downloadTemplate" text type="primary">{{ $t("common.templateDownload") }}</NButton> </template> 点此下载导入模板 </NPopover> <NUpload accept=".csv,.xls,.xlsx" ref="upload" action="/https/wenku.csdn.net/api/v1/interlock/ledger/lock" :default-upload="false" :max="10" multiple @change="handleChange" > <NUploadDragger> <icon-mdi-tray-upload class="text-icon" /> <NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText> <NP depth="3" style="margin: 8px 0 0 0"> 请参考上传模板来提供数据,支持csv,xls,xlsx格式,单次最多上传10个文件 </NP> </NUploadDragger> </NUpload> <template #action> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $t("common.confirm") }}</NButton> </NSpace> </template> </NModal> </template> <style scoped></style>

import { request } from "../request"; // 定义设备搜索参数类型 export interface ZtEquipSearchParams { current?: number; // 当前页码 size?: number; // 每页数量 name?: string; // 设备名称搜索 NMCPdtMdlequip?: string; } // 定义设备数据结构 export interface ZtEquipItem { id: number; code: string; name: string; softtype:string; type: string; remark: string; status: string; nMCPdtMdlDept: string; applicationArea?: string; technology?: string; productStage?: string; pkId?: string; batchId?: string; mainCode?: string; } // 定义接口响应结构 export interface ZtEquipListResponse { records: ZtEquipItem[]; total: number; current: number; size: number; } /** 获取设备列表 */ export function fetchGetZtEquipList(data?: ZtEquipSearchParams) { return request<ZtEquipListResponse>({ url: "/https/wenku.csdn.net/zentao/equip/all", // 修改为实际的设备列表接口路径 method: "post", data:data }); } // 设备名称搜索缓存 const equipSearchCache = new Map<string, ZtEquipItem[]>(); // 部门名称搜索缓存(独立实现) const deptSearchCache = new Map<string, string[]>(); /** * 实时搜索设备(≥2字符触发API) * @param query 设备名称关键词 * @param maxResults 最大返回数量 */ export const searchEquipments = async ( query: string, maxResults: number = 20 ): Promise<ZtEquipItem[]> => { if (!query || query.trim().length < 2) return []; // 尝试从设备搜索缓存获取 const cacheKey = equip:${query.toLowerCase()}; if (equipSearchCache.has(cacheKey)) { return equipSearchCache.get(cacheKey)!.slice(0, maxResults); } try { const result = await fetchGetZtEquipList({ name: query, size: maxResults }); if (result.data?.records) { const equipments = result.data.records; // 更新设备搜索缓存 equipSearchCache.set(cacheKey, equipments); return equipments; } return []; } catch (error) { console.error('设备搜索失败:', error); return []; } }; /** * 根据设备ID获取部门信息 * @param equipId 设备ID */ export const getDeptByEquipId = async ( equipId: number ): Promise<string | undefined> => { try { const result = await fetchGetZtEquipList({ size: 1, NMCPdtMdlequip: equipId.toString() }); if (result.data?.records?.[0]) { return result.data.records[0].nMCPdtMdlDept; } return undefined; } catch (error) { console.error('获取设备部门失败:', error); return undefined; } }; /** * 实时搜索部门名称(独立实现) * @param deptQuery 部门名称关键词 * @param maxResults 最大返回数量 */ export const searchDepartments = async ( deptQuery: string, maxResults: number = 20 ): Promise<string[]> => { if (!deptQuery || deptQuery.trim().length < 2) return []; // 尝试从部门搜索缓存获取 const cacheKey = dept:${deptQuery.toLowerCase()}; if (deptSearchCache.has(cacheKey)) { return deptSearchCache.get(cacheKey)!; } try { const result = await fetchGetZtEquipList({ size: maxResults }); if (result.data?.records) { // 提取并过滤部门名称 const departments = Array.from( new Set( result.data.records .map(item => item.nMCPdtMdlDept) .filter(dept => dept && dept.includes(deptQuery)) ) ); // 更新部门搜索缓存 deptSearchCache.set(cacheKey, departments); return departments; } return []; } catch (error) { console.error('部门搜索失败:', error); return []; } }; /** * 清除设备搜索缓存 */ export const clearEquipCache = () => { equipSearchCache.clear(); }; /** * 清除部门搜索缓存 */ export const clearDeptCache = () => { deptSearchCache.clear(); }; <script setup lang="ts"> import { onMounted, ref, watch } from "vue"; import { $t } from "@/locales"; import { lockTypesOptions, logLevelTypeOptions, moduleTypesOptions, } from "@/constants/business"; import { translateOptions } from "@/utils/common"; import { useNaiveForm } from "@/hooks/common/form"; import { router } from "@/router"; import { searchEquipments, searchDepartments } from "@/service/api"; // 导入设备搜索API // 设备搜索状态 const equipLoading = ref(false); const equipOptions = ref<any[]>([]); const lastEquipSearch = ref(""); // 部门搜索状态 const deptLoading = ref(false); const deptOptions = ref<any[]>([]); const lastDeptSearch = ref(""); const currentRoute = ref(router.currentRoute); defineOptions({ name: "search", }); interface Emits { (e: "reset"): void; (e: "search"): void; } const emit = defineEmits<Emits>(); const { formRef, validate, restoreValidation } = useNaiveForm(); const model = defineModel<Api.InterLock.LedgerSearchParams>("model", { required: true, }); /** * 设备搜索处理函数 * @param query 搜索关键词 */ const handleequipSearch = async (query: string) => { if (!query || query.trim().length < 2) { equipOptions.value = []; return; } // 避免重复搜索相同查询 if (lastEquipSearch.value === query) return; lastEquipSearch.value = query; equipLoading.value = true; try { const results = await searchEquipments(query); equipOptions.value = results.map(equip => ({ label: ${equip.name}, value: equip.id })); } catch (error) { console.error('设备搜索失败:', error); equipOptions.value = []; } finally { equipLoading.value = false; } }; /** * 部门搜索处理函数 * @param query 搜索关键词 */ const handleDeptSearch = async (query: string) => { if (!query || query.trim().length < 2) { deptOptions.value = []; return; } // 避免重复搜索相同查询 if (lastDeptSearch.value === query) return; lastDeptSearch.value = query; deptLoading.value = true; try { const results = await searchDepartments(query); // 去重部门名称 const uniqueDepts = [...new Set(results.map(item => item.nMCPdtMdlDept))]; deptOptions.value = uniqueDepts.map(dept => ({ label: dept, value: dept })); } catch (error) { console.error('部门搜索失败:', error); deptOptions.value = []; } finally { deptLoading.value = false; } }; // 重置搜索状态 const resetSearch = () => { equipOptions.value = []; deptOptions.value = []; lastEquipSearch.value = ""; lastDeptSearch.value = ""; }; async function reset() { await restoreValidation(); resetSearch(); emit("reset"); } async function search() { await validate(); emit("search"); } onMounted(() => { // 初始加载常用设备选项 if (model.value.product) { handleequipSearch(model.value.product); } }); // 监听路由参数变化 - 仅处理设备 watch( () => currentRoute.value, (newRoute) => { // 仅从路由参数获取设备信息 const product = newRoute.params.product || newRoute.query.product; model.value.product = product as string; }, { immediate: true }, ); </script> <template> <NCard :bordered="false" size="small" class="card-wrapper"> <NCollapse> <NCollapseItem :title="$t('common.search')" name="interlock-ledger-search" > <NForm ref="formRef" :model="model" label-placement="left" :label-width="80" > <NGrid responsive="screen" item-responsive> <NFormItemGi span="24 s:12 m:6" :label="$t('page.interlock.ledger.alarmName')" path="alarmName" class="pr-24px" > <NInput v-model:value="model.alarmName" :placeholder="$t('page.interlock.ledger.form.alarmName')" /> </NFormItemGi> <NFormItemGi span="24 s:12 m:6" :label="$t('page.interlock.ledger.product')" path="product" class="pr-24px" > <NSelect v-model:value="model.product" :placeholder="$t('page.interlock.ledger.form.product')" :options="equipOptions" :loading="equipLoading" :remote="true" filterable clearable @search="handleequipSearch" /> </NFormItemGi> <NFormItemGi span="24 s:12 m:6" :label="$t('page.interlock.ledger.moduleName')" path="moduleName" class="pr-24px" > <NInput v-model:value="model.moduleName" :placeholder="$t('page.interlock.ledger.form.moduleName')" /> </NFormItemGi> <NFormItemGi span="24 s:12 m:6" :label="$t('page.interlock.ledger.lockType')" path="lockType" class="pr-24px" > <NSelect v-model:value="model.lockType" :placeholder="$t('page.interlock.ledger.form.lockType')" :options="translateOptions(lockTypesOptions)" filterable clearable /> </NFormItemGi> <NFormItemGi span="24 s:12 m:6" :label="$t('page.interlock.ledger.moduleType')" path="moduleType" class="pr-24px" > <NSelect v-model:value="model.moduleType" :placeholder="$t('page.interlock.ledger.form.moduleType')" :options="translateOptions(moduleTypesOptions)" filterable clearable /> </NFormItemGi> <NFormItemGi span="24 s:12 m:6" :label="$t('page.interlock.ledger.alarmLevel')" path="alarmLevel" class="pr-24px" > <NSelect v-model:value="model.alarmLevel" :placeholder="$t('page.interlock.ledger.form.alarmLevel')" :options="translateOptions(logLevelTypeOptions)" filterable clearable /> </NFormItemGi> <NFormItemGi span="24 s:12 m:6" :label="$t('page.interlock.ledger.deptName')" path="deptName" class="pr-24px" > <NSelect v-model:value="model.deptName" :placeholder="$t('page.interlock.ledger.form.deptName')" :options="deptOptions" :loading="deptLoading" :remote="true" filterable clearable @search="handleDeptSearch" /> </NFormItemGi> <NFormItemGi span="24 s:12 m:6"> <NSpace class="w-full" justify="end"> <NButton @click="reset"> <template #icon> <icon-ic-round-refresh class="text-icon" /> </template> {{ $t("common.reset") }} </NButton> <NButton type="primary" ghost @click="search"> <template #icon> <icon-ic-round-search class="text-icon" /> </template> {{ $t("common.search") }} </NButton> </NSpace> </NFormItemGi> </NGrid> </NForm> </NCollapseItem> </NCollapse> </NCard> </template> <style scoped></style> <script setup lang="ts"> import { computed, onMounted, reactive, ref, watch } from "vue"; import { useFormRules, useNaiveForm } from "@/hooks/common/form"; import { fetchAddInterlock, fetchUpdateInterlock } from "@/service/api"; import { $t } from "@/locales"; import { lockTypesOptions, logLevelTypeOptions, moduleTypesOptions, } from "@/constants/business"; import { translateOptions } from "@/utils/common"; import { NSelect } from "naive-ui"; defineOptions({ name: "LedgerOperateDrawer", }); interface Props { /** the type of operation */ operateType: NaiveUI.TableOperateType; /** the edit row data */ rowData?: Api.InterLock.Ledger | null; } const props = defineProps(); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); const { formRef, validate, restoreValidation } = useNaiveForm(); const { defaultRequiredRule } = useFormRules(); const title = computed(() => { const titles: Record<NaiveUI.TableOperateType, string> = { add: $t("common.add"), edit: $t("common.edit"), clone: $t("common.clone"), }; return titles[props.operateType]; }); const model: Api.InterLock.LedgerUpdateParams = reactive(createDefaultModel()); function createDefaultModel(): Api.InterLock.LedgerAddParams { return { alarmName: "", lockType: "sinterlock", moduleName: "", moduleType: null, product: null, deptName: "", trigger: "", channelNumber: "", triggerWay: "", alarmDesc: "", alarmLevel: "INFO", alarmDetail: "", alarmAction: "", alarmChannel: "", alarmActionDesc: "", condition: "", desc: "", }; } type RuleKey = Extract< keyof Api.InterLock.LedgerUpdateParams, "alarmName" | "lockType" | "moduleName" | "moduleType" | "product" >; const rules = ref<Record<RuleKey, App.Global.FormRule>>({ alarmName: defaultRequiredRule, lockType: defaultRequiredRule, moduleName: defaultRequiredRule, moduleType: defaultRequiredRule, product: defaultRequiredRule, }); function closeDrawer() { visible.value = false; } async function handleSubmit() { await validate(); // request if (props.operateType === "add" || props.operateType === "clone") { const { error } = await fetchAddInterlock(model); if (!error) { window.$message?.success($t("common.addSuccess")); } } else if (props.operateType === "edit") { const { error } = await fetchUpdateInterlock(model); if (!error) { window.$message?.success($t("common.updateSuccess")); } } closeDrawer(); emit("submitted"); } watch(visible, () => { if (visible.value) { restoreValidation(); } }); </script> <template> <NDrawer v-model:show="visible" display-directive="show" :width="560"> <NDrawerContent :title="title" :native-scrollbar="false" closable> <NForm ref="formRef" :model="model" :rules="rules"> <NFormItem :label="$t('page.interlock.ledger.alarmName')" path="alarmName" > <NInput v-model:value="model.alarmName" :placeholder="$t('page.interlock.ledger.form.alarmName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.lockType')" path="lockType" > <NSelect v-model:value="model.lockType" :options="translateOptions(lockTypesOptions)" size="small" class="w-120px" :disabled="operateType === 'edit' || operateType === 'clone'" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.product')" path="product"> <NSelect v-model:value="model.product" filterable clearable :placeholder="$t('page.interlock.ledger.form.product')" :options="filteredequipOptions" :loading="equipLoading" :remote="true" @update:value="handleProductChange" @search="handleequipSearch" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleName')" path="moduleName" > <NInput v-model:value="model.moduleName" :placeholder="$t('page.interlock.ledger.form.moduleName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleType')" path="moduleType" > <NSelect v-model:value="model.moduleType" :options="translateOptions(moduleTypesOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.deptName')" path="deptName" > <NSpin v-if="deptLoading" size="small" /> <NTag v-if="model.deptName" type="info">{{ model.deptName }}</NTag> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmLevel')" path="alarmLevel" > <NSelect v-model:value="model.alarmLevel" :options="translateOptions(logLevelTypeOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.trigger')" path="trigger"> <NInput v-model:value="model.trigger" :placeholder="$t('page.interlock.ledger.form.trigger')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.triggerWay')" path="triggerWay" > <NInput v-model:value="model.triggerWay" :placeholder="$t('page.interlock.ledger.form.triggerWay')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmDesc')" path="alarmDesc" > <NInput v-model:value="model.alarmDesc" :placeholder="$t('page.interlock.ledger.form.alarmDesc')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmAction')" path="alarmAction" > <NInput v-model:value="model.alarmAction" :placeholder="$t('page.interlock.ledger.form.alarmAction')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmChannel')" path="alarmChannel" > <NInput v-model:value="model.alarmChannel" :placeholder="$t('page.interlock.ledger.form.alarmChannel')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmActionDesc')" path="alarmActionDesc" > <NInput v-model:value="model.alarmActionDesc" :placeholder="$t('page.interlock.ledger.form.alarmActionDesc')" /> </NFormItem> <NFormItem v-if="model.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.condition')" path="condition" > <NInput v-model:value="model.condition" :placeholder="$t('page.interlock.ledger.form.condition')" /> </NFormItem> <NFormItem v-if="model.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.channelNumber')" path="channelNumber" > <NInput v-model:value="model.channelNumber" :placeholder="$t('page.interlock.ledger.form.channelNumber')" /> </NFormItem> <NFormItem v-if="model.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.alarmDetail')" path="alarmDetail" > <NInput v-model:value="model.alarmDetail" :placeholder="$t('page.interlock.ledger.form.alarmDetail')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.desc')"> <NInput v-model:value="model.desc" :placeholder="$t('page.interlock.ledger.form.desc')" /> </NFormItem> </NForm> <template #footer> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $t("common.confirm") }}</NButton> </NSpace> </template> </NDrawerContent> </NDrawer> </template> <style scoped></style>

<script setup lang="ts"> import { computed, onMounted, reactive, ref, watch } from "vue"; import { useFormRules, useNaiveForm } from "@/hooks/common/form"; import { fetchAddInterlock, fetchUpdateInterlock } from "@/service/api"; import { $t } from "@/locales"; import { lockTypesOptions, logLevelTypeOptions, moduleTypesOptions, } from "@/constants/business"; import { translateOptions } from "@/utils/common"; import { NSelect } from "naive-ui"; defineOptions({ name: "LedgerOperateDrawer", }); interface Props { /** the type of operation */ operateType: NaiveUI.TableOperateType; /** the edit row data */ rowData?: Api.InterLock.Ledger | null; } const props = defineProps(); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); const { formRef, validate, restoreValidation } = useNaiveForm(); const { defaultRequiredRule } = useFormRules(); const title = computed(() => { const titles: Record<NaiveUI.TableOperateType, string> = { add: $t("common.add"), edit: $t("common.edit"), clone: $t("common.clone"), }; return titles[props.operateType]; }); const model: Api.InterLock.LedgerUpdateParams = reactive(createDefaultModel()); function createDefaultModel(): Api.InterLock.LedgerAddParams { return { alarmName: "", lockType: "sinterlock", moduleName: "", moduleType: null, product: null, deptName: "", trigger: "", channelNumber: "", triggerWay: "", alarmDesc: "", alarmLevel: "INFO", alarmDetail: "", alarmAction: "", alarmChannel: "", alarmActionDesc: "", condition: "", desc: "", }; } type RuleKey = Extract< keyof Api.InterLock.LedgerUpdateParams, "alarmName" | "lockType" | "moduleName" | "moduleType" | "product" >; const rules = ref<Record<RuleKey, App.Global.FormRule>>({ alarmName: defaultRequiredRule, lockType: defaultRequiredRule, moduleName: defaultRequiredRule, moduleType: defaultRequiredRule, product: defaultRequiredRule, }); function closeDrawer() { visible.value = false; } async function handleSubmit() { await validate(); // request if (props.operateType === "add" || props.operateType === "clone") { const { error } = await fetchAddInterlock(model); if (!error) { window.$message?.success($t("common.addSuccess")); } } else if (props.operateType === "edit") { const { error } = await fetchUpdateInterlock(model); if (!error) { window.$message?.success($t("common.updateSuccess")); } } closeDrawer(); emit("submitted"); } watch(visible, () => { if (visible.value) { restoreValidation(); } }); </script> <template> <NDrawer v-model:show="visible" display-directive="show" :width="560"> <NDrawerContent :title="title" :native-scrollbar="false" closable> <NForm ref="formRef" :model="model" :rules="rules"> <NFormItem :label="$t('page.interlock.ledger.alarmName')" path="alarmName" > <NInput v-model:value="model.alarmName" :placeholder="$t('page.interlock.ledger.form.alarmName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.lockType')" path="lockType" > <NSelect v-model:value="model.lockType" :options="translateOptions(lockTypesOptions)" size="small" class="w-120px" :disabled="operateType === 'edit' || operateType === 'clone'" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.product')" path="product"> <NSelect v-model:value="model.product" filterable clearable :placeholder="$t('page.interlock.ledger.form.product')" :options="filteredequipOptions" :loading="equipLoading" :remote="true" @update:value="handleProductChange" @search="handleequipSearch" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleName')" path="moduleName" > <NInput v-model:value="model.moduleName" :placeholder="$t('page.interlock.ledger.form.moduleName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleType')" path="moduleType" > <NSelect v-model:value="model.moduleType" :options="translateOptions(moduleTypesOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.deptName')" path="deptName" > <NSpin v-if="deptLoading" size="small" /> <NTag v-if="model.deptName" type="info">{{ model.deptName }}</NTag> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmLevel')" path="alarmLevel" > <NSelect v-model:value="model.alarmLevel" :options="translateOptions(logLevelTypeOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.trigger')" path="trigger"> <NInput v-model:value="model.trigger" :placeholder="$t('page.interlock.ledger.form.trigger')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.triggerWay')" path="triggerWay" > <NInput v-model:value="model.triggerWay" :placeholder="$t('page.interlock.ledger.form.triggerWay')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmDesc')" path="alarmDesc" > <NInput v-model:value="model.alarmDesc" :placeholder="$t('page.interlock.ledger.form.alarmDesc')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmAction')" path="alarmAction" > <NInput v-model:value="model.alarmAction" :placeholder="$t('page.interlock.ledger.form.alarmAction')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmChannel')" path="alarmChannel" > <NInput v-model:value="model.alarmChannel" :placeholder="$t('page.interlock.ledger.form.alarmChannel')" /> </NFormItem> <NFormItem v-if="model.lockType === 'vinterlock'" :label="$t('page.interlock.ledger.alarmActionDesc')" path="alarmActionDesc" > <NInput v-model:value="model.alarmActionDesc" :placeholder="$t('page.interlock.ledger.form.alarmActionDesc')" /> </NFormItem> <NFormItem v-if="model.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.condition')" path="condition" > <NInput v-model:value="model.condition" :placeholder="$t('page.interlock.ledger.form.condition')" /> </NFormItem> <NFormItem v-if="model.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.channelNumber')" path="channelNumber" > <NInput v-model:value="model.channelNumber" :placeholder="$t('page.interlock.ledger.form.channelNumber')" /> </NFormItem> <NFormItem v-if="model.lockType === 'sinterlock'" :label="$t('page.interlock.ledger.alarmDetail')" path="alarmDetail" > <NInput v-model:value="model.alarmDetail" :placeholder="$t('page.interlock.ledger.form.alarmDetail')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.desc')"> <NInput v-model:value="model.desc" :placeholder="$t('page.interlock.ledger.form.desc')" /> </NFormItem> </NForm> <template #footer> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $t("common.confirm") }}</NButton> </NSpace> </template> </NDrawerContent> </NDrawer> </template> <style scoped></style> 设备通过搜索关键字进行下拉选择,部门在选择设备后自动带出

<script setup lang="ts"> import { computed, reactive, ref, watch } from "vue"; import { useFormRules, useNaiveForm } from "@/hooks/common/form"; import { fetchAddInterlock, fetchUpdateInterlock, } from "@/service/api"; import { $t } from "@/locales"; import { lockTypesOptions, logLevelTypeOptions, moduleTypesOptions, } from "@/constants/business"; import { translateOptions } from "@/utils/common"; import { NSelect } from "naive-ui"; defineOptions({ name: "LedgerOperateDrawer", }); interface Props { /** the type of operation */ operateType: NaiveUI.TableOperateType; /** the edit row data */ rowData?: Api.InterLock.Ledger | null; } const props = defineProps(); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); const { formRef, validate, restoreValidation } = useNaiveForm(); const { defaultRequiredRule } = useFormRules(); const title = computed(() => { const titles: Record<NaiveUI.TableOperateType, string> = { add: $t("common.add"), edit: $t("common.edit"), clone: $t("common.clone"), }; return titles[props.operateType]; }); const model: Api.InterLock.LedgerUpdateParams = reactive(createDefaultModel()); function createDefaultModel(): Api.InterLock.LedgerAddParams { return { alarmName: "", lockType: "sinterlock", moduleName: "", moduleType: "", product: "", deptName: "", trigger: "", channelNumber: "", triggerWay: "", alarmDesc: "", alarmLevel: "INFO", alarmAction: "", alarmChannel: "", alarmActionDesc: "", condition: "", desc: "", }; } type RuleKey = Extract< keyof Api.InterLock.LedgerUpdateParams, | "alarmName" | "lockType" | "moduleName" | "moduleType" | "product" >; const rules = ref<Record<RuleKey, App.Global.FormRule>>({ alarmName: defaultRequiredRule, lockType: defaultRequiredRule, moduleName: defaultRequiredRule, moduleType: defaultRequiredRule, product: defaultRequiredRule, }); function handleInitModel() { Object.assign(model, createDefaultModel()); const operateTypes = ["edit", "clone"]; if (operateTypes.includes(props.operateType) && props.rowData) { Object.assign(model, props.rowData); } } function closeDrawer() { visible.value = false; } async function handleSubmit() { await validate(); // request if (props.operateType === "add" || props.operateType === "clone") { const { error } = await fetchAddInterlock(model); if (!error) { window.$message?.success($t("common.addSuccess")); } } else if (props.operateType === "edit") { const { error } = await fetchUpdateInterlock(model); if (!error) { window.$message?.success($t("common.updateSuccess")); } } closeDrawer(); emit("submitted"); } watch(visible, () => { if (visible.value) { handleInitModel(); restoreValidation(); } }); </script> <template> <NDrawer v-model:show="visible" display-directive="show" :width="360"> <NDrawerContent :title="title" :native-scrollbar="false" closable> <NForm ref="formRef" :model="model" :rules="rules"> <NFormItem :label="$t('page.interlock.ledger.alarmName')" path="alarmName" > <NInput v-model:value="model.alarmName" :placeholder="$t('page.interlock.ledger.form.alarmName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.lockType')" path="lockType" > <NSelect v-model:value="model.lockType" :options="translateOptions(lockTypesOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.product')" path="product"> <NInput v-model:value="model.product" :placeholder="$t('page.interlock.ledger.form.product')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleName')" path="moduleName" > <NInput v-model:value="model.moduleName" :placeholder="$t('page.interlock.ledger.form.moduleName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleType')" path="moduleType" > <NSelect v-model:value="model.moduleType" :options="translateOptions(moduleTypesOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.deptName')" path="deptName" > <NInput v-model:value="model.deptName" :placeholder="$t('page.interlock.ledger.form.deptName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.trigger')" path="trigger"> <NInput v-model:value="model.trigger" :placeholder="$t('page.interlock.ledger.form.trigger')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.triggerWay')" path="triggerWay" > <NInput v-model:value="model.triggerWay" :placeholder="$t('page.interlock.ledger.form.triggerWay')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmDesc')" path="alarmDesc" > <NInput v-model:value="model.alarmDesc" :placeholder="$t('page.interlock.ledger.form.alarmDesc')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmLevel')" path="alarmLevel" > <NSelect v-model:value="model.alarmLevel" :options="translateOptions(logLevelTypeOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmAction')" path="alarmAction" > <NInput v-model:value="model.alarmAction" :placeholder="$t('page.interlock.ledger.form.alarmAction')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmChannel')" path="alarmChannel" > <NInput v-model:value="model.alarmChannel" :placeholder="$t('page.interlock.ledger.form.alarmChannel')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.channelNumber')" path="channelNumber" > <NInput v-model:value="model.channelNumber" :placeholder="$t('page.interlock.ledger.form.channelNumber')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmActionDesc')"> <NInput v-model:value="model.alarmActionDesc" :placeholder="$t('page.interlock.ledger.form.alarmActionDesc')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.condition')"> <NInput v-model:value="model.condition" :placeholder="$t('page.interlock.ledger.form.condition')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.desc')"> <NInput v-model:value="model.desc" :placeholder="$t('page.interlock.ledger.form.desc')" /> </NFormItem> </NForm> <template #footer> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $t("common.confirm") }}</NButton> </NSpace> </template> </NDrawerContent> </NDrawer> </template> <style scoped></style> 如何将设备选项加入到product,当选择了product,deptName将自动选择并不能修改

<script setup lang="ts"> import { computed, onMounted, reactive, ref, watch } from "vue"; import { useFormRules, useNaiveForm } from "@/hooks/common/form"; import { fetchAddInterlock, fetchUpdateInterlock, } from "@/service/api"; import { $t } from "@/locales"; import { lockTypesOptions, logLevelTypeOptions, moduleTypesOptions, } from "@/constants/business"; import { translateOptions } from "@/utils/common"; import { NSelect } from "naive-ui"; import { useEquipment } from "@/hooks/common/useEquipment"; // 引入设备钩子 const { loading: equipLoading, filteredequipOptions, handleequipSearch, loadEquipMap, getequip, nameToequipMap } = useEquipment(); // 部门加载状态 const deptLoading = ref(false); // 初始化加载设备列表 onMounted(() => { loadEquipMap(); }); defineOptions({ name: "LedgerOperateDrawer", }); interface Props { /** the type of operation */ operateType: NaiveUI.TableOperateType; /** the edit row data */ rowData?: Api.InterLock.Ledger | null; } const props = defineProps(); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); const { formRef, validate, restoreValidation } = useNaiveForm(); const { defaultRequiredRule } = useFormRules(); const title = computed(() => { const titles: Record<NaiveUI.TableOperateType, string> = { add: $t("common.add"), edit: $t("common.edit"), clone: $t("common.clone"), }; return titles[props.operateType]; }); const model: Api.InterLock.LedgerUpdateParams = reactive(createDefaultModel()); function createDefaultModel(): Api.InterLock.LedgerAddParams { return { alarmName: "", lockType: "sinterlock", moduleName: "", moduleType: "", product: "", deptName: "", trigger: "", channelNumber: "", triggerWay: "", alarmDesc: "", alarmLevel: "INFO", alarmDetail: "", alarmAction: "", alarmChannel: "", alarmActionDesc: "", condition: "", desc: "", }; } type RuleKey = Extract< keyof Api.InterLock.LedgerUpdateParams, | "alarmName" | "lockType" | "moduleName" | "moduleType" | "product" >; const rules = ref<Record<RuleKey, App.Global.FormRule>>({ alarmName: defaultRequiredRule, lockType: defaultRequiredRule, moduleName: defaultRequiredRule, moduleType: defaultRequiredRule, product: defaultRequiredRule, }); // 设备选择处理 const handleProductChange = async (productName: string) => { model.product = productName; deptLoading.value = true; try { if (productName) { // 优先从本地映射获取部门 if (nameToequipMap.value[productName]) { model.deptName = nameToequipMap.value[productName]; } // 本地没有则调用API else { const dept = await getequip(productName); model.deptName = dept || ""; } } else { model.deptName = ""; } } catch (error) { console.error('获取部门失败:', error); } finally { deptLoading.value = false; } }; function handleInitModel() { Object.assign(model, createDefaultModel()); if (["edit", "clone"].includes(props.operateType) && props.rowData) { // 深拷贝避免引用问题 const rowData = JSON.parse(JSON.stringify(props.rowData)); Object.assign(model, rowData); // 同步设置部门(从本地映射获取) if (rowData.product && nameToequipMap.value[rowData.product]) { model.deptName = nameToequipMap.value[rowData.product]; } } } function closeDrawer() { visible.value = false; } async function handleSubmit() { await validate(); // request // 确保部门信息已设置 if (model.product && !model.deptName) { const dept = await getequip(model.product); model.deptName = dept || ""; } if (props.operateType === "add" || props.operateType === "clone") { const { error } = await fetchAddInterlock(model); if (!error) { window.$message?.success($t("common.addSuccess")); } } else if (props.operateType === "edit") { const { error } = await fetchUpdateInterlock(model); if (!error) { window.$message?.success($t("common.updateSuccess")); } } closeDrawer(); emit("submitted"); } watch(visible, () => { if (visible.value) { handleInitModel(); restoreValidation(); } }); </script> <template> <NDrawer v-model:show="visible" display-directive="show" :width="360"> <NDrawerContent :title="title" :native-scrollbar="false" closable> <NForm ref="formRef" :model="model" :rules="rules"> <NFormItem :label="$t('page.interlock.ledger.alarmName')" path="alarmName" > <NInput v-model:value="model.alarmName" :placeholder="$t('page.interlock.ledger.form.alarmName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.lockType')" path="lockType" > <NSelect v-model:value="model.lockType" :options="translateOptions(lockTypesOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.product')" path="product"> <NSelect v-model:value="model.product" filterable clearable :placeholder="$t('page.interlock.ledger.form.product')" :options="filteredequipOptions" :loading="equipLoading" :remote="true" @update:value="handleProductChange" @search="handleequipSearch" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleName')" path="moduleName" > <NInput v-model:value="model.moduleName" :placeholder="$t('page.interlock.ledger.form.moduleName')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.moduleType')" path="moduleType" > <NSelect v-model:value="model.moduleType" :options="translateOptions(moduleTypesOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.deptName')" path="deptName" > <NInput v-model:value="model.deptName" :placeholder="$t('page.interlock.ledger.form.deptName')" readonly :loading="deptLoading" > <template #suffix> <NSpin v-if="deptLoading" size="small" /> <NIcon v-else-if="model.product && !model.deptName" :component="WarningFilled" color="#ff9900" /> </template> </NInput> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.trigger')" path="trigger"> <NInput v-model:value="model.trigger" :placeholder="$t('page.interlock.ledger.form.trigger')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.triggerWay')" path="triggerWay" > <NInput v-model:value="model.triggerWay" :placeholder="$t('page.interlock.ledger.form.triggerWay')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmDesc')" path="alarmDesc" > <NInput v-model:value="model.alarmDesc" :placeholder="$t('page.interlock.ledger.form.alarmDesc')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmLevel')" path="alarmLevel" > <NSelect v-model:value="model.alarmLevel" :options="translateOptions(logLevelTypeOptions)" size="small" class="w-120px" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmAction')" path="alarmAction" > <NInput v-model:value="model.alarmAction" :placeholder="$t('page.interlock.ledger.form.alarmAction')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmChannel')" path="alarmChannel" > <NInput v-model:value="model.alarmChannel" :placeholder="$t('page.interlock.ledger.form.alarmChannel')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.channelNumber')" path="channelNumber" > <NInput v-model:value="model.channelNumber" :placeholder="$t('page.interlock.ledger.form.channelNumber')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.alarmActionDesc')"> <NInput v-model:value="model.alarmActionDesc" :placeholder="$t('page.interlock.ledger.form.alarmActionDesc')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.condition')"> <NInput v-model:value="model.condition" :placeholder="$t('page.interlock.ledger.form.condition')" /> </NFormItem> <NFormItem :label="$t('page.interlock.ledger.desc')"> <NInput v-model:value="model.desc" :placeholder="$t('page.interlock.ledger.form.desc')" /> </NFormItem> </NForm> <template #footer> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $t("common.confirm") }}</NButton> </NSpace> </template> </NDrawerContent> </NDrawer> </template> <style scoped></style> 为什么不能自动根据设备名称自动获取部门

<script setup lang="ts"> import { onMounted, reactive, ref, watch } from "vue"; import { $t } from "@/locales"; import { UploadFileInfo, UploadInst } from "naive-ui"; import { exportXlsx, SheetData } from "@/utils/export-excel"; import { fetchAddInterlock, fetchUpdateInterlock, fetchUploadInterlockLedger} from "@/service/api"; import { useEquipment } from "@/hooks/common/useEquipment"; defineOptions({ name: "LedgerUploadDrawer", }); interface Props { /** the type of operation */ } // 引入设备钩子 const { loading: equipLoading, filteredequipOptions, handleequipSearch, loadEquipMap, getequip, nameToequipMap, resetEquipSearch } = useEquipment(); onMounted(() => { loadEquipMap(); }); const props = defineProps(); const fileListLengthRef = ref(0); const uploadRef = ref<UploadInst | null>(null); // 定义符合实际接口返回的数据结构 interface UploadResult { total_files: number; // 总文件数(数字类型) success_files: number; // 成功文件数(数字类型) total_records: number; // 导入记录数(数字类型) details: FileDetail[]; // 文件详情 } interface FileDetail { filename: string; // 文件名 status: "success" | "partial" | "fail"; // 状态 imported: number; // 导入记录数(数字类型) errors: string[]; // 错误信息 } const uploadResult = ref<UploadResult | null>(null); const loading = ref(false); const fileList = ref<UploadFileInfo[]>([]); // 使用单独的ref管理文件列表 const fileDetail = ref<FileDetail | null>(null); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); //const model: any = reactive(createDefaultModel()); const model = reactive({ // 不再需要file属性 }); function createDefaultModel(): any { return { file: null, }; } function handleInitModel() { Object.assign(model, createDefaultModel()); } function closeDrawer() { visible.value = false; } async function handleSubmit() { // request if (fileList.value.length === 0) { window.$message?.error("没有待上传文件"); return; } loading.value = true; uploadResult.value = null; // 重置结果 try { const formData = new FormData(); // 添加所有文件到FormData fileList.value.forEach((fileInfo) => { if (fileInfo.file) { formData.append("files", fileInfo.file); } }); const { data, error } = await fetchUploadInterlockLedger(formData); console.log(data) if (!error && data) { // 使用实际接口返回的数据结构 uploadResult.value = { total_files: data.total_files || fileList.value.length, success_files: data.success_files || 0, total_records: data.total_records || 0, details: data.details }; // 显示详细上传结果 // if (uploadResult.value?.success_files <= uploadResult.value?.total_files){ // window.$message?.warning( // ${uploadResult.value?.success_files}/${uploadResult.value?.total_files}个文件上传成功 // ); // } let allErrors: any[] = []; // 存储所有错误信息 if (uploadResult.value.details.length > 0) { for (var val in uploadResult.value.details) { if (uploadResult.value.details[val].errors.length > 0) { // 将当前元素的errors数组拼接到总错误列表 allErrors = allErrors.concat(uploadResult.value.details[val].errors); window.$message?.error(${uploadResult.value.details[val].filename}:${allErrors.join('\n')}); } } // 统一显示所有错误(用换行符分隔) // if (allErrors.length > 0) { // window.$message?.warning(allErrors.join('\n')); // } } console.log(allErrors) if (allErrors.length == 0) { window.$message?.success("上传成功"); } } } catch (err) { window.$message?.error("网络错误"); } finally { loading.value = false; } closeDrawer(); emit("submitted"); } function handleChange(data: { fileList: UploadFileInfo[] }) { fileList.value = data.fileList; // 更新文件列表 uploadResult.value = null; // 重置结果 } //sheet={} function downloadTemplate() { const sheets: SheetData[] = [ { sheetName: "互锁台账列表", data: [ [ "报警名称", "触发器", "满足条件", "渠道号", "报警级别", "报警描述", "触发器渠道", "报警动作", "报警动作Data通道", "报警动作描述", "互锁类型", "所属模块", "模块类型", "所属产品", "所属部门", "备注", ], ], }, ]; exportXlsx(sheets, "互锁台账导入模板"); } // watch(visible, () => { // if (visible.value) { // handleInitModel(); // } // }); function clearFiles() { fileList.value = []; if (uploadRef.value) { uploadRef.value.clear(); } uploadResult.value = null; } watch(visible, (newVal) => { if (newVal) { clearFiles(); // 每次打开时清空文件 } }); </script> <template> <NModal v-model:show="visible" preset="dialog" :title="$t('common.upload')"> <NPopover trigger="hover"> <template #trigger> <NButton @click="downloadTemplate" text type="primary">{{ $t("common.templateDownload") }}</NButton> </template> 点此下载导入模板 </NPopover> <NUpload accept=".csv,.xls,.xlsx" ref="uploadRef" :default-upload="false" :max="10" multiple :file-list="fileList" @change="handleChange" > <NUploadDragger @click="clearFiles"> <icon-mdi-tray-upload class="text-icon" /> <NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText> <NP depth="3" style="margin: 8px 0 0 0"> 请参考上传模板来提供数据,支持csv,xls,xlsx格式,单次最多上传10个文件 </NP> </NUploadDragger> </NUpload> <template #action> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit" :loading="loading" :disabled="fileList.length === 0" > {{ $t("common.confirm") }}</NButton> </NSpace> </template> </NModal> </template> <style scoped></style> 运用useEquipment

<script setup lang="tsx"> import { DataTableSortState, NButton, NPopconfirm, NTag } from "naive-ui"; import { fetchBatchDeleteInterlock, fetchDeleteInterlock, fetchGetInterlockLedgerList, } from "@/service/api"; import { $t, toSnakeNoFmt } from "@/locales"; import { useAppStore } from "@/store/modules/app"; import { useTable, useTableOperate } from "@/hooks/common/table"; import { useAuth } from "@/hooks/business/auth"; import LedgerOperateDrawer from "./modules/ledger-operate-drawer.vue"; import LedgerSearch from "./modules/ledger-search.vue"; import LedgerUploadDrawer from "./modules/ledger-upload-drawer.vue"; import InterlockDetailModal from "./modules/ledger-detail-modal.vue"; import { useBoolean } from "@sa/hooks"; import { moduleTypesRecord } from "@/constants/business"; import { exportXlsx, SheetData } from "@/utils/export-excel"; import { ref } from "vue"; import { renderTooltip2, smartTextSplit } from "@/utils/other"; import tableDownload from "@/components/common/table-Download.vue"; const appStore = useAppStore(); // 定义合法的标签类型 type TagType = "default" | "error" | "success" | "warning" | "info" | "primary"; const alarmTagColor = ref<Record<string, TagType>>({ DEBUG: "default", INFO: "info", WARN: "warning", ERROR: "error", FATAL: "error", }); const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams, } = useTable({ apiFn: fetchGetInterlockLedgerList, showTotal: true, apiParams: { current: 1, size: 10, // if you want to use the searchParams in Form, you need to define the following properties, and the value is null // the value can not be undefined, otherwise the property in Form will not be reactive alarmName: null, lockType: null, moduleName: null, moduleType: null, deptName: null, alarmLevel: null, product: null, alarmDesc: null, alarmAction: null, alarmActionDesc: null, desc: null, orderList: [], }, columns: () => [ { type: "selection", align: "center", width: 48, }, { key: "index", title: $t("common.index"), align: "center", width: 48, }, { key: "alarmName", title: $t("page.interlock.ledger.alarmName"), align: "center", width: 200, fixed: "left", sorter: true, resizable: true, ellipsis: { tooltip: true }, render: (row) => ( <NButton strong secondary size="small" type="primary" onClick={() => viewAlarm(row.id)} > {row.alarmName} </NButton> ), }, { key: "lockType", title: $t("page.interlock.ledger.lockType"), align: "center", resizable: true, minWidth: 50, render: (row) => { if (row.lockType) { return ( <NTag type={ row.lockType === "vinterlock" ? "error" : row.lockType === "sinterlock" ? "success" : "default" } bordered={false} > {row.lockType} </NTag> ); } }, }, { key: "moduleName", title: $t("page.interlock.ledger.moduleName"), align: "center", sorter: true, resizable: true, minWidth: 50, }, { key: "moduleType", title: $t("page.interlock.ledger.moduleType"), align: "center", resizable: true, minWidth: 50, render: (row) => { if (row.moduleType) { const label = $t( moduleTypesRecord[row.moduleType as Api.InterLock.moduleTypes], ); return ( <NTag type="default" bordered={false}> {label} </NTag> ); } return null; }, }, { key: "product", title: $t("page.interlock.ledger.product"), align: "center", sorter: true, minWidth: 100, resizable: true, }, { key: "deptName", title: $t("page.interlock.ledger.deptName"), align: "center", sorter: true, minWidth: 50, resizable: true, }, { key: "alarmLevel", title: $t("page.interlock.ledger.alarmLevel"), align: "center", sorter: true, resizable: true, minWidth: 50, render: (row) => { const tp = alarmTagColor.value[row.alarmLevel] || "default"; return ( <NTag type={tp} bordered={false}> {row.alarmLevel} </NTag> ); }, }, { key: "trigger", title: $t("page.interlock.ledger.trigger"), align: "center", sorter: true, resizable: true, width: 170, render: (row) => { const contents: any[] = smartTextSplit(row.trigger, 50); return row.trigger ? renderTooltip2(row.trigger.slice(0, 20), contents) : null; }, }, { key: "triggerWay", title: $t("page.interlock.ledger.triggerWay"), align: "center", sorter: true, resizable: true, minWidth: 100, }, // { // key: "alarmDesc", // title: $t("page.interlock.ledger.alarmDesc"), // align: "center", // width: 100, // render: (row) => ( // <NTag bordered={false} type="info"> // <NEllipsis style="max-width:80px">{row.alarmDesc}</NEllipsis> // </NTag> // ), // }, // { // key: "alarmAction", // title: $t("page.interlock.ledger.alarmAction"), // align: "center", // minWidth: 50, // }, // { // key: "alarmChannel", // title: $t("page.interlock.ledger.alarmChannel"), // align: "center", // minWidth: 50, // }, // { // key: "alarmActionDesc", // title: $t("page.interlock.ledger.alarmActionDesc"), // align: "center", // width: 90, // render: (row) => ( // <NTag bordered={false} type="info"> // <NEllipsis style="max-width:70px">{row.alarmDesc}</NEllipsis> // </NTag> // ) // }, // { // key: "condition", // title: $t("page.interlock.ledger.condition"), // align: "center", // minWidth: 50, // render: (row) => ( // <NTag bordered={false} type="info"> // <NEllipsis style="max-width:70px">{row.condition}</NEllipsis> // </NTag> // ), // }, // { // key: "channelNumber", // title: $t("page.interlock.ledger.channelNumber"), // align: "center", // minWidth: 50, // }, // { // key: "alarmDetail", // title: $t("page.interlock.ledger.alarmDetail"), // align: "center", // minWidth: 50, // }, { key: "desc", title: $t("page.interlock.ledger.desc"), align: "center", minWidth: 50, }, { key: "operate", title: $t("common.operate"), align: "center", width: 160, render: (row) => ( {hasAuth("B_Add_Del_Batch-del") ? ( <NButton type="primary" ghost size="small" onClick={() => edit(row.id)} > {$t("common.edit")} </NButton> ) : null} {hasAuth("B_Add_Del_Batch-del") ? ( <NPopconfirm onPositiveClick={() => handleDelete(row.id)}> {{ default: () => $t("common.confirmDelete"), trigger: () => ( <NButton type="error" ghost size="small"> {$t("common.delete")} </NButton> ), }} </NPopconfirm> ) : null} {hasAuth("B_InterLockLedger_Clone") ? ( <NButton type="primary" ghost size="small" onClick={() => clone(row.id)} > {$t("common.clone")} </NButton> ) : null} ), }, ], }); const { drawerVisible, operateType, editingData, handleAdd, getEditingData, handleEdit, handleClone, checkedRowKeys, onBatchDeleted, onDeleted, // closeDrawer } = useTableOperate(data, getData); const { hasAuth } = useAuth(); const uploadModal = useBoolean(); const detailModal = useBoolean(); const { bool: uploadVisible, setTrue: openUploadModal } = uploadModal; const { bool: detailVisible, setTrue: openDetailModal } = detailModal; const viewAlarm = (id: number) => { console.log("打开互锁详情对话框", id, editingData); //handleEdit(id); getEditingData(id); openDetailModal(); }; async function handleBatchDelete() { // request const { error } = await fetchBatchDeleteInterlock({ ids: checkedRowKeys.value, }); if (!error) { onBatchDeleted(); } } async function handleDelete(id: number) { // request const { error } = await fetchDeleteInterlock({ id }); if (!error) { onDeleted(); } } function edit(id: number) { handleEdit(id); } function clone(id: number) { handleClone(id); } const handleSorterChange = (newSorter: DataTableSortState) => { //小驼峰命名转换成下划线命名 去除fmt let columnKey = toSnakeNoFmt(String(newSorter.columnKey)); console.log(newSorter.columnKey, newSorter.order); if (newSorter.order === "ascend") { searchParams.orderList = [columnKey]; } else if (newSorter.order === "descend") { searchParams.orderList = ["-" + columnKey]; } else { searchParams.orderList = []; } getData(); }; </script> <template> <LedgerSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" /> <NCard :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper"> <template #header >{{ $t("page.interlock.ledger.title") }} ({{ $t("datatable.itemCount", { total: mobilePagination?.itemCount, }) }}) </template> <template #header-extra> <template #prefix> <NButton size="small" @click=" () => { openUploadModal(); } " > <template #icon> <icon-mdi-tray-upload class="text-icon" :class="{ 'animate-spin': loading }" /> </template> {{ $t("common.upload") }} </NButton> </template> </template> <NDataTable v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" size="small" :flex-height="!appStore.isMobile" :scroll-x="962" :loading="loading" remote :row-key="(row) => row.id" :pagination="mobilePagination" class="sm:h-full" @update:sorter="handleSorterChange" /> <InterlockDetailModal v-model:visible="detailVisible" operate-type="detail" :row-data="editingData" /> <LedgerOperateDrawer v-model:visible="drawerVisible" :operate-type="operateType" :row-data="editingData" @submitted="getData" /> <LedgerUploadDrawer v-model:visible="uploadVisible" @submitted="getData"> </LedgerUploadDrawer> </NCard> </template> <style scoped></style> 被注释的列不想在前端展示,但是需要导出; <template> <n-popconfirm @positive-click="handlePositveClick" @negative-click="handleNegativeClick" > <template #trigger> <NButton size="small" type="primary" ghost @click="init"> <template #icon> <n-spin size="small" v-if="loading" style="width: 5px"></n-spin> <icon-mdi-tray-download v-else :class="{ 'animate-spin': loading, width: '5px' }" /> </template> {{ $t("common.download") }} </NButton> </template> {{ text }} </n-popconfirm> </template> <script setup lang="ts"> import { exportXlsx, SheetData } from "@/utils/export-excel"; import { NCard, NText, useDialog, useMessage } from "naive-ui"; import { ref, onMounted, onUnmounted, nextTick, watch, Ref, inject } from "vue"; defineOptions({ name: "tableDownload", }); const message = useMessage(); const dialog = useDialog(); type Milliseconds = number; const props = defineProps<{ size: number; ids: any; apiFn: any; apiParams: Api.Product.ProductSearchParams; columns: any; excelName: string; timeOut?: Milliseconds; }>(); let apiParams = ref(); const loading = ref(false); const text = ref(); const init = () => { apiParams.value = { ...formatSearchParams(props.apiParams), ...{ size: props.size == 0 ? -1 : props.size, //-1 后端返回符合条件的全部数据 current: 1, ids: props.ids, }, }; console.log(apiParams.value); if (props.size === 0) { text.value = "即将下载符合条件的全部数据!"; } else { text.value = "即将下载选择的数据!"; } }; const handlePositveClick = () => { handleDownload(apiParams.value); console.log("handlePositveClick"); }; const handleNegativeClick = () => { loading.value = false; console.log("handleNegativeClick"); }; async function handleDownload(apiParams: any) { loading.value = true; console.log(apiParams); const { data, error } = await props.apiFn(apiParams, props.timeOut); if (error) { dialog.error({ title: "获取数据时出错", content: error, }); console.error("获取数据时出错:", error); loading.value = false; return; } // 获取表头 const titleMap: any = buildTitleMap(props.columns); const headers = Object.keys(titleMap).map((key) => titleMap[key]); const sd = [headers]; data.records.forEach((e: { [x: string]: any }) => { const tmp: any[] = []; Object.keys(titleMap).forEach((key) => { tmp.push(e[key]); }); sd.push(tmp); }); const sheetName = props.excelName; const sheets: SheetData[] = [ { sheetName: sheetName, data: sd, }, ]; exportXlsx(sheets, ${sheetName}); loading.value = false; } function extractTextFromVNode(vnode: Node | Node[] | string): string { if (typeof vnode === "string") return vnode; if (Array.isArray(vnode)) return vnode.map(extractTextFromVNode).join(""); if (vnode && "children" in vnode) { const children = vnode.children; if (children === undefined) { return ""; } return extractTextFromVNode(children as string | Node | Node[]); } return ""; } function buildTitleMap(columns: any[]): Record<string, string> { const titleMap: Record<string, string> = { id: "ID", }; columns.forEach((col) => { const { key, title } = col; //如果key不存在,跳过 if (!key || key === "index") { return; } let headerTitle = "Unknown"; if (typeof title === "function") { const vnode = title(); headerTitle = extractTextFromVNode(vnode); } else if (typeof title === "string") { headerTitle = title; } //将 key 与标题存入 titleMap titleMap[key] = headerTitle; }); return titleMap; } async function useTableDownload( apiFn: any, downloadParams: any, columns: any, excelName: string, ) {} function formatSearchParams(params: Record<string, unknown>) { const formattedParams: Record<string, unknown> = {}; Object.entries(params).forEach(([key, value]) => { if (value !== null && value !== undefined) { formattedParams[key] = value; } }); return formattedParams; } </script> <style scoped lang="scss"> :deep(.n-spin) { font-size: 15px; } :deep(.n-base-loading .n-base-loading__container) { width: 1rem; height: 1rem; animation: rotator 3s linear infinite both; margin-top: 5px; margin-left: 5px; } </style> 这是表格导出组件

<script setup lang="ts"> import { onMounted, reactive, ref, watch } from "vue"; import { $t } from "@/locales"; import { UploadFileInfo, UploadInst } from "naive-ui"; import { exportXlsx, SheetData } from "@/utils/export-excel"; import { fetchAddInterlock, fetchUpdateInterlock, fetchUploadInterlockLedger} from "@/service/api"; import { useEquipment } from "@/hooks/common/useEquipment"; defineOptions({ name: "LedgerUploadDrawer", }); interface Props { /** the type of operation */ } // 引入设备钩子 const { loading: equipLoading, filteredequipOptions, handleequipSearch, loadEquipMap, getequip, nameToequipMap, resetEquipSearch } = useEquipment(); onMounted(() => { loadEquipMap(); }); const props = defineProps(); const fileListLengthRef = ref(0); const uploadRef = ref<UploadInst | null>(null); // 定义符合实际接口返回的数据结构 interface UploadResult { total_files: number; // 总文件数(数字类型) success_files: number; // 成功文件数(数字类型) total_records: number; // 导入记录数(数字类型) details: FileDetail[]; // 文件详情 } interface FileDetail { filename: string; // 文件名 status: "success" | "partial" | "fail"; // 状态 imported: number; // 导入记录数(数字类型) errors: string[]; // 错误信息 } const uploadResult = ref<UploadResult | null>(null); const loading = ref(false); const fileList = ref<UploadFileInfo[]>([]); // 使用单独的ref管理文件列表 const fileDetail = ref<FileDetail | null>(null); interface Emits { (e: "submitted"): void; } const emit = defineEmits<Emits>(); const visible = defineModel<boolean>("visible", { default: false, }); //const model: any = reactive(createDefaultModel()); const model = reactive({ // 不再需要file属性 }); function createDefaultModel(): any { return { file: null, }; } function handleInitModel() { Object.assign(model, createDefaultModel()); } function closeDrawer() { visible.value = false; } async function handleSubmit() { // request if (fileList.value.length === 0) { window.$message?.error("没有待上传文件"); return; } loading.value = true; uploadResult.value = null; // 重置结果 try { const formData = new FormData(); // 添加所有文件到FormData fileList.value.forEach((fileInfo) => { if (fileInfo.file) { formData.append("files", fileInfo.file); } }); const { data, error } = await fetchUploadInterlockLedger(formData); console.log(data) if (!error && data) { // 使用实际接口返回的数据结构 uploadResult.value = { total_files: data.total_files || fileList.value.length, success_files: data.success_files || 0, total_records: data.total_records || 0, details: data.details }; // 显示详细上传结果 // if (uploadResult.value?.success_files <= uploadResult.value?.total_files){ // window.$message?.warning( // ${uploadResult.value?.success_files}/${uploadResult.value?.total_files}个文件上传成功 // ); // } let allErrors: any[] = []; // 存储所有错误信息 if (uploadResult.value.details.length > 0) { for (var val in uploadResult.value.details) { if (uploadResult.value.details[val].errors.length > 0) { // 将当前元素的errors数组拼接到总错误列表 allErrors = allErrors.concat(uploadResult.value.details[val].errors); window.$message?.error(${uploadResult.value.details[val].filename}:${allErrors.join('\n')}); } } // 统一显示所有错误(用换行符分隔) // if (allErrors.length > 0) { // window.$message?.warning(allErrors.join('\n')); // } } console.log(allErrors) if (allErrors.length == 0) { window.$message?.success("上传成功"); } } } catch (err) { window.$message?.error("网络错误"); } finally { loading.value = false; } closeDrawer(); emit("submitted"); } function handleChange(data: { fileList: UploadFileInfo[] }) { fileList.value = data.fileList; // 更新文件列表 uploadResult.value = null; // 重置结果 } //sheet={} function downloadTemplate() { const equipList = Object.keys(nameToequipMap.value); const endRow = equipList.length + 1; // 计算结束行 // 创建主工作表 const mainSheet: SheetData = { sheetName: "互锁台账列表", data: [ // 表头行 [ "报警名称", "触发器", "满足条件", "渠道号", "报警级别", "报警描述", "触发器渠道", "报警动作", "报警动作Data通道", "报警动作描述", "互锁类型", "所属模块", "模块类型", "所属产品", // 需要设置下拉选择的列 "所属部门", "备注" ], ], // 优化数据验证:使用动态范围引用 dataValidations: [{ type: 'list', formula1: 设备选项说明!$A$2:$A$${endRow}, ref: "N2:N1000" // 修正:从第3行开始应用 }] }; // 创建设备选项说明表 const helpSheet: SheetData = { sheetName: "设备选项说明", data: [ ["设备选项列表"], ...equipList.map(item => [item]) ], }; exportXlsx([mainSheet, helpSheet], "互锁台账导入模板"); } // watch(visible, () => { // if (visible.value) { // handleInitModel(); // } // }); function clearFiles() { fileList.value = []; if (uploadRef.value) { uploadRef.value.clear(); } uploadResult.value = null; } watch(visible, (newVal) => { if (newVal) { clearFiles(); // 每次打开时清空文件 } }); </script> <template> <NModal v-model:show="visible" preset="dialog" :title="$t('common.upload')"> <NPopover trigger="hover"> <template #trigger> <NButton @click="downloadTemplate" text type="primary">{{ $t("common.templateDownload") }}</NButton> </template> 点此下载导入模板 </NPopover> <NUpload accept=".csv,.xls,.xlsx" ref="uploadRef" :default-upload="false" :max="10" multiple :file-list="fileList" @change="handleChange" > <NUploadDragger @click="clearFiles"> <icon-mdi-tray-upload class="text-icon" /> <NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText> <NP depth="3" style="margin: 8px 0 0 0"> 请参考上传模板来提供数据,支持csv,xls,xlsx格式,单次最多上传10个文件 </NP> </NUploadDragger> </NUpload> <template #action> <NSpace :size="16"> <NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton> <NButton type="primary" @click="handleSubmit" :loading="loading" :disabled="fileList.length === 0" > {{ $t("common.confirm") }}</NButton> </NSpace> </template> </NModal> </template> <style scoped></style> 那就用exceljs 帮我修改这个代码,对模版的"所属产品设置下拉选择的列

最新推荐

recommend-type

工业自动化中PLC与MCGS在小型水厂恒压供水系统中的应用设计 - 工业自动化 指南

内容概要:本文介绍了基于S7-200 PLC与MCGS组态软件的小型水厂恒压供水系统设计,通过PLC作为核心控制单元,结合压力传感器、水泵及管网,实现供水压力的自动调节。系统采用梯形图与指令表混合编程方式,依据实时压力数据动态调整水泵运行数量与转速,确保供水稳定并实现节能降耗。实际案例表明,该系统显著提升了供水稳定性并降低了能耗。 适合人群:从事工业自动化、水利系统设计、PLC编程及相关工程应用的工程师和技术人员,具备一定PLC与自动化基础知识的1-3年经验研发人员。 使用场景及目标:适用于小型水厂、社区供水系统等需要恒压供水的场景,目标是实现供水压力稳定、节能运行、自动化控制,并提升系统可靠性与经济效益。 阅读建议:结合S7-200 PLC编程实践与MCGS组态软件操作,重点关注控制逻辑设计、传感器信号处理及节能算法实现,建议配合实际系统调试加深理解。
recommend-type

Java源码-ssm635政府项目管理平台+vue+前后分离毕设项目(完整项目).zip

本项目是基于Java源码的ssm635政府项目管理平台,采用前后分离架构,前端使用Vue框架,后端基于SSM(Spring、SpringMVC、MyBatis)技术栈开发。项目旨在为政府机构提供高效的项目管理解决方案,涵盖项目申报、审批、进度跟踪、资源分配、文档管理等功能模块。通过前后分离的设计,提升了系统的可维护性和用户体验。项目支持多角色权限管理,确保数据安全与操作规范。开发此项目的目的是为了满足政府项目管理的实际需求,提高工作效率,实现信息化管理。毕设项目源码常年开发定制更新,希望对需要的同学有帮助。
recommend-type

钢结构规范讲座.ppt

钢结构规范讲座.ppt
recommend-type

一汽大众汽车有限公司涂装相关污染物终端治理方案的探索.pdf

一汽大众汽车有限公司涂装相关污染物终端治理方案的探索.pdf
recommend-type

基于二次近似的双层进化算法附Matlab代码.rar

1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行。
recommend-type

在Windows环境下安装配置高性能负载均衡软件Haproxy

### 知识点详细说明 #### 标题:haproxy-window 标题中提到的“haproxy-window”暗示了该文档或文件集合针对的是Windows操作系统平台,特别是Windows 7 64位版本和Windows 2008 R2服务器版本。它指明了HAProxy这一软件产品在Windows环境下的兼容性和适用性。 #### 描述:兼容在win7 64bit/window2008R2下使用。Haproxy是一个开源的高性能的反向代理或者说是负载均衡服务软件之一,它支持双机热备、虚拟主机、基于TCP和HTTP应用代理等功能。 描述部分详细地介绍了HAProxy的一些关键功能和特点,以及它的适用范围。 1. **HAProxy在Windows环境的兼容性**: - HAProxy通常在Linux环境下运行,不过文档描述表明它也可以在Windows 7 64位系统和Windows Server 2008 R2上运行,这提供了微软环境下的负载均衡解决方案。 2. **HAProxy定义**: - HAProxy是一个高性能的开源软件,它的主要职责是作为反向代理和负载均衡器。反向代理的工作原理是接收客户端请求,然后将这些请求转发到后端服务器,之后再将服务器的响应返回给客户端。 3. **负载均衡功能**: - HAProxy的一个核心功能是负载均衡,它能够将流量分散到多个服务器上,以避免任何单一服务器上的过载,同时提高应用的整体性能和可靠性。 4. **高可用性特性**: - 双机热备功能确保了在一个主服务器发生故障时,可以迅速切换到备用服务器上,从而实现服务的连续性,减少宕机时间。 5. **虚拟主机支持**: - 虚拟主机支持指的是HAProxy能够处理在同一IP地址上托管多个域名的网站,每个网站就像在独立服务器上运行一样。这对于在单个服务器上托管多个网站的情况非常有用。 6. **协议支持**: - HAProxy支持基于TCP和HTTP协议的应用代理。这表示它可以管理不同类型的网络流量,包括Web服务器流量和更通用的网络应用流量。 #### 标签:haproxy 标签“haproxy”强调了文档或文件集合的焦点是HAProxy负载均衡软件。这可以帮助用户快速识别文档内容与HAProxy相关的特性、配置、故障排除或使用案例。 #### 压缩包子文件的文件名称列表:haproxy-1.7.8 文件列表中“haproxy-1.7.8”指的是HAProxy的一个特定版本。这个版本号表明了用户可以预期关于该版本的具体信息、更新内容、新功能或是潜在的修复。 ### 总结 本文介绍了HAProxy在Windows环境下的应用,特别是其在Windows 7 64位和Windows Server 2008 R2操作系统上的运行能力。HAProxy作为一款负载均衡器和反向代理服务,提供了多种服务功能,包括高可用性的双机热备、支持虚拟主机以及基于TCP和HTTP协议的应用代理功能。这个软件是开源的,并且不断有新版本发布,如版本1.7.8,每一个版本都可能包含性能改进、新功能和安全更新。对于在Windows环境下寻求负载均衡解决方案的系统管理员和技术人员来说,HAProxy是一个重要的资源和工具。
recommend-type

元宇宙中的智能扩展现实:新兴理论与应用探索

# 元宇宙中的智能扩展现实:新兴理论与应用 ## 1. 元宇宙的特征 元宇宙是一个具有多种独特特征的环境,这些特征使其区别于传统的现实世界和虚拟世界。具体如下: - **协作环境**:人们在元宇宙中协作以实现经济、社会和休闲等不同目标。 - **在线空间**:基于三维的在线环境,人们可以沉浸其中。 - **共享世界**:人们能够分享活动、观点和信息,购物也成为一种网络化体验。 - **增强和科技化场所**:借助增强现实技术,人们可以丰富体验,还能通过虚拟元素、技术和互联网进行社交和互动。 - **多用户环境**:人们可以同时使用相同的技术或进行相同的活动,是现实生活的延伸。 - **无限世界
recommend-type

mockitomonkey

在讨论 **Mockito** 和 **Monkey Testing** 时,通常会涉及两个不同的技术领域:一个是单元测试中的模拟框架(Mockito),另一个是自动化测试中用于随机事件生成的测试方法(Monkey Testing)。以下是它们的定义、用途及可能的结合方式。 ### Mockito 框架概述 Mockito 是一个流行的 Java 单元测试框架,它允许开发者创建和配置模拟对象(mock objects),从而在不依赖外部系统或复杂对象的情况下测试代码逻辑。Mockito 的主要优势在于其简洁的 API 和强大的验证功能,例如: - 模拟接口或类的行为 - 验证方法调用次数
recommend-type

深度学习中的卷积运算指南:调参与矩阵操作

这篇文章是一份关于深度学习中卷积算术的指南,特别是在卷积神经网络(CNN)中的调参指导。深度学习是一种基于人工神经网络的学习方法,它在图像识别、语音识别和自然语言处理等众多领域取得了突破性的成果。而卷积神经网络是深度学习中最重要、最具影响力的一类神经网络模型,尤其在图像处理领域表现出色。本文将详细探讨卷积操作及其算术的基础知识,以及如何对其进行有效调参。 1. 卷积操作的基础 1.1 离散卷积 离散卷积是卷积神经网络中最基本的运算之一。在数学上,两个离散函数的卷积可以被定义为一个新函数,它是两个函数相对滑动并相乘后积分(或求和)的结果。在计算机视觉中,通常使用的是二维离散卷积,它处理的是图像矩阵。卷积核(或滤波器)在图像上滑动,每次与图像的一个局部区域相乘并求和,生成一个新的二维矩阵,也就是特征图(feature map)。 1.2 池化 池化(Pooling)是降低特征维度的一种常用技术,目的是减少计算量并防止过拟合。池化操作通常跟随在卷积操作之后。最常用的池化操作是最大池化(Max Pooling),它通过选择每个池化窗口内的最大值来替代该窗口内的所有值。池化操作还可以是平均池化(Average Pooling)等其他类型。 2. 卷积算术 2.1 无零填充,单位步长 在没有使用零填充(padding)和使用单位步长(stride)的情况下,卷积操作可能会导致特征图的尺寸小于输入图像尺寸。步长表示卷积核每次移动的像素数。 2.2 零填充,单位步长 零填充可以保持特征图的尺寸不变。有两种常见的零填充方式:半填充(same padding)和全填充(full padding)。半填充使得输出特征图的宽度和高度与输入一致;全填充则使得特征图的尺寸更大。 2.2.1 半(same)填充 使用半填充是为了保持特征图与输入图像尺寸一致,其计算方法是根据卷积核尺寸和步长来确定填充的数量。 2.2.2 全填充 全填充通常用于保证所有输入像素均被卷积核考虑,但结果特征图尺寸会大于输入。 2.3 无零填充,非单位步长 当步长大于1时,输出特征图的尺寸会小于使用单位步长的情况。非单位步长的卷积操作通常用于减少特征图的尺寸,以降低模型复杂度和计算量。 2.4 零填充,非单位步长 在使用非单位步长的同时,结合零填充可以更灵活地控制特征图的尺寸。可以基于需要的输出尺寸和卷积核大小来决定填充的量。 3. 池化算术 池化算术涉及到将输入特征图分割成多个区域,并从每个区域中选择代表值(通常是最大值或平均值)形成输出特征图。池化算术包括了池化区域的大小和步长的设定,其设计直接影响到网络的特征抽象能力和感受野大小。 4. 转置卷积算术 4.1 卷积作为矩阵操作 转置卷积有时被称为分数步长卷积,它在数学上可以被看作是传统卷积操作的转置。这意味着它是传统卷积操作矩阵表示的反操作。 4.2 转置卷积 转置卷积在实现上通常通过零填充和插值来扩展输入特征图的尺寸,常用于生成图像的上采样过程中,例如在像素点生成任务中。 4.3-4.6 不同的填充和步长的转置卷积 文章继续详细讨论了不同零填充和步长设置下的转置卷积算术。在转置卷积中,单位步长与非单位步长的处理方式与传统卷积相似,但转置卷积的目的在于增大特征图尺寸,这与传统卷积操作减少特征图尺寸相反。转置卷积算术在生成模型如GAN(生成对抗网络)中尤为重要,它帮助网络生成高分辨率的图像。 标签中提到了CNN调参、机器学习、深度学习和padding。这些标签体现了本文的重点是卷积神经网络中的参数调整,特别是如何通过调整padding来控制输出特征图的大小。此外,文章还涉及机器学习和深度学习的基础知识,强调了在设计CNN模型时对卷积层和池化层进行参数设置的重要性。 从文件名称列表中可以看到,这篇指南由两位作者编写,其中lecun-98.pdf可能是指Yann LeCun教授在1998年发表的关于深度学习卷积网络的开创性工作,而A guide to convolution arithmetic for deep learning.pdf正是本文档的标题。 总结来说,本文提供了一个全面的指南,通过详细讲解卷积和池化操作的各种参数设置,帮助读者理解如何在CNN中进行有效的调参,以及这些操作是如何对深度学习模型的性能产生影响的。通过合理地应用这些知识,可以优化模型结构,提高模型的性能和效率。
recommend-type

奢侈品时尚零售中的人工智能与扩展现实

# 奢侈品时尚零售中的人工智能与扩展现实 ## 1. 纳米层面的双重关系 在奢侈品时尚零售领域,纳米层面体现了一线员工与奢侈品时尚消费者之间的双重关系。一线员工不仅包括人类,还涵盖了人工智能代理,如聊天机器人和店内机器人。人类一线员工需依据零售组织文化和身份接受培训,同时享有所在国家法律规定的劳动权利和义务,并遵循时尚奢侈品牌的总体政策。 而人工智能代理在知识和情感方面不断进化,最终可能会更清晰地意识到自身存在,甚至开始主张权利,未来还有可能成为消费者。与此同时,融合纳米技术设备或采用增强能力假肢的混合人类,也能同时扮演员工和顾客的双重角色。 在这种情况下,人类与人工智能代理、不同技术水