<a-table :row-selection="rowSelection" :columns="computedColumns" :customRow="handleCheck" :data-source="list" :pagination="pagination" @change="tableChange" >找不到key是在哪里设置的

时间: 2025-08-01 15:33:48 AIGC 浏览: 22
<think>我们正在讨论的是在Vue或React中使用AntDesign的Table组件(a-table)时如何正确设置key属性。关键点:1.在AntDesign中,表格的每一行需要一个唯一的key来标识。2.在React版本的AntDesignTable中,我们通常使用`rowKey`属性来指定数据源中作为唯一标识的字段名,或者一个函数来生成唯一key。3.在Vue版本的AntDesignTable中,同样使用`rowKey`属性(在Vue中可能是`:rowKey`)来指定。根据引用[1]中提到的ali-react-table(一个高性能的React表格组件)也强调了性能,其中虚拟滚动依赖于正确的key设置。引用[2]展示了Vue中a-table的使用,其中提到了column.key,但这里我们需要的是行的key,不是列的key。引用[3]展示了React中AntDesignTable的使用,其中在展开行时使用了`defaultExpandedRowKeys`,它需要一个key的数组,这个key就是行的key。在示例中,他们使用`pageList.map(r=>r.key)`,说明数据源中有一个key字段。引用[4]则展示了Vue中columns的定义,其中每列通过dataIndex来指定数据字段,但没有直接涉及行key。因此,我们重点讨论如何设置行的key。在ReactAntDesignTable中:-通过`rowKey`属性设置,可以是字符串(指定数据中的字段名)或函数(如:`rowKey={record=>record.id}`)。-如果没有设置`rowKey`,Table会默认使用数据源的索引(index)作为key,但这在数据排序、过滤、分页等操作时可能会导致问题,因为索引会变。在VueAntDesignTable中:-同样通过`rowKey`属性设置,可以是字符串或函数。在Vue中,我们通常使用绑定属性:`:rowKey="(record)=>record.id"`或`rowKey="id"`。注意事项:-key必须是唯一的,否则会导致渲染错误。-如果数据中没有唯一标识,可以考虑使用函数生成一个唯一值(例如,使用数据中的多个字段组合,或者使用第三方库生成唯一ID),但通常建议后端提供唯一标识。下面分别给出React和Vue的示例:###ReactAntDesignTable设置rowKey```jsximport{Table}from'antd';constdataSource=[{id:'1',//唯一标识name:'JohnBrown',age:32,},{id:'2',name:'JimGreen',age:42,},];constcolumns=[{title:'Name',dataIndex:'name',key:'name',},{title:'Age',dataIndex:'age',key:'age',},];//使用字符串指定key字段<TabledataSource={dataSource}columns={columns}rowKey="id"/>//或者使用函数<TabledataSource={dataSource}columns={columns}rowKey={record=>record.id}/>```###VueAntDesignTable设置rowKey```vue<template><a-table:data-source="dataSource":columns="columns":rowKey="record=>record.id"></a-table><!--或者--><a-table:data-source="dataSource":columns="columns"rowKey="id"></a-table></template><script>exportdefault{data(){return{dataSource:[{id:'1',name:'JohnBrown',age:32,},{id:'2',name:'JimGreen',age:42,},],columns:[{title:'Name',dataIndex:'name',key:'name',},{title:'Age',dataIndex:'age',key:'age',},],};},};</script>```###常见问题及解决1.**为什么设置了rowKey,但是选择行、展开行等功能仍然有问题?**-确保rowKey指定的字段在每条数据中都是唯一的,且不会变化。如果数据中有重复的id,或者id在数据更新时发生变化,会导致问题。-在动态数据(比如从后端获取)中,确保数据加载完成后再渲染表格(如引用[3]中,他们通过判断数据长度来条件渲染表格)。2.**数据中没有唯一字段怎么办?**-如果数据中确实没有唯一字段,可以考虑使用函数生成一个唯一key,例如:```jsx//React<TablerowKey={record=>`${record.field1}-${record.field2}`}/>``````vue<!--Vue--><a-table:rowKey="record=>`${record.field1}-${record.field2}`"/>```-或者使用索引(不推荐,因为索引在数据变动时会变):```jsx<TablerowKey={(record,index)=>index}/>//注意:当数据会变化(排序、过滤等)时,这样会导致问题```3.**在Vue中,使用rowKey属性时,如果直接写字符串(如`rowKey="id"`)和写函数(`:rowKey="record=>record.id"`)有什么区别?**-在Vue中,`rowKey="id"`是字符串属性,表格内部会将其解释为从记录中获取id字段作为key。-`:rowKey="record=>record.id"`是传递一个函数,效果相同,但更灵活(比如可以组合多个字段)。4.**使用虚拟滚动时,key的重要性**-如引用[1]所述,虚拟滚动需要正确的key来跟踪行位置,如果key不稳定,会导致滚动时出现错乱。总结:设置key是使用AntDesignTable组件的重要步骤,确保每一行有一个稳定且唯一的标识,这样表格的各种功能(选择、展开、排序等)才能正常工作。相关问题:1.在AntDesignTable中,如何实现行的单选模式?2.如何动态更新AntDesignTable的数据源并保持选中行的状态?3.AntDesignTable中如何根据行数据禁用某些行的选择?4.在AntDesignTable中同时使用行选择和行展开时需要注意什么?5.如何解决AntDesignTable在动态加载数据时展开行不生效的问题?</think>### Ant Design Table 中 Key 属性设置指南 在 Vue 和 React 中使用 Ant Design Table 组件时,正确设置 `key` 属性至关重要,它直接关系到表格性能、行选择、展开等功能的正确性。 #### 一、核心概念 1. **`rowKey` 的作用**: - 唯一标识表格中的每一行数据 - 用于行选择(`rowSelection`)、行展开(`expandable`)等功能的关联 - 优化虚拟滚动性能(引用[1]中提到的万级数据处理依赖此机制) 2. **与普通 `key` 的区别**: - 普通 `key`:React/Vue 列表渲染的通用优化属性 - `rowKey`:Ant Design Table 特有的行标识配置 #### 二、Vue 版本设置方式 ```vue <template> <a-table :data-source="data" :rowKey="rowKeyResolver" // ✨ 关键设置 :row-selection="{ selectedRowKeys }" :expandable="{ defaultExpandedRowKeys }" > <!-- 列定义 --> </a-table> </template> <script> export default { data() { return { // 数据源必须包含唯一标识字段 data: [ { id: 'user1', name: '张三', age: 28 }, { id: 'user2', name: '李四', age: 32 } ], // 行选择绑定值 selectedRowKeys: [], // 行展开绑定值 defaultExpandedRowKeys: ['user1'] } }, methods: { // 推荐方式:函数指定 key rowKeyResolver(record) { return record.id; // 使用唯一字段 }, // 备选方式:字符串指定 key 字段名 // rowKey: 'id' } } </script> ``` #### 三、React 版本设置方式 ```jsx import { Table } from 'antd'; function App() { const data = [ { userId: 'U001', name: 'John' }, { userId: 'U002', name: 'Jane' } ]; return ( <Table dataSource={data} rowKey={record => record.userId} // ✨ 关键设置 rowSelection={{ selectedRowKeys, onChange: setSelectedRowKeys }} expandable={{ defaultExpandedRowKeys: ['U001'] }} /> ); } ``` #### 四、配置规则与最佳实践 | **场景** | **推荐写法** | **注意事项** | |----------|--------------|--------------| | 数据有唯一ID | `rowKey="id"` | 确保字段值唯一 | | 复合主键 | `:rowKey="r => `${r.id}-${r.dept}`"` | 避免冲突 | | 无唯一字段 | `rowKey={(r,i) => `row-${i}`}` | 最后手段(性能较差) | | 动态数据 | 数据更新后重置选择状态 | 引用[3]的解决方案 | | 服务端分页 | 使用稳定ID(非索引) | 跨页选择必需 | #### 五、常见问题解决 1. **行选择绑定值异常**: ```jsx // React 错误:绑定到索引值 // ❌ 错误:selectedRowKeys 会变成 [0, 1] 而不是 ['U001','U002'] <Table rowKey={(record, index) => index} ... /> // ✅ 正确:绑定到稳定ID <Table rowKey="userId" ... /> ``` 2. **行展开不生效**(引用[3]问题): ```jsx // 确保数据加载完成后再设置展开(引用[3]方案) {data.length > 0 && ( <Table expandable={{ defaultExpandedRowKeys: data.map(item => item.key) }} /> )} ``` 3. **虚拟滚动性能优化**(引用[1]): - 必须设置 `rowKey` 才能启用虚拟滚动 - 确保 key 值是稳定不变的(避免使用随机数) #### 六、调试技巧 1. 检查 key 值唯一性: ```javascript // Vue mounted() { const keys = this.data.map(item => this.rowKeyResolver(item)); const uniqueKeys = new Set(keys); if(keys.length !== uniqueKeys.size) { console.error('存在重复的rowKey值'); } } ``` 2. 监控选择状态变化: ```jsx // React const onSelectChange = (selectedKeys, selectedRows) => { console.log('Selected Keys:', selectedKeys); setSelectedRowKeys(selectedKeys); }; ``` > **关键总结**:`rowKey` 是 Ant Design Table 的核心配置,必须确保: > 1. 使用数据中的**稳定唯一标识** > 2. 避免使用数组索引(分页/排序时会失效) > 3. 在 Vue 和 React 中的配置方式类似(函数或字符串) > 4. 正确设置可保证行选择/展开/虚拟滚动等功能正常工作[^1][^3] --- ### 相关问题 1. Ant Design Table 如何实现跨页保持选中状态? 2. 虚拟滚动开启条件是什么?如何优化万级数据表格性能? 3. 动态更新数据源时如何避免行展开状态丢失? 4. 如何在自定义列渲染中获取当前行的 key 值? 5. 复合主键场景下如何确保 rowKey 的唯一性?
阅读全文

相关推荐

<template> <a-card :bordered="false" style="margin-bottom: 10px;"> </a-card> <a-card :bordered="false" > <bt-inventory-add-form v-if="showAddModal" ref="btInventoryAddForm" :contractId="contractId" @ok="getList" @close="showAddModal = false" /> <bt-inventory-edit-form v-if="showEditModal" ref="btInventoryEditForm" :contractId="contractId" @ok="getList" @close="showEditModal = false" /> <a-tooltip title="刷新"> <a-icon @click="getList" class="action" :type="loading ? 'loading' : 'reload'" /> </a-tooltip> <a-table :loading="loading" rowKey="id" size="middle" @change="handleTableChange" @refresh="getList" :columns="columns" :data-source="btInventoryList" :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" :pagination="false" > </a-table> </a-card> 暂无相关库存数据 <a-tag v-if="isOutbound" color="green" style="margin-left:8px;">已出库</a-tag> </template> <script> import { listBtInventory, delBtInventory, exportBtInventory } from '@/api/tt/Inventory' import AdvanceTable from '@/components/pt/table/AdvanceTable' import BtInventoryAddForm from '@/views/bt/btoutbound/modules/BtInventoryAddForm' import BtInventoryEditForm from '@/views/bt/btoutbound/modules/BtInventoryEditForm' export default { name: 'BtInventory', props: { isOutbound: { type: Boolean, default: false, // 默认不显示“已出库” }, contractId: { type: String, required: true }, title: { type: String, default: '子表' } },watch: { hasData: { immediate: true, handler(newVal) { if (!newVal && !this.isOutbound) { this.$emit('outbound-status', true); this.isOutbound = true; } } } }, components: { AdvanceTable, BtInventoryAddForm, BtInventoryEditForm }, data () { return { hasData:false, showAddModal: false, showEditModal: false, // 遮罩层 loading: true, // 选中数组 ids: [], // 选中的主键集合 selectedRowKeys: [], // 选中的数据集合 selectedRows: [], // 高级搜索 展开/关闭 advanced: false, // 非单个禁用 single: true, // 非多个禁用 multiple: true, // 总条数 total: 0, // label的百分比 labelCol: { span: 6 }, // 内容区域的百分比 wrapperCol: { span: 18 }, // 合同管理表格数据 btInventoryList: [], // 查询参数 queryParam: { pageNum: 1, pageSize: 1000, itemName: undefined, description: undefined, quantity: undefined, beginIndate: undefined }, columns: [ { title: '物资名称', dataIndex: 'itemName', ellipsis: true, align: 'left', width: '12.8%' }, { title: '规格型号', dataIndex: 'description', ellipsis: true, align: 'left', width: '12.8%' }, { title: '总库存', dataIndex: 'fulfilledQuantity', align: 'right', width: '12.8%' }, { title: '当前库存数量', dataIndex: 'quantity', align: 'right', width: '12.8%' }, { title: '入库日期', dataIndex: 'indate', align: 'center', width: '12.8%' }, // { // title: '操作', // dataIndex: 'operation', // align: 'center', // width: '10%', // scopedSlots: { customRender: 'operation' } // } ] } }, created () { this.getList() }, methods: { /** 查询合同管理列表 */ getList () { this.loading = true this.queryParam.contractId = this.contractId listBtInventory(this.queryParam).then(response => { this.btInventoryList = response.data.list this.total = response.data.total this.loading = false if(this.btInventoryList.length > 0){ this.hasData = true }else{ this.hasData = false } }) }, /** 搜索按钮操作 */ handleQuery () { this.queryParam.pageNum = 1 this.getList() }, /** 重置按钮操作 */ resetQuery () { this.queryParam = { pageNum: 1, pageSize: 1000, itemName: undefined, description: undefined, quantity: undefined, beginIndate: undefined } this.handleQuery() }, /** 翻页操作 */ onShowSizeChange (current, pageSize) { this.queryParam.pageSize = pageSize this.getList() }, /** 翻页操作 */ onSizeChange (current, size) { this.queryParam.pageNum = 1 this.queryParam.pageSize = size this.getList() }, /** 翻页操作 */ changeSize (current, pageSize) { this.queryParam.pageNum = current this.queryParam.pageSize = pageSize this.getList() }, /** 翻页操作 */ onSelectChange (selectedRowKeys, selectedRows) { this.selectedRowKeys = selectedRowKeys this.selectedRows = selectedRows this.ids = this.selectedRows.map(item => item.id) this.single = selectedRowKeys.length !== 1 this.multiple = !selectedRowKeys.length }, /** 查询折叠和展开操作 */ toggleAdvanced () { this.advanced = !this.advanced }, handleAdd () { this.showAddModal = true this.$nextTick(() => ( this.$refs.btInventoryAddForm.handleAdd() )) }, handleUpdate (record, ids) { this.showEditModal = true this.$nextTick(() => ( this.$refs.btInventoryEditForm.handleUpdate(record, ids) )) }, /** 删除按钮操作 */ handleDelete (row) { var that = this const btInventoryIds = row.id || this.ids this.$confirm({ title: '确认删除所选中数据?', onOk () { return delBtInventory(btInventoryIds) .then(() => { that.onSelectChange([], []) that.getList() that.$message.success( '删除成功', 3 ) }) }, onCancel () {} }) }, /** 导出按钮操作 */ handleExport () { var that = this this.$confirm({ title: '是否确认导出?', content: '此操作将导出当前条件下所有数据而非选中数据', onOk () { return exportBtInventory(that.queryParam) .then(response => { that.download(response.msg) that.$message.success( '导出成功', 3 ) }) }, onCancel () {} }) }, handleTableChange (pagination, filters, sorter) { if (sorter.field !== undefined && sorter.field !== null && sorter.field !== '') { this.queryParam.orderByColumn = 'a.' + sorter.field this.queryParam.isAsc = sorter.order } this.getList() } } } </script> <style scoped> .empty-container { padding: 20px; text-align: center; background: #fff; border-radius: 4px; } .action { font-size: 16px; cursor: pointer; margin-left: 8px; } .action:hover { color: #1890ff; } </style><template> <a-card :bordered="false" style="margin-bottom: 10px;"> <a-form :labelCol="labelCol" :wrapperCol="wrapperCol" ref="queryForm"> <a-row :gutter="32"> <a-col :span="6" > <a-form-item label="合同名称"> <a-input v-model="queryParam.contractName" placeholder="请输入合同名称" allow-clear @keyup.enter.native="handleQuery"/> </a-form-item> </a-col> <a-col :span="6" > <a-form-item label="合同正式编号"> <a-input v-model="queryParam.contractId" placeholder="请输入合同正式编号" allow-clear @keyup.enter.native="handleQuery"/> </a-form-item> </a-col> <a-col :span="6" > <a-form-item label="对方签约单位"> <a-input v-model="queryParam.counterpartContractUnit" placeholder="请输入对方签约单位" allow-clear @keyup.enter.native="handleQuery"/> </a-form-item> </a-col> <a-col> <a-button type="primary" @click="handleQuery"><a-icon type="search" />查询</a-button> <a-button style="margin-left: 8px" @click="resetQuery"><a-icon type="redo" />重置</a-button> {{ advanced ? '收起' : '展开' }} <a-icon :type="advanced ? 'up' : 'down'"/> </a-col> </a-row> </a-form> </a-card> <a-card :bordered="false" class="table-card"> <bt-contracts-detailed-add-form v-if="showAddModal" ref="contractmanageAddForm" :firstContractTypeOptions="firstContractTypeOptions" :secondContractTypeOptions="secondContractTypeOptions" :sealTypeOptions="sealTypeOptions" :signTypeOptions="signTypeOptions" @ok="getList" @close="showAddModal = false" /> <bt-contracts-detailed-edit-form v-if="showEditModal" ref="contractmanageEditForm" :firstContractTypeOptions="firstContractTypeOptions" :secondContractTypeOptions="secondContractTypeOptions" :sealTypeOptions="sealTypeOptions" :signTypeOptions="signTypeOptions" @ok="getList" @close="showEditModal = false" /> <BtLiveEidt v-if="showLiveModal" ref="liveEidt" :firstContractTypeOptions="firstContractTypeOptions" :secondContractTypeOptions="secondContractTypeOptions" :sealTypeOptions="sealTypeOptions" :signTypeOptions="signTypeOptions" @ok="getList" @close="showLiveModal = false" /> <outBound v-if="visible" ref="outBoundref" @close="onClose" /> <advance-table title="出库管理" :pagination="{ current: queryParam.pageNum, pageSize: queryParam.pageSize, total: total, showSizeChanger: true, showLessItems: true, showQuickJumper: true, showTotal: (total, range) => 第 ${range[0]}-${range[1]} 条,总计 ${total} 条, onChange: changeSize, onShowSizeChange: onShowSizeChange }" tableKey="base-contractmanage-index-table" @change="handleTableChange" rowKey="id" size="middle" @refresh="getList" :columns="columns" :data-source="contractmanageList" :loading="loading" :format-conditions="true" :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" > <bt-inventory-index ref="BtInventoryIndex" title="子表" :contractId="record.id" @outbound-status="(status) => handleOutboundStatus(record, status)" /> {{ firstContractTypeFormat(record) }} {{ secondContractTypeFormat(record) }} {{ sealTypeFormat(record) }} {{ signTypeFormat(record) }} <a-radio-group default-value="a" size="small"> <a-button type="primary" @click="handleUpdate1(record)" v-hasPermi="['bt:btOutbound:edit']" :size="size" ghost> <a-icon type="upload" />生活出库 </a-button> | <a-button type="primary" @click="handleUpdate(record)" v-hasPermi="['bt:btOutbound:edit']" :size="size" > <a-icon type="upload" />生产出库 </a-button> | <a-button type="dashed" @click="showDrawer(record)" v-hasPermi="['bt:btOutbound:list']" :size="size"> <a-icon type="table" />出库记录 </a-button> <a-button v-if="record.hasOutbound" type="danger" @click="resetOutboundStatus(record)" :size="size" > <a-icon type="undo" />重置为未出库 </a-button> </a-radio-group> <a-tag v-if="record.hasOutbound" color="green">已出库</a-tag> <a-tag v-else color="orange">未出库</a-tag> </advance-table> </a-card> </template> <script> import { listBtContractsDetailed, delBtContractsDetailed, exportBtContractsDetailed, getInitData } from '@/api/tt/ContractsDetailed' import AdvanceTable from '@/components/pt/table/AdvanceTable' import BtContractsDetailedAddForm from '@/views/bt/btoutbound/modules/BtContractsDetailedAddForm' import BtContractsDetailedEditForm from '@/views/bt/btoutbound/modules/BtContractsDetailedEditForm' import BtInventoryIndex from '@/views/bt/btoutbound/BtInventoryIndex' import outBound from '@/views/bt/btoutbound/outbound/index.vue' import BtLiveEidt from './modules/BtLiveEidt.vue' export default { name: 'BtoutBound', components: { AdvanceTable, BtInventoryIndex, outBound, BtContractsDetailedAddForm, BtContractsDetailedEditForm, BtLiveEidt }, data () { return { size:'small', visible: false, showAddModal: false, showEditModal: false, showLiveModal: false, // 遮罩层 loading: true, // 选中数组 ids: [], // 选中的主键集合 selectedRowKeys: [], // 选中的数据集合 selectedRows: [], // 高级搜索 展开/关闭 advanced: false, // 非单个禁用 single: true, // 非多个禁用 multiple: true, // 总条数 total: 0, // label的百分比 labelCol: { span: 6 }, // 内容区域的百分比 wrapperCol: { span: 18 }, // 合同管理表格数据 contractmanageList: [], // 一级合同类型字典 firstContractTypeOptions: [], // 二级合同类型字典 secondContractTypeOptions: [], // 用章类别字典 sealTypeOptions: [], // 签署类型字典 signTypeOptions: [], outBoundId: 0, // 查询参数 queryParam: { pageNum: 1, pageSize: 10, contractName: undefined, contractId: undefined, organizer: undefined, counterpartContractUnit: undefined }, columns: [ { title: '合同分类', dataIndex: 'contractClassification', ellipsis: true, align: 'left', width: '12.8%' }, { title: '合同名称', dataIndex: 'contractName', ellipsis: true, align: 'left', width: '12.8%' }, { title: '合同正式编号', dataIndex: 'contractId', ellipsis: true, align: 'left', width: '12.8%' }, { title: '采购方式', dataIndex: 'procurementMethod', ellipsis: true, align: 'left', width: '12.8%' }, { title: '对方签约单位', dataIndex: 'counterpartContractUnit', ellipsis: true, align: 'left', width: '12.8%' }, { title: '我方签订日期', dataIndex: 'ourSignDate', ellipsis: true, align: 'left', width: '12.8%' }, { title: '合同总金额', dataIndex: 'totalContractAmount', ellipsis: true, align: 'left', width: '12.8%' }, { title: '删除作废', dataIndex: 'deletedOrVoided', ellipsis: true, align: 'left', width: '12.8%' }, { title: '出库状态', dataIndex: 'hasOutbound', align: 'center', width: '10%', customRender: (text, record) => { return record.hasOutbound ? <a-tag color="green">已出库</a-tag> : <a-tag color="orange">未出库</a-tag>; } }, { title: '操作', dataIndex: 'operation', align: 'center', width: '60%', scopedSlots: { customRender: 'operation' } } ] } }, created () { this.getList() getInitData('contract_type_level1,contract_type_level2,sys_oper_type,sys_oper_type').then(response => { this.firstContractTypeOptions = response.data.contract_type_level1 this.secondContractTypeOptions = response.data.contract_type_level2 this.sealTypeOptions = response.data.sys_oper_type this.signTypeOptions = response.data.sys_oper_type }) }, methods: { resetOutboundStatus(record) { const index = this.contractmanageList.findIndex(item => item.id === record.id); if (index !== -1) { this.$set(this.contractmanageList[index], 'hasOutbound', false); // 更新前端状态 localStorage.removeItem(outbound_${record.id}); // 清除 localStorage 存储 this.$message.success("已重置为未出库"); } }, handleOutboundStatus(record, status) { const index = this.contractmanageList.findIndex(item => item.id === record.id); if (index !== -1 && status && !this.contractmanageList[index].hasOutbound) { this.$set(this.contractmanageList[index], 'hasOutbound', true); localStorage.setItem(outbound_${record.id}, 'true'); // 存储到 localStorage } }, onClose () { this.visible = false this.$emit('close') }, showDrawer(record) { this.visible = true const that = this that.$nextTick(() => ( that.$refs.outBoundref.handleList(record) )) }, // onClose() { // this.visible = false; // }, /** 查询合同管理列表 */ getList () { this.loading = true; listBtContractsDetailed(this.queryParam).then(response => { this.contractmanageList = response.data.list.map(item => { // 从 localStorage 读取是否已出库 const storedOutbound = localStorage.getItem(outbound_${item.id}); return { ...item, hasOutbound: storedOutbound === 'true' || false, }; }); this.total = response.data.total; this.loading = false; }); }, // 一级合同类型字典翻译 firstContractTypeFormat (row) { if (row.firstContractType) { return this.selectDictLabel(this.firstContractTypeOptions, row.firstContractType) } else { return '' } }, // 二级合同类型字典翻译 secondContractTypeFormat (row) { if (row.secondContractType) { return this.selectDictLabel(this.secondContractTypeOptions, row.secondContractType) } else { return '' } }, // 用章类别字典翻译 sealTypeFormat (row) { if (row.sealType) { return this.selectDictLabel(this.sealTypeOptions, row.sealType) } else { return '' } }, // 签署类型字典翻译 signTypeFormat (row) { if (row.signType) { return this.selectDictLabel(this.signTypeOptions, row.signType) } else { return '' } }, /** 搜索按钮操作 */ handleQuery () { this.queryParam.pageNum = 1 this.getList() }, /** 重置按钮操作 */ resetQuery () { this.queryParam = { pageNum: 1, pageSize: 10, contractName: undefined, contractId: undefined, organizer: undefined, counterpartContractUnit: undefined } this.handleQuery() }, /** 翻页操作 */ onShowSizeChange (current, pageSize) { this.queryParam.pageSize = pageSize this.getList() }, /** 翻页操作 */ onSizeChange (current, size) { this.queryParam.pageNum = 1 this.queryParam.pageSize = size this.getList() }, /** 翻页操作 */ changeSize (current, pageSize) { this.queryParam.pageNum = current this.queryParam.pageSize = pageSize this.getList() }, /** 翻页操作 */ onSelectChange (selectedRowKeys, selectedRows) { this.selectedRowKeys = selectedRowKeys this.selectedRows = selectedRows this.ids = this.selectedRows.map(item => item.id) this.single = selectedRowKeys.length !== 1 this.multiple = !selectedRowKeys.length }, /** 查询折叠和展开操作 */ toggleAdvanced () { this.advanced = !this.advanced }, handleAdd () { this.showAddModal = true this.$nextTick(() => ( this.$refs.contractmanageAddForm.handleAdd() )) }, //生活导出 handleUpdate1 (record, ids) { this.showLiveModal = true this.$nextTick(() => ( this.$refs.liveEidt.handleUpdate(record, ids) )) }, handleUpdate (record, ids) { this.showEditModal = true this.$nextTick(() => ( this.$refs.contractmanageEditForm.handleUpdate(record, ids) )) }, /** 删除按钮操作 */ handleDelete (row) { var that = this const contractmanageIds = row.id || this.ids this.$confirm({ title: '确认删除所选中数据?', onOk () { return delBtContractsDetailed(contractmanageIds) .then(() => { that.onSelectChange([], []) that.getList() that.$message.success( '删除成功', 3 ) }) }, onCancel () {} }) }, /** 导出按钮操作 */ handleExport () { var that = this this.$confirm({ title: '是否确认导出?', content: '此操作将导出当前条件下所有数据而非选中数据', onOk () { return exportBtContractsDetailed(that.queryParam) .then(response => { that.download(response.msg) that.$message.success( '导出成功', 3 ) }) }, onCancel () {} }) }, handleTableChange (pagination, filters, sorter) { if (sorter.field !== undefined && sorter.field !== null && sorter.field !== '') { this.queryParam.orderByColumn = 'a.' + sorter.field this.queryParam.isAsc = sorter.order } this.getList() } } } </script> 进行修改,我现在的未出库和已出库是存储在客户端,但现在要改成存储到数据库里,/** * 修改合同管理 */ @PreAuthorize("@ss.hasPermi('bt:btOutbound:edit')") @Log(title = "合同管理", businessType = BusinessType.UPDATE) @PutMapping public R edit(@RequestBody @Validated ContractsDetailed btContractsDetailed) { return R.status(btContractsDetailedService.save(btContractsDetailed)); }存储时调用这个接口里的 ecc_transfer varchar(50) DEFAULT NULL COMMENT '传输ECC', 这个字段,直接存储汉字

<template> <a-card :bordered="false"> <a-form layout="inline" @keyup.enter.native="searchQuery"> <a-row :gutter="24"> </a-row> </a-form> <a-button @click="handleAdd" type="primary" icon="plus">新增</a-button> <a-button type="primary" icon="download" @click="handleExportXls('流程实例关联表单')">导出</a-button> <a-upload name="file" :showUploadList="false" :multiple="false" :headers="tokenHeader" :action="importExcelUrl" @change="handleImportExcel"> <a-button type="primary" icon="import">导入</a-button> </a-upload> <j-super-query :fieldList="superFieldList" ref="superQueryModal" @handleSuperQuery="handleSuperQuery"></j-super-query> <a-dropdown v-if="selectedRowKeys.length > 0"> <a-menu slot="overlay"> <a-menu-item key="1" @click="batchDel"><a-icon type="delete"/>删除</a-menu-item> </a-menu> <a-button style="margin-left: 8px"> 批量操作 <a-icon type="down" /></a-button> </a-dropdown> 已选择 {{ selectedRowKeys.length }}清空 <a-table ref="table" size="middle" :scroll="{x:true}" bordered rowKey="id" :columns="columns" :dataSource="dataSource" :pagination="ipagination" :loading="loading" :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}" class="j-table-force-nowrap" @change="handleTableChange"> <template slot="htmlSlot" slot-scope="text"> </template> <template slot="imgSlot" slot-scope="text"> 无图片 </template> <template slot="fileSlot" slot-scope="text"> 无文件 <a-button v-else :ghost="true" type="primary" icon="download" size="small" @click="downloadFile(text)"> 下载 </a-button> </template> 编辑 <a-divider type="vertical" /> <a-dropdown> 更多 <a-icon type="down" /> <a-menu slot="overlay"> <a-menu-item> 详情 </a-menu-item> <a-menu-item> <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)"> 删除 </a-popconfirm> </a-menu-item> </a-menu> </a-dropdown> </a-table> <sys-deploy-form-modal ref="modalForm" @ok="modalFormOk"></sys-deploy-form-modal> </a-card> </template> <script> import '@/assets/less/TableExpand.less' import { mixinDevice } from '@/utils/mixin' import { JeecgListMixin } from '@/mixins/JeecgListMixin' import SysDeployFormModal from './modules/SysDeployFormModal' export default { name: 'SysDeployFormList', mixins:[JeecgListMixin, mixinDevice], components: { SysDeployFormModal }, data () { return { description: '流程实例关联表单管理页面', // 表头 columns: [ { title: '#', dataIndex: '', key:'rowIndex', width:60, align:"center", customRender:function (t,r,index) { return parseInt(index)+1; } }, { title:'表单主键', align:"center", dataIndex: 'formId' }, { title:'流程实例主键', align:"center", dataIndex: 'deployId' }, { title:'创建人', align:"center", dataIndex: 'createBy' }, { title:'创建日期', align:"center", dataIndex: 'createTime' }, { title: '操作', dataIndex: 'action', align:"center", fixed:"right", width:147, scopedSlots: { customRender: 'action' } } ], url: { list: "/flowable/sysDeployForm/list", delete: "/flowable/sysDeployForm/delete", deleteBatch: "/flowable/sysDeployForm/deleteBatch", exportXlsUrl: "/flowable/sysDeployForm/exportXls", importExcelUrl: "flowable/sysDeployForm/importExcel", }, dictOptions:{}, superFieldList:[], } }, created() { this.getSuperFieldList(); }, computed: { importExcelUrl: function(){ return ${window._CONFIG['domianURL']}/${this.url.importExcelUrl}; }, }, methods: { /** 挂载流程定义 */ submitDefinitionForm(row) { this.customformParam.formId = row.id; updateCustomForm(this.customformParam).then(res => { this.$message.success(res.message); this.definitionOpen = false; this.getList(); }) }, handleCurrentChange(data) { if (data) { this.currentRow = JSON.parse(data.formContent); } }, getCategoryName(category) { let find = this.categorys.find(o => o.id == category); if (find) { return find.name } return '' }, initDictConfig(){ }, getSuperFieldList(){ let fieldList=[]; fieldList.push({type:'string',value:'formId',text:'表单主键'}) fieldList.push({type:'string',value:'deployId',text:'流程实例主键'}) fieldList.push({type:'string',value:'createBy',text:'创建人'}) fieldList.push({type:'datetime',value:'createTime',text:'创建日期'}) this.superFieldList = fieldList } } } </script> <style scoped> @import '~@assets/less/common.less'; </style>上传文件为vue文件,vue版本为vue2版本,脚本为JavaScript编写;现在将该vue文件进行转化:转化vue版本为vue3版本,脚本为TypeScript编写

<template> 查询条件 <a-form-model :model="searchForm" layout="inline" ref="searchForm" class="searchForm"> <a-form-model-item label="题库名称" prop="title"> <a-input v-model="searchForm.title" placeholder="请输入题库名称" /> </a-form-model-item> <a-form-model-item class="searchButton"> <a-button type="primary" v-if="QX.read" @click="getSearch">查询</a-button> <a-button type="default" v-if="QX.read" @click="restSearch('searchForm')">重置</a-button> </a-form-model-item> </a-form-model> <a-button type="primary" @click="addBank" icon="plus" v-if="QX.add">新增</a-button> <a-table :columns="columns" :data-source="dataList" :pagination="false" :loading="loading" rowKey="questionBankId" :scroll="{ y: this.$getViewportSize().height - 300 }" > <a-icon class="iconBtn" type="edit" />编辑 <a-divider v-if="QX.edit && QX.delete" type="vertical" /> <a-popconfirm title="确认是否删除?" ok-text="是" cancel-text="否" @confirm="removeBank(record)" > <a-icon class="iconBtn" type="delete" />删除 </a-popconfirm> </a-table> <template slot="action" slot-scope="text, record, index"> 删除 </template> <a-pagination show-size-changer :total="totalPage" :current="pageIndex" :pageSize="pageSize" @showSizeChange="onShowSizeChange" @change="onChangePage" style="float: right; margin-top: 15px" /> <a-drawer :closable="true" :title="title" width="auto" :visible="visible" @close="visible = !visible" > <a-spin class="submitLoading" :spinning="submitLoading"> <a-form-model :model="form" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rules" ref="form" class="lay-drawer-form"> <a-row> <a-col :span="12"> <a-form-model-item label="题库名称" prop="title"> <a-input v-model="form.title" :maxLength="25" placeholder="请输入题库名称" style="width: 380px" /> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item label="关联岗位" prop="positionId"> <a-select v-model="form.positionId" style="width: 380px" placeholder="请选择岗位"> <a-select-option v-for="(label, value) in positionDict" :key="value" :value="value"> {{ label }} </a-select-option> </a-select> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item label="参与PK" prop="participateInPk"> <a-switch v-model="form.participateInPk" :checkedValue="1" :unCheckedValue="0" /> </a-form-model-item> </a-col> </a-row> </a-form-model> <a-button type="primary" @click="downloadTemplate">下载模板</a-button> <a-upload name="file" :showUploadList="false" :beforeUpload="beforeUpload" accept=".xlsx,.xls" :disabled="!importEnabled" > <a-button :disabled="!importEnabled"><a-icon type="upload" /> 导入题目</a-button> </a-upload> <a-button type="dashed" @click="addTopicVisible = true" :disabled="!addEnabled" > <a-icon type="plus" /> 添加题目 </a-button> {{ index + 1 }}. {{ topic.content }} <label v-for="option in getOptions(topic)" :key="option.key"> <input type="radio" :name="'topic' + topic.topicId" :value="option.key" :checked="topic.correctAnswer === option.key" /> {{ option.key }}. {{ option.value }} </label> <label> <input type="radio" :name="'topic' + topic.topicId" value="正确" :checked="topic.correctAnswer === '正确'" /> 正确 </label> <label> <input type="radio" :name="'topic' + topic.topicId" value="错误" :checked="topic.correctAnswer === '错误'" /> 错误 </label> <a-icon type="delete" /> 删除 <a-pagination v-model="topicPageNum" :pageSize="topicPageSize" :total="totalTopicCount" @change="handleTopicPageChange" style="text-align: right;" /> <a-button type="default" @click="visible = !visible" > 取消 </a-button> <a-button type="primary" @click="submitForm" > 确认 </a-button> </a-spin> </a-drawer> <a-drawer title="新增题目" :visible="addTopicVisible" @close="addTopicVisible = false" width="500" > <a-form-model :model="addTopicForm" layout="vertical" :rules="rulesForAddTopic" ref="addTopicFormRef"> <a-form-model-item label="题目类型" prop="topicType"> <a-select v-model="addTopicForm.topicType" style="width: 100%"> <a-select-option :value="1">选择题</a-select-option> <a-select-option :value="2">判断题</a-select-option> </a-select> </a-form-model-item> <a-form-model-item label="题目内容" prop="content"> <a-input v-model="addTopicForm.content" placeholder="请输入题目内容" /> </a-form-model-item> <a-form-model-item label="选项A" prop="optionA"> <a-input v-model="addTopicForm.optionA" placeholder="请输入选项A内容" /> </a-form-model-item> <a-form-model-item label="选项B" prop="optionB"> <a-input v-model="addTopicForm.optionB" placeholder="请输入选项B内容" /> </a-form-model-item> <a-form-model-item label="选项C" prop="optionC"> <a-input v-model="addTopicForm.optionC" placeholder="请输入选项C内容" /> </a-form-model-item> <a-form-model-item label="选项D" prop="optionD"> <a-input v-model="addTopicForm.optionD" placeholder="请输入选项D内容" /> </a-form-model-item> <a-form-model-item label="正确答案" prop="correctAnswer"> <a-select v-model="addTopicForm.correctAnswer" style="width: 100%"> <a-select-option value="A">A</a-select-option> <a-select-option value="B">B</a-select-option> <a-select-option value="C">C</a-select-option> <a-select-option value="D">D</a-select-option> </a-select> </a-form-model-item> <a-form-model-item label="正确答案" prop="correctAnswer"> <a-select v-model="addTopicForm.correctAnswer" style="width: 100%"> <a-select-option value="正确">正确</a-select-option> <a-select-option value="错误">错误</a-select-option> </a-select> </a-form-model-item> <a-button @click="addTopicVisible = false">取消</a-button> <a-button type="primary" @click="saveNewTopic">保存</a-button> </a-form-model> </a-drawer> </template> <script> import { req, fileDownload } from '../../../api/axiosFun'; import preventBack from 'vue-prevent-browser-back'; export default { name: 'Bank', mixins: [preventBack], data() { return { QX: {}, topicQX: {}, topicList: [], totalTopicCount: 0, // 题目总数 topicPageNum: 1, // 当前页码 topicPageSize: 10, // 每页数量 addTopicVisible: false, addTopicForm: { content: '', // 题目内容 topicType: 1, // 题目类型:1=选择题,2=判断题 optionA: '', optionB: '', optionC: '', optionD: '', correctAnswer: '', }, disabled: false, checkedKeys: [], selectAuth: [], treeData: [], positionDict: {}, title: '', labelCol: { span: 4 }, wrapperCol: { span: 20 }, tableHeight: 0, expanded: false, // 筛选条件是否展开 form: { questionBankId: 0, bankCode: '', title: '', positionId: '', participateInPk: true, }, isEdit: false, // 是否是编辑状态 isAdd: false, // 是否是新增状态 importEnabled: false, // 导入题目按钮是否可用 - 默认为不可用 addEnabled: false, // 添加题目按钮是否可用 - 默认为不可用 rules: { positionId: [ { required: true, message: '请选择岗位', trigger: 'blur' }, ], title: [ { required: true, message: '请输入题库名称', trigger: 'blur' }, ], }, rulesForAddTopic: { content: [ { required: true, message: '请输入题目内容', trigger: ['blur', 'change'] }, ], topicType: [ { required: true, message: '请选择题目类型', trigger: 'change' }, ], optionA: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项A', trigger: ['blur', 'change'], }, ], optionB: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项B', trigger: ['blur', 'change'], }, ], optionC: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项C', trigger: ['blur', 'change'], }, ], optionD: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项D', trigger: ['blur', 'change'], }, ], correctAnswer: [ { required: true, message: '请选择正确答案', trigger: 'change' }, ], }, searchForm: { title: '', }, visible: false, dataList: [], columns, loading: false, submitLoading: false, pageIndex: 1, pageSize: 10, totalPage: 0, ops: { vuescroll: {}, scrollPanel: {}, rail: { keepShow: true, }, bar: { hoverStyle: true, onlyShowBarOnScroll: false, // 是否只有滚动的时候才显示滚动条 background: '#F5F5F5', // 滚动条颜色 opacity: 1, // 滚动条透明度 'overflow-x': 'hidden', }, }, }; }, watch: { 'addTopicForm.topicType': function (newVal) { // 当题目类型变化时,触发相关字段的验证 this.$nextTick(() => { if (this.$refs.addTopicFormRef) { // 验证选项字段 if (newVal === 1) { this.$refs.addTopicFormRef.validateFields(['optionA', 'optionB', 'optionC', 'optionD']); } else { // 清除非必填字段的验证状态 this.$refs.addTopicFormRef.clearValidate(['optionA', 'optionB', 'optionC', 'optionD']); } } }); }, visible(newVal, oldVal) { if (!newVal) { this.restForm('form'); this.form.questionBankId = 0; this.checkedKeys = []; this.selectAuth = []; this.treeData = []; // 重置状态标志 this.isEdit = false; this.isAdd = false; } else { // 当抽屉打开时强制更新按钮状态 this.$nextTick(() => { this.$forceUpdate(); }); } }, }, mounted() { this.actionTitle = '操作'; this.getDict('position').then(res => { const dictMap = {}; res.data.forEach(item => { dictMap[item.dicValue] = item.dicDisplayName; }); this.positionDict = dictMap; }); this.getBankList(); }, methods: { // 获取字典数据方法 getDict(type) { return req('get', /dict/getDictItemByNo, { dicNo: type }) .then((res) => { if (res.result === 'success') { return res; } throw new Error(res.message || '获取字典数据失败'); }) .catch((error) => { console.error(获取${type}字典失败:, error); throw error; }); }, /* 新增题库 */ addBank() { this.visible = true; this.disabled = false; this.title = '新增题库'; // 获取功能权限树 req('get', '/bank/getTree', {}).then((res) => { this.treeData = res.data; }); // 初始化题目列表为空 this.topicList = []; this.totalTopicCount = 0; this.topicPageNum = 1; this.isEdit = false; // 不是编辑状态 this.isAdd = true; // 设置为新增状态 // 在新增状态下禁用导入和添加功能 this.importEnabled = false; this.addEnabled = false; // 强制更新按钮状态 this.$nextTick(() => { this.$forceUpdate(); }); }, // 查询 getSearch() { this.pageIndex = 1; this.getBankList(); }, /* 重置查询 */ restSearch(form) { this.restForm(form); this.pageIndex = 1; this.getBankList(); }, /* 删除题库 */ removeBank(record) { this.loading = true; req('post', '/bank/removeBQuestionBank', { questionBankId: record.questionBankId, }).then((res) => { this.loading = false; if (res.result === 'success') { this.$message.success(res.message); this.getBankList(); } }); }, downloadTemplate() { fileDownload('get', '/topic/downloadTemplate', null).then((res) => { const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = '题库模板.xlsx'; link.click(); window.URL.revokeObjectURL(link.href); }).catch(() => { this.$message.error('下载失败'); }); }, beforeUpload(file) { // 显示加载状态 this.loading = true; const formData = new FormData(); formData.append('file', file); formData.append('questionBankId', this.form.questionBankId); // 添加 questionBankId 字段 req('post', '/topic/import', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }) .then((res) => { // 隐藏加载状态 this.loading = false; if (res.result === 'success') { // 确保在DOM更新后再刷新题目列表 this.$nextTick(() => { // 刷新题目列表 this.getTopicList(this.form.questionBankId, this.topicPageNum, this.topicPageSize) .then(() => { this.$message.success('导入成功'); }) .catch(() => { this.$message.error('刷新题目列表失败'); }); }); } else { this.$message.error(res.message || '导入失败'); } }) .catch((error) => { // 隐藏加载状态 this.loading = false; this.$message.error('导入失败,请重试'); console.error('导入题目出错:', error); }); return false; // 阻止默认上传行为 }, // 获取题库下的题目列表(返回Promise以便链式调用) getTopicList(questionBankId, pageNum = 1, pageSize = 10) { return new Promise((resolve, reject) => { req('post', '/topic/list', { questionBankId, page: pageNum, rows: pageSize, }).then((res) => { if (res.result === 'success') { this.topicQX = res.QX; if (!this.topicQX.edit && !this.topicQX.delete) { this.hideAction(); } else { this.topicList = res.data.map((item, index) => ({ index: (pageNum - 1) * pageSize + index + 1, content: item.content, optionA: item.optionA, optionB: item.optionB, optionC: item.optionC, optionD: item.optionD, topicId: item.topicId, correctAnswer: item.correctAnswer, topicType: item.topicType, })); } // 更新总条数用于分页组件 this.totalTopicCount = res.page.totalResult; resolve(res); } else { this.$message.error(res.message || '获取题目列表失败'); reject(res); } }).catch((error) => { this.$message.error('获取题目列表失败'); reject(error); }); }); }, handleTopicPageChange(page) { this.topicPageNum = page; this.getTopicList(this.form.questionBankId, page, this.topicPageSize); }, // 获取选择题的选项 getOptions(topic) { if (topic.topicType === 1) { // 选择题:返回 A/B/C/D 选项 const options = []; if (topic.optionA) options.push({ key: 'A', value: topic.optionA }); if (topic.optionB) options.push({ key: 'B', value: topic.optionB }); if (topic.optionC) options.push({ key: 'C', value: topic.optionC }); if (topic.optionD) options.push({ key: 'D', value: topic.optionD }); return options; } else if (topic.topicType === 2) { // 判断题:直接返回 正确/错误 return [ { key: '正确', value: '正确' }, { key: '错误', value: '错误' }, ]; } return []; }, checkAnswer(topic, selectedAnswer) { // 仅记录用户选择的答案,不进行是否正确的判断 this.$set(topic, 'userAnswer', selectedAnswer); }, resetAddTopicForm() { this.addTopicForm = { content: '', topicType: 1, optionA: '', optionB: '', optionC: '', optionD: '', correctAnswer: '', }; }, saveNewTopic() { this.$refs.addTopicFormRef.validate((valid, fields) => { if (!valid) { console.log('表单验证失败:', fields); // 找出第一个错误字段并聚焦 const firstErrorField = Object.keys(fields).find(key => fields[key]); if (firstErrorField && this.$refs.addTopicFormRef) { const formItem = this.$refs.addTopicFormRef.$children.find( child => child.prop === firstErrorField, ); if (formItem && formItem.$el) { const input = formItem.$el.querySelector('input, select, textarea'); if (input) input.focus(); } } return; } // 验证通过,处理保存逻辑 const newTopic = { ...this.addTopicForm }; // 发送请求保存题目 req('post', '/topic/add', { questionBankId: this.form.questionBankId, optionA: newTopic.optionA, optionB: newTopic.optionB, optionC: newTopic.optionC, optionD: newTopic.optionD, correctAnswer: newTopic.correctAnswer, content: newTopic.content, topicType: newTopic.topicType, }).then((res) => { if (res.result === 'success') { this.$message.success('题目添加成功'); this.addTopicVisible = false; this.getTopicList(this.form.questionBankId); // 刷新题目列表 } else { this.$message.error(res.message || '保存失败'); } this.resetAddTopicForm(); }).catch((err) => { this.$message.error('网络异常,请重试'); }); }); }, /* 删除题库下的题目 */ removeQuestion(topicId) { this.$confirm({ title: '确认删除该题目?', content: '删除后将无法恢复', okText: '是', cancelText: '否', onOk: () => { req('post', '/topic/removeBTopic', { topicId, questionBankId: this.form.questionBankId, }).then((res) => { if (res.result === 'success') { this.$message.success(res.message); // 刷新题目列表 this.getTopicList(this.form.questionBankId, this.topicPageNum, this.topicPageSize); } else { this.$message.error(res.message || '删除失败'); } }); }, }); }, editAuth(record) { this.loading = true; req('post', '/bank/getBQuestionBank', { questionBankId: record.questionBankId, }).then((res) => { this.loading = false; if (res.result === 'success') { this.visible = true; this.disabled = true; this.title = '修改题库'; this.isEdit = true; // 设置为编辑状态 this.isAdd = false; // 不是新增状态 // 在编辑状态下启用导入和添加功能 this.importEnabled = true; this.addEnabled = true; // 强制更新按钮状态 this.$nextTick(() => { this.$forceUpdate(); }); const bank = res.data; this.$nextTick(() => { this.form.questionBankId = bank.questionBankId; this.form.title = bank.title; this.form.participateInPk = Boolean(bank.participateInPk); this.form.positionId = bank.positionId; this.treeData = bank.treeData; this.checkedKeys = bank.auths; // 获取题目列表 this.topicPageNum = 1; this.getTopicList(bank.questionBankId, this.topicPageNum, this.topicPageSize); }); } }); }, /* 保存or修改题库信息 */ submitForm() { this.$refs.form.validate((valid) => { if (valid) { this.form.participateInPk = this.form.participateInPk ? 1 : 0; const url = this.form.questionBankId ? 'edit' : 'add'; const selectAuth = this.selectAuth; this.form.selectAuth = JSON.stringify(selectAuth); this.submitLoading = true; req('post', /bank/${url}, this.form).then((res) => { if (res.result === 'success') { this.visible = false; this.getBankList(); // 如果是新增题库且成功,获取题目列表 if (!this.form.questionBankId && res.data && res.data.questionBankId) { this.form.questionBankId = res.data.questionBankId; this.topicPageNum = 1; this.getTopicList(res.data.questionBankId, this.topicPageNum, this.topicPageSize); // 新增成功后启用导入和添加功能 this.importEnabled = true; this.addEnabled = true; } this.$message.success(res.message); // 重置新增/编辑状态 this.isEdit = false; this.isAdd = false; } this.submitLoading = false; }); } }); }, /* 重置表单 */ restForm(form) { this.$refs[form].resetFields(); }, /* 改变页数事件 */ onChangePage(page, pageSize) { this.pageIndex = page; this.getBankList(); }, /* 改变每页显示条数 */ onShowSizeChange(current, pageSize) { this.pageIndex = 1; this.pageSize = pageSize; this.getBankList(); }, /* 题库信息列表 */ getBankList() { this.loading = true; this.searchForm.page = this.pageIndex; this.searchForm.rows = this.pageSize; req('post', '/bank/list', this.searchForm) .then((res) => { if (res.result === 'success') { this.dataList = res.data; this.QX = res.QX; // 无权限隐藏操作列 if (!this.QX.edit && !this.QX.delete) { this.hideAction(); } else if (columns[columns.length - 1].title != '操作') { columns.push(actionShow); } this.totalPage = res.page.totalResult; } this.loading = false; }).catch((error) => { this.loading = false; }); }, /* 无所有行操作权限时,隐藏操作栏 */ hideAction() { if (columns[columns.length - 1].title == '操作') { columns.splice(columns.length - 1, 1); } }, /* 校验代号类型 */ validCode(value) { if (value.length > 20) { value = value.slice(0, 20); } for (let i = value.length - 1; i >= 0; i--) { const unicode = value.charCodeAt(i); if (unicode > 65280 && unicode < 65375) { value = value.substr(0, i); } } this.value = value; }, }, }; const actionShow = { title: '操作', width: '200px', hide: true, dataIndex: 'action', key: 'action', align: 'center', scopedSlots: { customRender: 'action' }, }; const columns = [ { title: '序号', width: '50px', align: 'center', customRender: (text, row, index) => index + 1, }, { title: '题库名称', align: 'center', dataIndex: 'title', key: 'title', width: '120px', }, { title: '涉及岗位', align: 'center', dataIndex: 'positionName', key: 'positionName', width: '110px', }, { title: '题目数量', dataIndex: 'topicCount', key: 'topicCount', align: 'center', width: '180px', }, { title: '操作', width: '150px', hide: true, dataIndex: 'action', key: 'action', align: 'center', scopedSlots: { customRender: 'action' }, }, ]; </script> <style scoped> .topic-list-container { max-height: calc(100vh - 400px); /* 根据实际布局调整最大高度 */ overflow-y: auto; } .pagination-wrapper { position: sticky; bottom: 0; left: 0; right: 0; background: #fff; padding: 10px; z-index: 1; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); margin-top: 15px; } .topic-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .topic-item { border: 1px solid #e8e8e8; padding: 15px; border-radius: 4px; position: relative; /* 为绝对定位的子元素提供定位上下文 */ } .topic-content { font-size: 16px; margin-bottom: 10px; } .options label { display: block; margin: 5px 0; font-size: 14px; } .options input[type="radio"] { margin-right: 5px; } /* 按钮禁用状态样式优化 */ .ant-upload.ant-upload-disabled .ant-upload-list, .ant-btn[disabled] { cursor: not-allowed; opacity: 0.5; } .topic-delete { position: absolute; top: 10px; right: 10px; } </style> <style scoped> @import "../../../assets/css/maincss.css"; </style> 在编辑页面中选择某个题目的选项后,再次打开该题时,默认显示的是上次选择的选项而不是正确答案。修改此问题

<template> <SearchForm v-show="isShowSearch" :search="_search" :reset="_reset" :columns="searchColumns" :search-param="searchParam" :search-col="searchCol" /> <slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" /> <slot name="toolButton"> <el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="refreshData" /> <el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle @click="openColSetting" /> <el-button v-if="showToolButton('search') && searchColumns?.length" :icon="Search" circle @click="isShowSearch = !isShowSearch" /> </slot> <slot name="tableTips"></slot> <el-table ref="tableRef" v-bind="$attrs" :data="processTableData" :border="border" :row-key="rowKey" @selection-change="selectionChange" > <slot /> <template v-for="item in tableColumns" :key="item"> <el-table-column v-if="item.type && columnTypes.includes(item.type)" v-bind="item" :align="item.align ?? 'center'" :reserve-selection="item.type == 'selection'" :selectable="item.isSelectable"> <template #default="scope"> <template v-if="item.type == 'expand'"> <component :is="item.render" v-bind="scope" v-if="item.render" /> <slot v-else :name="item.type" v-bind="scope" /> </template> <el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]"> </el-radio> <el-tag v-if="item.type == 'sort'" class="move"> <el-icon> <DCaret /></el-icon> </el-tag> </template> </el-table-column> <template v-for="slot in Object.keys($slots)" #[slot]="scope"> <slot :name="slot" v-bind="scope" /> </template> </template> <template #append> <slot name="append" /> </template> <template #empty> <slot name="empty"> notData 暂无数据 </slot> </template> </el-table> <slot name="pagination"> </slot> <ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" /> </template> <script setup lang="ts" name="ProTable"> import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue"; import { ElTable } from "element-plus"; import { useTable } from "@/hooks/useTable"; import { useSelection } from "@/hooks/useSelection"; import { BreakPoint } from "@/components/Grid/interface"; import { ColumnProps, TypeProps } from "@/components/ProTable/interface"; import { Refresh, Operation, Search } from "@element-plus/icons-vue"; import { handleProp } from "@/utils"; import SearchForm from "@/components/SearchForm/index.vue"; import Pagination from "./components/Pagination.vue"; import ColSetting from "./components/ColSetting.vue"; import TableColumn from "./components/TableColumn.vue"; import Sortable from "sortablejs"; export interface ProTableProps { columns: ColumnProps[]; // 列配置项 ==> 必传 data?: any[]; // 静态 table data 数据,若存在则不会使用 requestApi 返回的 data ==> 非必传 requestApi?: (params: any) => Promise<any>; // 请求表格数据的 api ==> 非必传 requestAuto?: boolean; // 是否自动执行请求 api ==> 非必传(默认为true) requestError?: (params: any) => void; // 表格 api 请求错误监听 ==> 非必传 dataCallback?: (data: any) => any; // 返回数据的回调函数,可以对数据进行处理 ==> 非必传 title?: string; // 表格标题 ==> 非必传 pagination?: boolean; // 是否需要分页组件 ==> 非必传(默认为true) initParam?: any; // 初始化请求参数 ==> 非必传(默认为{}) border?: boolean; // 是否带有纵向边框 ==> 非必传(默认为true) toolButton?: ("refresh" | "setting" | "search")[] | boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true) rowKey?: string; // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id) searchCol?: number | Record<BreakPoint, number>; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 } tableTipsFlag?: boolean; // 表格上方的提示信息 ==> 非必传 (默认为false) } // 接受父组件参数,配置默认值 const props = withDefaults(defineProps(), { columns: () => [], requestAuto: true, pagination: true, initParam: {}, border: true, toolButton: true, rowKey: "id", searchCol: () => ({ xs: 1, sm: 3, md: 3, lg: 4, xl: 5 }), tableTipsFlag: false }); // table 实例 const tableRef = ref<InstanceType<typeof ElTable>>(); // column 列类型 const columnTypes: TypeProps[] = ["selection", "radio", "index", "expand", "sort"]; // 是否显示搜索模块 const isShowSearch = ref(true); // 控制 ToolButton 显示 const showToolButton = (key: "refresh" | "setting" | "search") => { return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton; }; // 单选值 const radio = ref(""); // 表格多选 Hooks const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey); // 表格操作 Hooks const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } = useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError, tableRef); // 清空选中数据列表 const clearSelection = () => tableRef.value!.clearSelection(); // 初始化表格数据 && 拖拽排序 onMounted(() => { dragSort(); props.requestAuto && getTableList(); props.data && (pageable.value.total = props.data.length); }); // 处理表格数据 const processTableData = computed(() => { if (!props.data) return tableData.value; if (!props.pagination) return props.data; return props.data.slice( (pageable.value.pageNum - 1) * pageable.value.pageSize, pageable.value.pageSize * pageable.value.pageNum ); }); // 监听页面 initParam 改化,重新获取表格数据 watch(() => props.initParam, () => { // 将初始化initParam参数赋值给表单 searchParam.value = { ...searchParam.value, ...props.initParam, }; getTableList() }, { deep: true }); // 接收 columns 并设置为响应式 const tableColumns = reactive<ColumnProps[]>(props.columns); // 扁平化 columns const flatColumns = computed(() => flatColumnsFunc(tableColumns)); // 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择) const enumMap = ref(new Map<string, { [key: string]: any }[]>()); const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => { if (!enumValue) return; // 如果当前 enumMap 存在相同的值 return if (enumMap.value.has(prop!) && (typeof enumValue === "function" || enumMap.value.get(prop!) === enumValue)) return; // 当前 enum 为静态数据,则直接存储到 enumMap if (typeof enumValue !== "function") return enumMap.value.set(prop!, unref(enumValue!)); // 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储 enumMap.value.set(prop!, []); // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap const { data } = await enumValue(); enumMap.value.set(prop!, data); }; const refreshData = () => { pageable.value.pageNum = 1; getTableList(); }; // 注入 enumMap provide("enumMap", enumMap); // 扁平化 columns 的方法 const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => { columns.forEach(async col => { if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children)); flatArr.push(col); // column 添加默认 isShow && isFilterEnum 属性值 col.isShow = col.isShow ?? true; col.isFilterEnum = col.isFilterEnum ?? true; // 设置 enumMap await setEnumMap(col); }); return flatArr.filter(item => !item._children?.length); }; // 过滤需要搜索的配置项 && 排序 const searchColumns = computed(() => { return flatColumns.value ?.filter(item => item.search?.el || item.search?.render) .sort((a, b) => a.search!.order! - b.search!.order!); }); // 设置 搜索表单默认排序 && 搜索表单项的默认值 searchColumns.value?.forEach((column, index) => { column.search!.order = column.search?.order ?? index + 2; const key = column.search?.key ?? handleProp(column.prop!); const defaultValue = column.search?.defaultValue; if (defaultValue !== undefined && defaultValue !== null) { searchInitParam.value[key] = defaultValue; searchParam.value[key] = defaultValue; } }); // 列设置 ==> 需要过滤掉不需要设置的列 const colRef = ref(); const colSetting = tableColumns!.filter(item => { const { type, prop, isShow } = item; return !columnTypes.includes(type!) && prop !== "operation" && isShow; }); const openColSetting = () => colRef.value.openColSetting(); // 定义 emit 事件 const emit = defineEmits<{ search: []; reset: []; dargSort: [{ newIndex?: number; oldIndex?: number }]; }>(); const _search = () => { search(); emit("search"); }; const _reset = () => { reset(); emit("reset"); }; // 拖拽排序 const dragSort = () => { const tbody = document.querySelector(".el-table__body-wrapper tbody") as HTMLElement; Sortable.create(tbody, { handle: ".move", animation: 300, onEnd({ newIndex, oldIndex }) { const [removedItem] = processTableData.value.splice(oldIndex!, 1); processTableData.value.splice(newIndex!, 0, removedItem); emit("dargSort", { newIndex, oldIndex }); } }); }; // 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去) defineExpose({ element: tableRef, tableData: processTableData, radio, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange, clearSelection, enumMap, isSelected, selectedList, selectedListIds, refreshData: getTableList // 暴露刷新方法 }); </script> import { ref, computed } from "vue"; /** * @description 表格多选数据操作 * @param {String} rowKey 当表格可以多选时,所指定的 id * */ export const useSelection = (rowKey: string = "id") => { const isSelected = ref<boolean>(false); const selectedList = ref<{ [key: string]: any }[]>([]); // 当前选中的所有 ids 数组 const selectedListIds = computed((): string[] => { let ids: string[] = []; selectedList.value.forEach(item => ids.push(item[rowKey])); return ids; }); /** * @description 多选操作 * @param {Array} rowArr 当前选择的所有数据 * @return void */ const selectionChange = (rowArr: { [key: string]: any }[]) => { rowArr.length ? (isSelected.value = true) : (isSelected.value = false); selectedList.value = rowArr; }; return { isSelected, selectedList, selectedListIds, selectionChange }; }; <template> <template #tableHeader="scope"> <el-button type="primary" v-if="hasBtnPermission('asset:inventory:save')" @click="addNewData('新增资产','add',{})">新增资产</el-button> <el-button type="primary" v-if="hasBtnPermission('asset:inventory:update')" @click="batcEdit(scope.selectedListIds)">批量编辑</el-button> <el-button type="primary" v-if="hasBtnPermission('asset:inventory:delete')" @click="batchDelete(scope.selectedListIds)">批量删除</el-button> <el-dropdown style="margin-left: 10px" v-if="hasBtnPermission('asset:inventory:downloadData')" @command="batchExport"> <el-button type="primary"> 批量导出 </el-button> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="1">导出所选数据</el-dropdown-item> <el-dropdown-item command="2">导出全部</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> <el-button type="primary" @click="openTagPrint(scope.selectedListIds)" v-if="hasBtnPermission('asset:inventory:printMark')" style="margin-left: 10px">打印资产标签</el-button> </template> <el-table-column type="selection" width="55"></el-table-column> <el-table-column prop="imageUrl" label="图片" minWidth="100px" class-name="is-center"> <template #default="{ row }"> 0"> <viewer :images="row.imageUrlList"> </viewer> </template> </el-table-column> <template #expand="scope"> {{ scope.row }} </template> <template #operation="scope" v-if="hasBtnPermission('asset:inventory:update')" > <el-button type="primary" link @click="editData('编辑资产','edit', scope.row)">编辑</el-button> </template> <el-dialog v-model="dialogFormVisible" title="选择资产类型" width="450" draggable > <el-form ref="ruleFormRef" :model="form" :rules="rules" label-width="93px"> <el-form-item label="类型" prop="type"> <el-select v-model="form.type" placeholder="请选择"> <el-option v-for="item in assetType" :key="item.value" :label="item.label" :value="item.value"></el-option> </el-select> </el-form-item> <el-form-item label="非标准资产" v-if="form.type === 2" prop="nonStandardAssetsId"> <el-select v-model="form.nonStandardAssetsId" placeholder="请选择"> <el-option v-for="item in nonstandardData" :key="item.id" :label="item.name" :value="item.id" :disabled="item.status == 0" ></el-option> </el-select> </el-form-item> </el-form> <template #footer> <el-button @click="closeDialog">取消</el-button> <el-button type="primary" @click="nextTips('新增资产','add',{})">下一步</el-button> </template> </el-dialog> <addAsset ref="addAssetRef" @previous-step="handlePreviousStep"/> <assetInfo ref="assetInfoRef"></assetInfo> </template> <script setup lang="tsx" name="assetInventory"> import { ref, reactive, inject, onMounted,nextTick } from "vue"; import ProTable from "@/components/ProTable/index.vue"; import { assetListData ,queryGroupList,addAssetList,deleteAssetList,assetListDown,editBatchAssetList,assetListInfo,editAssetList} from "@/api/modules/assetAllocation"; import { ProTableInstance } from "@/components/ProTable/interface"; import { setTreeData,formatToTree } from "@/utils/tools"; import { assetClassificationList,getPositionList,setValueClildList,nonstandardList,getOrgSubjectList,getInstitution } from '@/api/modules/public'; import { getUserDepartment } from "@/api/modules/user"; import { assetListType,assetType,markStatusType } from "@/utils/dict"; import { da, fa, pa } from "element-plus/es/locale"; import addAsset from "./mode/addAsset.vue"; import assetInfo from "./mode/assetInfo.vue"; import { useHandleData } from "@/hooks/useHandleData"; import { ElMessage, FormInstance } from "element-plus"; import { printAssetMark } from "@/api/modules/assetAllocation"; import moment from "moment"; const hasBtnPermission: any = inject('hasBtnPermission'); // ProTable 实例 const proTable = ref(); // 子界面需要用到的设置值 const configuration = reactive({ assetCategory: [], // 资产分类 positionList: [], // 存放地点 departmentList: [], // 使用部门 sourceList: [], // 资产来源 nonstandardList: [], // 非标准资产 unitList:[], // 计量单位 institutionalEntity:[], //机构主体 assentityList:[], // 主体 assinstitutional:[],//机构 }) const selectedIds = ref<string[]>([]); // 在组件顶部定义 // 非标准资产 const nonstandardData = ref([]); const assentityList = ref([]) const assinstitutional = ref([]) // 资产类型弹窗 const dialogFormVisible = ref(false) const form = reactive({ type:'', nonStandardAssetsId:'' }) const rules = reactive({ type: [{ required: true, message: "请选择资产类型", trigger: ["blur", "change"] }], nonStandardAssetsId: [{ required: true, message: "请选择非标准资产", trigger: ["blur", "change"] }], }) const getTableList = (params: any) => { if (params.purchaseDate) { params.purchaseDateStart = params.purchaseDate[0] + ' 00:00:00'; params.purchaseDateEnd= params.purchaseDate[1] + ' 23:59:59'; delete params.purchaseDate; } if (params.maintenanceExpirationDate) { params.maintenanceExpirationDateStart = params.maintenanceExpirationDate[0] + ' 00:00:00'; params.maintenanceExpirationDateEnd = params.maintenanceExpirationDate[1] + ' 23:59:59'; delete params.maintenanceExpirationDate; } if(params.useUserNameData) { params.useUserNameList = params.useUserNameData.split('\n'); delete params.useUserNameData } if(params.assetCodeData) { params.assetCodeList = params.assetCodeData.split('\n'); delete params.assetCodeData } return assetListData(params) } const refreshTable = () => { proTable.value!.pageable.pageNum = 1; proTable.value?.refreshData(); // 清除表格勾选项 if (proTable.value) { proTable.value.clearSelection(); } }; const dataCallback = (data: any) => { const dataList = data?.dataList || []; const processedList = dataList.map(item => { try { return { ...item, imageUrlList: typeof item?.imageUrl === 'string' ? item.imageUrl.split(',') : [], purchaseDate: item.purchaseDate ? item.purchaseDate.split(" ")[0] : '' }; } catch (error) { return { ...item, imageUrlList: [] }; } }); return { list: processedList, total: data?.totalCount, pageNum: data?.page, pageSize: data?.pageSize }; }; // 查询计量单位 const getUnitList = async () => { const response = await setValueClildList({dictCode: 'UNIT_MEASUREMENT'}); const data = response.data || []; configuration.unitList = data } // 查询机构主体 const getOrgSubjectListData = async() => { // 机构 const responseAss = await getInstitution({ id: 'ASS_INSTITUTIONAL' }); const data = responseAss.data || []; if(data && data.length > 0) { configuration.assinstitutional = data assinstitutional.value = data } // 主体 const response = await getInstitution({ id: 'OFFICIAL_SEAL_ORG' }); const data1 = response.data || []; if(data1 && data1.length > 0) { configuration.assentityList = data1 assentityList.value = data1 } // 机构主体(二和一接口),用来把主体,机构以及部门三者关联起来,单独调用上面接口,主要是为了排序好看,无语子...... const res = await getOrgSubjectList({}); const data2 = res.data || []; if(data && data.length > 0) { configuration.institutionalEntity = data2 } } const formatToDepTree = (arr, pid = 0) =>{ let result = []; for (let i = 0; i < arr.length; i++) { if (arr[i].pid === pid) { arr[i].label = arr[i].name let children = formatToDepTree(arr, arr[i].workOADepartmentId); if (children.length > 0) { arr[i].children = children; } result.push(arr[i]); } } return result; } // 表格配置项 const columns = reactive([ { prop: "assetStatus", label: "资产状态", fixed: "left", minWidth:100 , enum: assetListType, search: { el: "select", props: { filterable: true } }, render: scope => { if (scope.row.assetStatus == '1') { return ( 空闲 ); } else if (scope.row.assetStatus == '2') { return ( 在用 ); } else if (scope.row.assetStatus == '3') { return ( 已处置 ); } } }, { prop: "markStatus", label: "资产标记", isShow:false, enum:markStatusType, search: { el: "select", props: { filterable: true } }, fieldNames: { label: "label", value: "value" } }, { prop: "markTagsName", label: "资产标记", fixed: "left", minWidth:100 , render: scope => { if (scope.row.markTags == '0') { return ( 派发待领用 ); } else if (scope.row.markTags == '1') { return ( 领用审批中 ); } else if (scope.row.markTags == '2') { return ( 退还审批中 ); } else if (scope.row.markTags == '3') { return ( 借用审批中 ); } else if (scope.row.markTags == '4') { return ( 借用 ); } else if (scope.row.markTags == '5') { return ( 调拨审批中 ); } else if (scope.row.markTags == '6') { return ( 维修审批中 ); } else if (scope.row.markTags == '7') { return ( 处置审批中 ); } else if (scope.row.markTags == '8') { return ( 待处理 ); } else if (scope.row.markTags == '9') { return ( 归还审批中 ); } } }, { prop: "assetCodeData", label: "资产编码", isShow:false, search: { el: "input", type: 'textarea', placeholder: '多个编码请换行' } , minWidth:100, }, { prop: "assetCode", label: "资产编码", fixed: "left", minWidth:100, render: (scope) => { return ( handleAssetCodeClick(scope.row)}> {scope.row.assetCode} ); } }, { prop: "assetName", label: "资产名称", fixed: "left", search: { el: "input" },minWidth:100 }, { prop: "assetCategoryName", label: "资产分类", minWidth:100 , }, { prop: "assetCategoryIdList", label: "资产分类", isShow:false, enum: async () => { // 获取资产分类数据,扁平数据 const { data } = await assetClassificationList({}); const treeData = formatToTree(data); configuration.assetCategory = treeData; return { data: treeData } }, fieldNames: { label: "categoryName", value: "id" }, search: { el: "tree-select", props: { filterable: true, multiple: true, checkStrictly: true, // 允许选择父节点 nodeKey: "id", // 每个节点的唯一标识字段 props: { label: "label", value: "id", children: "children" } } }, }, { prop: "nonStandardAssetsId", label: "资产类型", minWidth:100 , isShow:false, }, { prop: "type", label: "资产类型", minWidth:100 ,enum: assetType, search: { el: "select", props: { filterable: true } },}, { prop: "useUserName", label: "使用人" }, { prop: "useUserNameData", label: "使用人", isShow:false, search: { el: "input", type: 'textarea', placeholder: '多个使用人请换行' } }, { prop: "useOrgIdList", label: "使用机构", search: { el: "select", props: { filterable: true, multiple: true}}, minWidth:100 , enum: assinstitutional, isShow:false, fieldNames: { label: "name", value: "detailCode" }, }, { prop: "useOrgName", label: "使用机构", minWidth:100 , }, { prop: "useSubjectId", label: "使用主体", search: { el: "select" } , minWidth:100 , enum: assentityList, isShow:false, fieldNames: { label: "remarks", value: "detailCode" }, }, { prop: "useSubjectName", label: "使用主体", minWidth:100 , }, { prop: "useDepartmentId", label: "使用部门", minWidth:100 , enum: async () => { // 获取部门数据 const { data } = await getUserDepartment(); data.forEach(item => { item.pid = item.extMap.parentWorkOADepartmentId item.workOADepartmentId = item.value item.id = item.value item.name = item.label }) const treeData = formatToDepTree(data); configuration.departmentList = treeData return { data: treeData } }, fieldNames: { label: "label", value: "value" }, search: { el: "tree-select", props: { filterable: true, multiple: true, checkStrictly: true, // 允许选择父节点 nodeKey: "value", // 每个节点的唯一标识字段 props: { label: "label", value: "value", children: "children" } } } }, { prop: "useDepartmentName", label: "使用部门", isShow:false, minWidth:100 , }, { prop: "storageLocationIdList", label: "存放地点", minWidth:100 , isShow:false, enum: async () => { // 获取存放地点 const { data } = await getPositionList({ pageNum: 1, pageSize: 9999 }); const deepCopy = JSON.parse(JSON.stringify(data['dataList'])); configuration.positionList = deepCopy; return { data: data['dataList'] }; }, fieldNames: { label: "position", value: "id" }, search: { el: "select", props: { filterable: true , multiple: true} }, }, { prop: "storageLocationName", label: "存放地点", minWidth:100 , }, { prop: "adminName", label: "管理员", search: { el: "input" } }, { prop: "affiliatedInstitutionName", label: "所属机构",minWidth:100 }, { prop: "affiliatedInstitutionIdList", label: "所属机构", isShow:false, search: { el: "select" , props: { filterable: true , multiple: true}},minWidth:100 ,enum: assinstitutional, fieldNames: { label: "name", value: "detailCode" }, }, { prop: "affiliatedSubjectName", label: "所属主体",minWidth:100 }, { prop: "affiliatedSubjectId", label: "所属主体", isShow:false, search: { el: "select" },minWidth:100,enum: assentityList, fieldNames: { label: "remarks", value: "detailCode" } }, { prop: "assetSourceTypeList", label: "资产来源", isShow:false, enum: async () => { // 获取资产来源 const data = await setValueClildList({dictCode:'SOURCE_ASSETS'}); configuration.sourceList = data['data'] return { data: data['data'] } }, fieldNames: { label: "itemLabel", value: "itemValue" }, search: { el: "select", props: { filterable: true , multiple: true}}, }, { prop: "sourceCode", label: "资产来源", minWidth:100 , }, { prop: "brand", label: "品牌", search: { el: "input" }, }, { prop: "specificationModel", label: "规格型号", search: { el: "input" },minWidth:100 }, { prop: "serialNumber", label: "序列号", search: { el: "input" } }, { prop: "measurementUnit", label: "计量单位",minWidth:100 }, { prop: "remarks", label: "备注",search: { el: "input" } }, { prop: "supplierName", label: "供应商", search: { el: "input" }, }, { prop: "inBoundNo", label: "入库单号",minWidth:100 }, { prop: "nonStandardAssetsId", label: "非标准资产", enum: async () => { // 获取非标准资产 const data = await nonstandardList({}); nonstandardData.value = data configuration.nonstandardList = data return { data: data } }, isShow: false, fieldNames: { label: "name", value: "id" }, search: { el: "select", props: { filterable: true } }, }, { prop: "purchaseDate", label: "购入日期", minWidth:148 , search: { el: "date-picker", span: 2, props: { type: "daterange", valueFormat: "YYYY-MM-DD" }, }, }, { prop: "maintenanceExpirationDate", label: "维保到期日期", isShow: false, search: { el: "date-picker", span: 2, props: { type: "daterange", valueFormat: "YYYY-MM-DD" }, }, }, { prop: "operation", label: "操作",fixed: "right", isShow: true, sortable: false } ]) // 批量导出 const batchExport = async (command: any) => { try { const selectedIds = proTable.value?.selectedListIds || []; // 验证选择(如果command不是2,则需要选择数据) if (command != 2 && selectedIds.length === 0) { ElMessage.error({ message: 请选择要操作的数据 }); return; } const params = { idList: command === 2 ? [] : selectedIds // command=2表示导出全部 }; // 1. 获取文件数据(确保response是Blob或ArrayBuffer) const response = await assetListDown(params); // 2. 检查响应数据是否有效 if (!response) { ElMessage.error("导出失败:未获取到文件数据"); return; } // 3. 创建Blob对象(明确指定MIME类型) const blob = new Blob([response], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8" }); // 4. 创建下载链接 const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = 资产清单_${new Date().toLocaleDateString()}.xlsx; // 添加日期避免重复 // 5. 触发下载 document.body.appendChild(link); link.click(); // 6. 清理 setTimeout(() => { document.body.removeChild(link); window.URL.revokeObjectURL(url); }, 100); } catch (error) { console.error("导出失败:", error); } }; // 批量删除 const batchDelete = async (ids: string[]) => { if(ids && ids.length === 0) { ElMessage.error({ message: 请选择要操作的数据 }); return } await useHandleData(deleteAssetList, { idList: ids }, 确认删除); refreshTable() } // 批量编辑 const batcEdit = async (ids: string[]) => { if (ids && ids.length === 0) { ElMessage.error({ message: 请选择要操作的数据 }); return; } // 从表格中获取当前所有选中的行数据 const selectedRows = proTable.value?.selectedList || []; const types = selectedRows.map(row => row.type); const uniqueTypes = [...new Set(types)]; if (uniqueTypes.length > 1) { ElMessage.warning("只能选择相同类型的资产进行批量编辑"); return; } selectedIds.value = ids; editBatchData('批量编辑', 'batchEdit', {}); } // 打印标签 const openTagPrint = async (ids: string[]) => { if(ids && ids.length === 0) { ElMessage.error({ message: 请选择要操作的数据 }); return } const data = await printAssetMark({ idList: ids }); if (data.code == 0) { ElMessage.success({ message: data.msg }); refreshTable() } else { ElMessage.error({ message: data.msg }); } } const closeDialog = () => { dialogFormVisible.value = false // 清除表格勾选项 if (proTable.value) { proTable.value.clearSelection(); } } // 子组件的上一步操作 const handlePreviousStep = () => { dialogFormVisible.value = true; // 重新打开对话框 // if( Type.value == 'batchEdit') {} // 回显之前选择的数据(form 已在 openDrawer 时保存) nextTick(() => { ruleFormRef.value?.clearValidate(); // 清除校验状态 }); proTable.value!.setCheckedRows(proTable.value?.selectedList); // console.log('proTable.value',proTable.value?.selectedList) // proTable.value!.toggleRowSelection(proTable.value?.selectedList[0]) // rows.forEach((row) => { // multipleTableRef.value!.toggleRowSelection( // row, // undefined, // ignoreSelectable // ) // }) }; const Title = ref(""); const Type = ref('add') const Row = ref({}) // 新增 const addNewData = (title: string,type:string, row: any = {}) => { Title.value = title Type.value = type Row.value = row // 清空表单值 form.type = ''; form.nonStandardAssetsId = ''; // 重置表单校验状态 nextTick(() => { ruleFormRef.value?.resetFields(); }); dialogFormVisible.value = true } // 编辑 const editData = async(title: string,type:string, row: any = {}) => { const {code , data ,msg} = await assetListInfo({ id: row.id }); if(code == 0) { form.type = row.type form.nonStandardAssetsId = '' let listData = [data] Title.value = title Type.value = type Row.value = listData openDrawer() } else { ElMessage.error(msg); } } // 批量编辑 const editBatchData = (title: string,type:string, row: any = {}) => { Title.value = title Type.value = type Row.value = row // 清空表单值 form.type = ''; form.nonStandardAssetsId = ''; // 重置表单校验状态 nextTick(() => { ruleFormRef.value?.resetFields(); }); dialogFormVisible.value = true } // 查看详情 const assetInfoRef = ref<InstanceType<typeof addAsset> | null>(null); const handleAssetCodeClick = async(row: any) => { const params = { row:{...row}, api:deleteAssetList, configuration:configuration, refreshTable: () => { proTable.value!.pageable.pageNum = 1; proTable.value?.refreshData(); } } assetInfoRef.value?.acceptParams(params) } // 下一步 const nextTips = () => { ruleFormRef.value!.validate(async valid => { if (!valid) return; try { openDrawer() } catch (error) { console.log(error); } }) } // 新增/编辑 const ruleFormRef = ref<FormInstance>(); const addAssetRef = ref<InstanceType<typeof addAsset> | null>(null); const openDialog = () => { // 清空表单值 form.type = ''; form.nonStandardAssetsId = ''; // 重置表单校验状态 nextTick(() => { ruleFormRef.value?.resetFields(); }); dialogFormVisible.value = true } const openDrawer = () => { if(Type.value === 'add') { dialogFormVisible.value = false const params = { title:Title.value, type:Type.value, row:{...Row.value}, form:{...form}, configuration:configuration, isView:false, api: addAssetList, refreshTable: () => { proTable.value!.pageable.pageNum = 1; proTable.value?.refreshData(); } } addAssetRef.value?.acceptParams(params) } else if(Type.value === 'edit'){ const params = { title:Title.value, type:Type.value, configuration:configuration, isView:false, row:{}, form:{...form}, infoRow:{...Row.value}, api: editAssetList, refreshTable: () => { proTable.value!.pageable.pageNum = 1; proTable.value?.refreshData(); } } addAssetRef.value?.acceptParams(params) } else { dialogFormVisible.value = false const params = { title:Title.value, type:Type.value, configuration:configuration, isView:false, form:{...form}, row:{selectedIds:selectedIds.value}, api: editBatchAssetList, refreshTable: () => { proTable.value!.pageable.pageNum = 1; proTable.value?.refreshData(); } } addAssetRef.value?.acceptParams(params) } } onMounted(() => { getUnitList(); getOrgSubjectListData(); }) </script> <style lang="scss" scoped> .more_imgs{ div { display: inline-flex; } .viewImage{ width: 25px; height: 25px; cursor: pointer; } } /* 或仅针对特定列 */ .el-table .el-table__header th.is-center .cell { text-align: center; } /* 确保选择列可见 */ ::v-deep .el-table__fixed-left { .el-table__cell.is-hidden > * { visibility: visible !important; } .el-checkbox { display: inline-block; } } </style>点击handlePreviousStep后,回显勾选的数据

<script setup lang="tsx"> import { DataTableSortState, NButton, NTag, SelectOption } from "naive-ui"; import { fetchGetChangeLedgerList, fetchBatchDeleteChange, fetchDeleteChange, } from "@/service/api"; import { generateZtUri } from "@/plugins"; import { $t } from "@/locales"; import { useAppStore } from "@/store/modules/app"; import { useTable, useTableOperate } from "@/hooks/common/table"; import { useAuth } from "@/hooks/business/auth"; import LedgerSearch from "./modules/ledger-search.vue"; import { useBoolean } from "@sa/hooks"; import { canUpgradeRecord } from "@/constants/business"; import { ref, onMounted, watch, h, computed } from "vue"; // import {useDepartment} from "@/hooks/common/useDepartment" // 导入嵌入模态框 import { getFullDeptOptions } from "@/service/api/"; // 导入完整部门选项服务 import { renderTooltip2, smartTextSplit } from "@/utils/other"; // 存储完整部门选项 const fullDeptOptions = ref<SelectOption[]>([]); // 加载完整部门列表 onMounted(async () => { fullDeptOptions.value = await getFullDeptOptions(); }); const deptRecord = computed(() => { const record: Record<string, string> = {}; fullDeptOptions.value.forEach(option => { record[option.value as string] = option.label as string; }); return record; }); const appStore = useAppStore(); const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams, } = useTable({ apiFn: fetchGetChangeLedgerList, 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 source: null, pecoName: null, peco: null, softwareChange: null, projectno: null, dept: null, canUpgrade: null, softResponsiblePerson: null, elecResponsiblePerson: null, changeResponsiblePerson: null, customerDemandTime: null, softCompletionTime: null, hardCompletionTime: null, fmtCustomerDemandTime: null, fmtSoftCompletionTime: null, fmtHardCompletionTime: null, orderList: [], }, columns: () => [ { type: "selection", align: "center", width: 48, }, { key: "index", title: $t("common.index"), align: "center", width: 64, sorter: false, }, { key: "hwswcoId", title: $t("page.change.ledger.hwswcoId"), align: "left", minWidth: 60, width: 60, maxWidth: 200, sorter: true, fixed: "left", resizable: true, render: (row) => { const contents: any[] = []; contents.push( 未填写${$t("page.change.ledger.hwswcoId")}, 无法跳转禅道, ); if (row.hwswcoId !== null) { return h( NButton, { strong: true, secondary: true, size: "small", type: "info", onClick: () => { const href = generateZtUri(${row?.hwswco_url}); window.open(href, "_blank"); }, }, { default: () => row.hwswcoId, }, ); } else { return <NTag>{row.hwswcoId}</NTag>; } }, }, { key: "source", title: $t("page.change.ledger.source"), align: "center", width: 80, sorter: true, resizable: true, }, { key: "pecoName", title: $t("page.change.ledger.pecoName"), align: "center", minWidth: 50, width: 100, maxWidth: 200, sorter: true, resizable: true, render: (row) => { const contents: any[] = smartTextSplit(row.pecoName, 50); return row.pecoName ? renderTooltip2(row.pecoName.slice(0, 10), contents) : null; }, }, { key: "peco", title: $t("page.change.ledger.peco"), align: "center", minWidth: 50, width: 100, maxWidth: 200, sorter: true, resizable: true, }, { key: "softwareChange", title: $t("page.change.ledger.softwareChange"), align: "center", minWidth: 50, width: 100, maxWidth: 200, sorter: true, resizable: true, }, { key: "projectno", title: $t("page.change.ledger.projectno"), align: "center", minWidth: 40, width: 80, maxWidth: 100, sorter: true, resizable: true, }, { key: "dept", title: $t("page.change.ledger.dept"), align: "center", width: 130, sorter: true, typeRecord: deptRecord, // 添加映射对象 render: (row) => { // 使用完整部门选项映射部门名称 if (row.dept && fullDeptOptions.value.length > 0) { const dept = fullDeptOptions.value.find( (option) => option.value === row.dept, ); if (dept) { return {dept.label}; } } return null; }, }, { key: "softResponsiblePersonName", title: $t("page.change.ledger.softResponsiblePersonName"), align: "center", minWidth: 50, width: 110, maxWidth: 200, sorter: true, resizable: true, }, { key: "elecResponsiblePersonName", title: $t("page.change.ledger.elecResponsiblePersonName"), align: "center", minWidth: 50, width: 110, maxWidth: 200, sorter: true, resizable: true, }, // { // key: "changeResponsiblePerson", // title: $t("page.change.ledger.changeResponsiblePerson"), // align: "center", // minWidth: 50, // }, { key: "canUpgrade", title: $t("page.change.ledger.canUpgrade"), align: "center", minWidth: 50, width: 80, maxWidth: 100, sorter: true, resizable: true, typeRecord: canUpgradeRecord, render: (row) => { console.log(row.canUpgrade); if (row.canUpgrade) { // console.log( row.canUpgrade, canUpgradeRecord[ // row.canUpgrade as Api.Change.canUpgrade // ]) const label = $t( canUpgradeRecord[row.canUpgrade as Api.Change.canUpgrade], ); return <NTag type="default">{label}</NTag>; } return null; }, }, { key: "fmtCustomerDemandTime", title: $t("page.change.ledger.customerDemandTime"), align: "center", width: 100, sorter: true, resizable: true, render: (row) => row.fmtCustomerDemandTime === "1970-01-01" ? null : row.fmtCustomerDemandTime, }, { key: "fmtSoftCompletionTime", title: $t("page.change.ledger.softCompletionTime"), align: "center", width: 100, sorter: true, resizable: true, render: (row) => row.fmtSoftCompletionTime === "1970-01-01" ? null : row.fmtSoftCompletionTime, }, { key: "fmtHardCompletionTime", title: $t("page.change.ledger.hardCompletionTime"), align: "center", width: 100, sorter: true, resizable: true, render: (row) => row.fmtHardCompletionTime === "1970-01-01" ? null : row.fmtHardCompletionTime, }, { key: "operate", title: $t("common.operate"), align: "center", width: 130, fixed: "right", render: (row) => ( {hasAuth("B_ChangeLedger_Upgrade") ? ( <NButton type="primary" ghost size="small" onClick={() => { const href = generateZtUri(${row?.hwswco_upgrade_url}); window.open(href, "_blank"); }} > {$t("common.upgrade")} </NButton> ) : null} {hasAuth("B_ChangeLedger_Assign") ? ( <NButton type="primary" ghost size="small" onClick={() => { const href = generateZtUri(${row?.hwswco_url}); window.open(href, "_blank"); }} > {$t("common.assign")} </NButton> ) : null} ), }, ], }); const { drawerVisible, operateType, editingData, handleAdd, handleEdit, handleClone, checkedRowKeys, onBatchDeleted, onDeleted, // closeDrawer } = useTableOperate(data, getData); const handleSorterChange = (newSorter: DataTableSortState) => { let columnKey = String(newSorter.columnKey); // 移除fmt前缀(不区分大小写) if (/^fmt/i.test(columnKey)) { columnKey = columnKey.substring(3); } // 小驼峰转下划线命名(优化连续大写字母处理) columnKey = columnKey .replace(/([a-z])([A-Z])/g, "$1_$2") .replace(/([A-Z])([A-Z][a-z])/g, "$1_$2") .toLowerCase(); // 处理排序状态 if (newSorter.order === "ascend") { searchParams.orderList = [columnKey]; } else if (newSorter.order === "descend") { searchParams.orderList = ["-" + columnKey]; } else { searchParams.orderList = []; } getData(); }; const { hasAuth } = useAuth(); const { bool: uploadVisible, setTrue: openModal } = useBoolean(); async function handleBatchDelete() { // request const { error } = await fetchBatchDeleteChange({ ids: checkedRowKeys.value, }); if (!error) { onBatchDeleted(); } } async function handleDelete(id: number) { // request const { error } = await fetchDeleteChange({ id }); if (!error) { onDeleted(); } } interface Emits { (e: "update-total", payload: number | undefined): void; } const emit = defineEmits<Emits>(); watch( () => mobilePagination.value.itemCount, (newVal, oldVal) => { emit("update-total", newVal); }, { deep: true }, ); getData(); const buList = ref<any[]>([ "DDP", "MDP", "FEP", "VDF", "CLEAN", "ETCH1", "ETCH2", "ETCH3", "HDF", ]); </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.change.ledger.title") }} ({{ $t("datatable.itemCount", { total: mobilePagination?.itemCount, }) }}) <NRadioGroup v-model:value="searchParams.dept" size="small" style="margin-top: 5px; border-top: 1px sloid var(--n-border-color)" @update:value=" () => { getData(); } " > <NRadioButton v-for="(item, key) in buList" :key="key" :value="item" :label="item" > </NRadioButton> </NRadioGroup> </template> <template #header-extra> <template #default> </template> </template> <NDataTable v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" size="small" :flex-height="!appStore.isMobile" :scroll-x="2400" :loading="loading" remote :row-key="(row) => row.id" :pagination="mobilePagination" class="sm:h-full" @update:sorter="handleSorterChange" /> </NCard> </template> <style scoped></style> bulist和searchParams.dept的转换

基础表格组件-EhGrid 平台表格组件 #API #属性 参数 说明 类型 可选值 默认值 id 表格的 id 属性 必填 string '' dataSource 表格数据来源配置 object/array[object] 必填 [] grid 表格展示相关的属性配置 object 必填 (default grid) options 表格的其他属性配置 object - (default options) table-collapse 是否通过点击表格标题收缩表格 boolean - false #回调事件 事件 说明 参数 selection-change 选择行的数据时发出,可以通过传入这个方法获取当前选中行 selected-rows:当前选中行数组 cell-click 单击单元格时发出 row, column, cell, event:选中行,选中列,点击单元格,点击事件 data-change 每次表格数据查询数据后发出 data:当前获取的表格数据 expand-row-click 与扩展表格配合使用,点击扩展表格的行后发出 row, column, event:选中行,选中列,点击事件 #实例方法 事件 说明 参数 返回值 doSearch / bindGrid 触发表格重新加载数据的操作 - 加载表格数据 的 Promise,成功结果的参数是 tabledata openExport 打开导出 excel 的模态框 - - clearSelection 清除表格选中状态 - - tableInstanceMethods 去调用对应 element 的 table 实例方法 functionName:需要调用的方法名, ...methodArg:需要传入方法的参数项 - #插槽 toolbar:写入按钮组的内容,将按钮放置在顶部按钮区域,样式功能等需自己完善。 #使用详解 #dataSource (Object | Array 必填) 示例: // template: <eh-grid :dataSource="dataSource"></eh-grid> //或 <eh-grid :data-source="dataSource"></eh-grid> ... // script => data : <script> ... data(){ return { dataSource: { // formdata参数:控制器名 (string 必填) bean: "mbbBdMrlController", // formdata参数:方法名 (string 必填) method: "select", // formdata参数:entity (string 必填) entity: "com.epichust.entity.MbbBdMrl", // 使用此方法代替bean与method作为请求方法(string 可选) request:'mbbBdMrlController!select.m', // 请求url参数 (Object 可选) params: { id : 123 }, // 配置过滤选项 (Array<Object>) filters: [ { field: "id", // 字段名 compare: "cn,ge", // 比较方式(多个以逗号分隔) data: "", // 值 type: "text" // 输入框类型(text、input、select、date、time、datetime) options:{ // 新增select类型,将模板查询输入改为选择框,可将EhSelect的传入属性作为该配置对象的属性。例如keyName、valueName等。 }, }, { field: "code", compare: "cn,eq", data: "", } ], // 是否首次自动加载数据 loadDataOnFirst: true, // 数据请求方法实例 (Function 可忽略) serviceInstance: null, // 默认过滤字段 (Array | Object 可忽略) serviceInstanceInputParameters: null } // 特殊情况下,如果将dataSource赋值为数组类型的数据,表格将直接以传入的数组作为数据源!! } } </script> #grid (Object 必填) 表格展示相关的属性配置 默认配置 / 示例: // template: <eh-grid :grid="grid"></eh-grid> ... // script => data : <script> ... data(){ return { // element table 大部分属性都可以应用于此 grid: { // 表格标题 title:"", // 表格描述文字 description:"", // required 展示的列名配置 columns: [ // el-table-column Attributes(详细属性在ele官网) // { name, refEntity, refName } // { // label, 表头 // prop, 列名 // isKey, 是否是主键 默认 true(要求是唯一值) // visible, 是否显示 默认 true // pass, 忽略发送该列名 默认 false // exportHideen, 是否导出时隐藏此列 默认 false // align, 居中 默认 'left' // formatter,width,minWidth,... } ], // 没有默认排序 defaultSort: { // prop: "id", // order: "ascending" }, // required 分页组件属性 pagination: { pageSize: 10, // 页容量 // currentPage: 1, pageSizes: [10, 20, 50, 100],// 页容量选项 }, // 出现表格框,且列可拉伸 border: true, size: "small", // 默认高度占据大半可视高度 // height: '', // 单位px,支持百分比高度,如:50% // rows:10, // page:1, // 最大高度,针对滚动条 maxHeight: "440px", //document.documentElement.clientHeight - 270, // 是否显示分页 pageable: true, // reduceMethod 待定 // reduceMethod: null, // 确定行是否被选择,结果取决于方法返回值 function selectable: null, // 是否支持列表项选中功能 mutiSelect: true, // 是否显示行号 showRowNumber: true, // 是否显示列名编辑 showColSetting: true, // 自定义数据列 // customColumnDataHeader: false, operates: { type: Object, default: { // width:按钮列宽 (200px), // fixed:是否固定(right), // list:按钮集合[] = // <{ // label: 按钮文字, // render: Function(key,row) 按钮文字渲染方法,如果存在这个方法,则label失效 // type :类型(primary / success / warning / danger / info / text), // style:Object 按钮样式, // icon:按钮图标, // disabled:是否禁用, // method:Function(key,row) 回调方法 // }> } }, // 是否可导出表格excel export: false, // 最大选择行数,默认1000,过多会顶替掉最开始选中的。 selectMax: 1000, // Number // 行选择框,操作按钮是否禁用 disabled: false, // 是否展示可伸缩grid sortable: true, // 表格所有的列是否可排序。(单个列在columns中单独设置) collapse: false, // collapse若为true,则配置此项,在折叠状态下标题可展示的字段。默认空。 collapseField: [], // ["code","date"] // 扩展行表格所展示的字段,类似columns,包含element列所有属性 expandColumns:[] // [{ prop:"name", label:"名称", width:"180px"}] // 加载扩展表格数据的方法 // 默认参数,row: 当前选择行,expandRows:所有已经展开的行数据,resolve:数据的回调。 // 展开行时,回调的第二个参数为 expandedRows;树形表格时第二参数为 expanded loadEexpandData: function(row, expandRows, resolve){ // 使用一个异步请求,通过qs转换formdata参数 return vue.$http.post("sysUserController!getRoleDetail.m", vue.$util.getParamByQs({ nd_: 1591845675402, id: row.id })) .then((res) => { // 请求结果传入resolve 回调函数,即可完成。 resolve(res && res.rows ? res.rows : []); }); }, "row-key": "id", // 必要!扩展表格和树形表格都需要定义此属性。 // 树形表格属性,最好与扩展表格 二选一使用。 "tree-props": { children: "children", hasChildren: "hasChildren" }, // 必要!树形子级属性:children 子级的集合,hasChildren:是否有子级的属性。 lazy: true, // 异步加载时,一般为 true load: (tree, treeNode, resolve) => { // tree:类似row,当前行数据。treeNode:树节点属性。resolve:数据的回调。 return vue.$http.post("sysUserController!getRoleDetail.m").then((res) => { // 请求结果传入resolve 回调函数,即可完成。 resolve(res && res.rows ? res.rows : []); }); }, // 是否显示手动上下排序按钮 sortButton: false, // 是否手动拖拽表格行 进行排序。若要保持选择行状态,需指定row-key !! manualSort: false, // 是否显示表格刷新按钮,默认显示 refreshBtn: true, // 当行选中时,选中行上添加的className highlightRowClass: '' // 未填时,选中没有新增任何class // 动态浮动表格滚动条 floatScroll: true, } } } </script> #options (Object 非必填) 表格的其他的一些属性配置 默认配置 / 示例: // template: <eh-grid :options="options"></eh-grid> ... // script => data : <script> ... data(){ return { options: { // 是否显示编辑过滤 showFilterTemplate: true, // 表格请求formdata参数 超时时间戳 (不传则自动计算后一分钟) nd: 1587624065070, // 表格请求formdata参数 search search: false, // 表格请求formdata参数 带总量查询 usetotal: true, // 额外参数字段:其中的属性会带入请求方法的 formdata中。 // 重要:如果要使用这个功能,需事先把 extraParamFields 定义好,哪怕是个空对象 {} extraParamFields: { // consition: '8a5aya54a5t4t43453thf4' filters: [ // 额外过滤条件,如果有值,则使用这个额外的过滤条件,数据写法与datasource中的filters一致。 { compare: "cn", data: "myName", field: "loginName" }, // 此三个属性值必填 ] }, // 在extraParamFields存在的情况下,extraParamMerged属性为true,则是额外过滤条件合并查询模板的过滤条件 extraParamMerged: false } } } </script> #selection-change (Function) 通过传入这个方法获取 当前选择行的数据 示例: <eh-grid @selection-change="getRow"></eh-grid> ... <script> ... methods: { getRow(row){ console.log(row) // 选其中一行会打印:[{"siteName":"车桥机加工厂","id":"123","code":"TESTCODE"}] } } </script> #tableInstanceMethods (Function : 未知) 传参:functionName (string) 需要调用的方法名。 后续传参:methodArg (参数项) 需要传入方法的参数项。 调用这个实例方法,实际去调用对应 element 的 table 实例方法。(el-table 的实例方法在 element 官方文档中查看) 示例: <eh-grid id="grid123" ref="grid123"></eh-grid> ... <script> ... methods: { // 这里我让第一行表格处于传中状态 selectRows(){ // 取值表格第一行的数据 let firstRow = this.$refs.grid123.data[0]; // 调用表格实例方法 toggleRowSelection,并传入必要的参数 this.$refs.grid123.tableInstanceMethods('toggleRowSelection', firstRow, true) } } </script> #使用插槽 #toolbar <eh-grid id="grid123" ref="grid123"> <template slot="toolbar"> <el-button @click="refresh">刷新</el-button> <el-button type="primary" @click="save">保存</el-button> </template> </eh-grid> ... <script> ... methods: { refresh(){ this.$refs.grid123.bindGrid() // 刷新了表格gird123的数据 } } </script> 这个是eh-grid的文档

<script setup lang="tsx"> import { DataTableSortState, NButton, NTag, SelectOption } from "naive-ui"; import { fetchGetChangeLedgerList, fetchBatchDeleteChange, fetchDeleteChange, } from "@/service/api"; import { generateZtUri } from "@/plugins"; import { $t } from "@/locales"; import { useAppStore } from "@/store/modules/app"; import { useTable, useTableOperate } from "@/hooks/common/table"; import { useAuth } from "@/hooks/business/auth"; import LedgerSearch from "./modules/ledger-search.vue"; import { useBoolean } from "@sa/hooks"; import { canUpgradeRecord } from "@/constants/business"; import { ref, onMounted, watch, h } from "vue"; // import {useDepartment} from "@/hooks/common/useDepartment" // 导入嵌入模态框 import { getFullDeptOptions } from "@/service/api/"; // 导入完整部门选项服务 import { renderTooltip2, smartTextSplit } from "@/utils/other"; // 存储完整部门选项 const fullDeptOptions = ref<SelectOption[]>([]); // 加载完整部门列表 onMounted(async () => { fullDeptOptions.value = await getFullDeptOptions(); }); const appStore = useAppStore(); const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams, } = useTable({ apiFn: fetchGetChangeLedgerList, 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 source: null, pecoName: null, peco: null, softwareChange: null, projectno: null, dept: null, canUpgrade: null, softResponsiblePerson: null, elecResponsiblePerson: null, changeResponsiblePerson: null, customerDemandTime: null, softCompletionTime: null, hardCompletionTime: null, fmtCustomerDemandTime: null, fmtSoftCompletionTime: null, fmtHardCompletionTime: null, orderList: [], }, columns: () => [ { type: "selection", align: "center", width: 48, }, { key: "index", title: $t("common.index"), align: "center", width: 64, sorter: false, }, { key: "hwswcoId", title: $t("page.change.ledger.hwswcoId"), align: "left", minWidth: 50, width: 80, maxWidth: 200, sorter: true, fixed: "left", resizable: true, // render: (row) => ( // // // {row.hwswcoId} // // // ), render: (row) => { //const href = ${row?.test_url}${row.testProcessId}?zentaosid=${appStore.zenTaoSid}; const contents: any[] = []; contents.push( 未填写${$t("page.change.ledger.hwswcoId")}, 无法跳转禅道, ); if (row.hwswcoId !== null) { return h( NButton, { strong: true, secondary: true, size: "small", type: "info", onClick: () => { const href = generateZtUri(${row?.hwswco_url}); window.open(href, "_blank"); }, }, { default: () => row.hwswcoId, }, ); } else { return <NTag>{row.hwswcoId}</NTag>; } }, }, { key: "source", title: $t("page.change.ledger.source"), align: "center", width: 80, sorter: true, resizable: true, }, { key: "pecoName", title: $t("page.change.ledger.pecoName"), align: "center", minWidth: 50, width: 100, maxWidth: 200, sorter: true, resizable: true, render: (row) => { const contents: any[] = smartTextSplit(row.pecoName, 50); return row.pecoName ? renderTooltip2(row.pecoName.slice(0, 10), contents) : null; }, }, { key: "peco", title: $t("page.change.ledger.peco"), align: "center", minWidth: 50, width: 100, maxWidth: 200, sorter: true, resizable: true, }, { key: "softwareChange", title: $t("page.change.ledger.softwareChange"), align: "center", minWidth: 50, width: 100, maxWidth: 200, sorter: true, resizable: true, }, { key: "projectno", title: $t("page.change.ledger.projectno"), align: "center", minWidth: 40, width: 80, maxWidth: 100, sorter: true, resizable: true, }, { key: "dept", title: $t("page.change.ledger.dept"), align: "center", width: 130, sorter: true, render: (row) => { // 使用完整部门选项映射部门名称 if (row.dept && fullDeptOptions.value.length > 0) { const dept = fullDeptOptions.value.find( (option) => option.value === row.dept, ); if (dept) { return <spn>{dept.label}</spn>; } } return null; }, }, { key: "softResponsiblePersonName", title: $t("page.change.ledger.softResponsiblePersonName"), align: "center", minWidth: 50, width: 110, maxWidth: 200, sorter: true, resizable: true, }, { key: "elecResponsiblePersonName", title: $t("page.change.ledger.elecResponsiblePersonName"), align: "center", minWidth: 50, width: 110, maxWidth: 200, sorter: true, resizable: true, }, // { // key: "changeResponsiblePerson", // title: $t("page.change.ledger.changeResponsiblePerson"), // align: "center", // minWidth: 50, // }, { key: "canUpgrade", title: $t("page.change.ledger.canUpgrade"), align: "center", minWidth: 50, width: 80, maxWidth: 100, sorter: true, resizable: true, typeRecord: canUpgradeRecord, render: (row) => { console.log(row.canUpgrade); if (row.canUpgrade) { // console.log( row.canUpgrade, canUpgradeRecord[ // row.canUpgrade as Api.Change.canUpgrade // ]) const label = $t( canUpgradeRecord[row.canUpgrade as Api.Change.canUpgrade], ); return <NTag type="default">{label}</NTag>; } return null; }, }, { key: "fmtCustomerDemandTime", title: $t("page.change.ledger.customerDemandTime"), align: "center", width: 100, sorter: true, resizable: true, render: (row) => row.fmtCustomerDemandTime === "1970-01-01" ? null : row.fmtCustomerDemandTime, }, { key: "fmtSoftCompletionTime", title: $t("page.change.ledger.softCompletionTime"), align: "center", width: 100, sorter: true, resizable: true, render: (row) => row.fmtSoftCompletionTime === "1970-01-01" ? null : row.fmtSoftCompletionTime, }, { key: "fmtHardCompletionTime", title: $t("page.change.ledger.hardCompletionTime"), align: "center", width: 100, sorter: true, resizable: true, render: (row) => row.fmtHardCompletionTime === "1970-01-01" ? null : row.fmtHardCompletionTime, }, { key: "operate", title: $t("common.operate"), align: "center", width: 130, fixed: "right", render: (row) => ( {hasAuth("B_ChangeLedger_Upgrade") ? ( <NButton type="primary" ghost size="small" onClick={() => { const href = generateZtUri(${row?.hwswco_upgrade_url}); window.open(href, "_blank"); }} > {$t("common.upgrade")} </NButton> ) : null} {hasAuth("B_ChangeLedger_Assign") ? ( <NButton type="primary" ghost size="small" onClick={() => { const href = generateZtUri(${row?.hwswco_url}); window.open(href, "_blank"); }} > {$t("common.assign")} </NButton> ) : null} ), }, ], }); const { drawerVisible, operateType, editingData, handleAdd, handleEdit, handleClone, checkedRowKeys, onBatchDeleted, onDeleted, // closeDrawer } = useTableOperate(data, getData); // 定义排序器 // const sorter = ref<DataTableSortState | null>(null); // // 使用自定义钩子 // const { sortTable } = useTableSort(data, sorter); // 处理排序器变化 const handleSorterChange = (newSorter: DataTableSortState) => { let columnKey = String(newSorter.columnKey); // 移除fmt前缀(不区分大小写) if (/^fmt/i.test(columnKey)) { columnKey = columnKey.substring(3); } // 小驼峰转下划线命名(优化连续大写字母处理) columnKey = columnKey .replace(/([a-z])([A-Z])/g, "$1_$2") .replace(/([A-Z])([A-Z][a-z])/g, "$1_$2") .toLowerCase(); // 处理排序状态 if (newSorter.order === "ascend") { searchParams.orderList = [columnKey]; } else if (newSorter.order === "descend") { searchParams.orderList = ["-" + columnKey]; } else { searchParams.orderList = []; } getData(); }; const { hasAuth } = useAuth(); const { bool: uploadVisible, setTrue: openModal } = useBoolean(); async function handleBatchDelete() { // request const { error } = await fetchBatchDeleteChange({ ids: checkedRowKeys.value, }); if (!error) { onBatchDeleted(); } } async function handleDelete(id: number) { // request const { error } = await fetchDeleteChange({ id }); if (!error) { onDeleted(); } } function edit(id: number) { handleEdit(id); } function clone(id: number) { handleClone(id); } interface Emits { (e: "update-total", payload: number | undefined): void; } const emit = defineEmits<Emits>(); watch( () => mobilePagination.value.itemCount, (newVal, oldVal) => { emit("update-total", newVal); }, { deep: true }, ); // async function handleDownload() { // // 请求cosnt {data, error } = await fetchGetProblemList({size:xxx,current:1}) // if (checkedRowKeys.value.length === 0) { // window.$message?.warning('请选择需要导出的数据!'); // return; // } // const { data:responseData, error } = await fetchGetChangeLedgerList({ // size: checkedRowKeys.value.length, // current: 1, // ids: checkedRowKeys.value, // }); // if (error) { // console.error("获取数据时出错:", error); // alert("获取数据时发生错误,请稍后再试。"); // return; // } // // 关键修改:将部门ID转换为部门名称 // const processedRecords = responseData.records.map(record => { // // 查找匹配的部门选项 // const deptOption = fullDeptOptions.value.find( // option => option.value === record.dept // ); // // 转换canUpgrade数值为文本标签 // const upgradeLabel = canUpgradeRecord[record.canUpgrade as keyof typeof canUpgradeRecord] // ? $t(canUpgradeRecord[record.canUpgrade as keyof typeof canUpgradeRecord]) // : record.canUpgrade; // 未匹配时保留原值 // // 返回新对象(保留原数据,替换dept和canUpgrade字段) // return { // ...record, // dept: deptOption ? deptOption.label : record.dept, // canUpgrade: upgradeLabel // 替换为转换后的文本 // }; // }); // // 翻译表头 // const titleMap = { // hwswcoId: $t("page.change.ledger.hwswcoId"), // source: $t("page.change.ledger.source"), // pecoName: $t("page.change.ledger.pecoName"), // peco: $t("page.change.ledger.peco"), // softwareChange:$t("page.change.ledger.softwareChange"), // projectno: $t("page.change.ledger.projectno"), // dept: $t("page.change.ledger.dept"), // softResponsiblePersonName: $t("page.change.ledger.softResponsiblePersonName"), // elecResponsiblePersonName: $t("page.change.ledger.elecResponsiblePersonName"), // canUpgrade: $t("page.change.ledger.canUpgrade"), // // customerDemandTime: $t("page.change.ledger.customerDemandTime"), // // softCompletionTime: $t("page.change.ledger.softCompletionTime"), // // hardCompletionTime: $t("page.change.ledger.hardCompletionTime"), // fmtCustomerDemandTime: $t("page.change.ledger.fmtCustomerDemandTime"), // fmtSoftCompletionTime: $t("page.change.ledger.fmtSoftCompletionTime"), // fmtHardCompletionTime: $t("page.change.ledger.fmtHardCompletionTime"), // }; // const headers = Object.keys(titleMap).map((key) => titleMap[key]); // const sheetData = [headers]; // processedRecords.forEach(record => { // const row = Object.keys(titleMap).map(key => record[key]); // sheetData.push(row); // }); // const sheetName = $t("page.change.ledger.title"); // const sheets: SheetData[] = [ // { // sheetName: sheetName, // data: sheetData, // }, // ]; // exportXlsx(sheets, ${sheetName}); // } // 在页面加载时获取最新数据 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.change.ledger.title") }} ({{ $t("datatable.itemCount", { total: mobilePagination?.itemCount, }) }}) </template> <template #header-extra> <template #default> </template> </template> <NDataTable v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" size="small" :flex-height="!appStore.isMobile" :scroll-x="2400" :loading="loading" remote :row-key="(row) => row.id" :pagination="mobilePagination" class="sm:h-full" @update:sorter="handleSorterChange" /> </NCard> </template> <style scoped></style>

大家在看

recommend-type

模拟电子技术设计自动化控制系统

主要内容: 选取一种方法设计音乐彩灯控制器,要求该音乐彩灯控制器电路由三路不同控制方法的彩灯所组成,采用不同颜色的发光二极管作课题实验。 基本要求: (1)第一路为音乐节奏控制彩灯,按音乐节拍变换彩灯花样。 (2)第二路按音量的强弱(信号幅度大小)控制彩灯。强音时,灯的亮度加大,且灯被点亮的数目增多。 (3)第三路按音量高低(信号频率高低)控制彩灯。低音时,某一部分灯点亮;高音时,另一部分灯电亮。
recommend-type

fonteditorV1.3.2.zip 字体工具

FontEditor为一款功能强大的字体编辑和字体格式转换工具,支持制作32*32的全字库。能将TTF,OTF矢量字库转换为BDF字符,可以很方便的将Windows已经安装到系统的矢量字库转换为BDF字库,并能将TTF,OTF文件直接转换成BDF格式,并支持BDF,FNT,FNB文件格式的互转换,随心所欲将windows字体应用于各种嵌入式系统中。并支持将GB2312,BIG5,GBK转换为UCS2,UTF8,并支持UCS2,UTF8编码的互转换   V1.2  运行环境:Win2003, WinXP, Win2000, NT, WinME   (1)BDF,FNT,FNB字体格式的互转换   (2)将Windows已经安装TTF转换为BDF格式   (3)将TTF文件导入转为BDF,FNT,FNB格式   (4)汉字查码   V1.25   (1)Windows已经安装TTF字体导入为BDF时,剔除无效字符   (2)将BDF,FNT,FNB导出为bitmap文件和字符宽度索引   (3)GB2312,BIG5,GBK转换为UCS2,UTF8,以及UCS2,UTF8互转换   V1.25.03   (1)将单个字符导出为Bitmap文件   (2)解决导出字库bitmap时,字符少于256个导出文件不正确的问题   (3)解决导出选择中字符实际上是导出所有字符的问题   V1.26   (1)增加修正字符点阵的功能,可对所有字符或者当前页字符的点阵大小和位移进行调整   (2)修正V1.25.03中导出位图文件选择取消无法退出程序的问题   V1.3   (1)增加导出全字库bitmap方式,同时支持二进制导出和ASCII码导出   (2)增强读取BDF文件的兼容性   (3)增加手动剔除无效字符功能   V1.3.2   (1)增加TTF文件导入调整字符点阵大小,控制位图的精度和导入位图的效果   运行环境:Win2003, WinXP, Win2000, NT, WinME
recommend-type

linux pcap 报文 解析 报头剥离

#########################功能说明################## pcapedit 程序可对pcap报文进行报文过滤,剥离部分包头及打标签功能。 支持通过pcap.conf配置文件设置标签内容及长度。 程序启动时,会导入配置文件中的标签内容,之后会将pcap包中的每个数据包末尾都打上该标签。标签内容填充不足,自动补0. ################################################## *************************可处理报文格式说明******** 可处理的报文格式需如下:ETH格式 --》ipv4 --> udp --> gtp_2152 --> ipv4 -->tcp -->http 剥离特殊包头后的帧格式:ETH格式 --》ipv4 --> tcp -->http *************************************************** @@@@@@@@@@@@@@@@@@@@@@@@@使用说明@@@@@@@@@@@@@@@@@ 将pcapedit 和 配置文件放到用一路径下,处理处的结果文件保存在当前路径。 [root@localhost cll]# ./pcapedit Input the pcap file name:3g_ctrl_data.pcap //符合帧格式的pcap文件处理结果 starting parse pcap file... 3g_ctrl_data.pcap input mark success! Input the pcap file name:aagxxx_data.pcap //符合帧格式的pcap文件处理结果 starting parse pcap file... aagxxx_data.pcap input mark success! Input the pcap file name:new3g_user.pcap //不符合帧格式的pcap文件处理结果 starting parse pcap file... cann't find target format packet. new3g_user.pcap input mark fail! Input the pcap file name:exit //exit 退出程序 [root@localhost cll]# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
recommend-type

基于TSI578的串行RapidIO交换模块设计

RapidIO互连构架是一种基于可靠性的开放式标准,可应用于连接多处理器、存储器和通用计算平台。Tundra公司的TSI578是第三代交换机芯片,可支援串行RapidIO的处理器与周边设备互连。文中简要介绍了基于TSI578芯片的RapidIO交换模块的设计原理和实现方法,并对一些关键技术进行介绍。
recommend-type

详细说明 VC++的MFC开发串口调试助手源代码,包括数据发送,接收,显示制式等29782183com

详细说明 VC++的MFC开发串口调试助手源代码,包括数据发送,接收,显示制式等29782183com

最新推荐

recommend-type

indispensable-tvosx64-3.14.0-javadoc.jar

indispensable-tvosx64-3.14.0-javadoc.jar
recommend-type

appsync-0.13.0-beta.jar

appsync-0.13.0-beta.jar
recommend-type

如官网下载不了的,直接下载安装包

如官网下载不了的,直接下载安装包
recommend-type

pic-smaller.zip

这款图片压缩工具支持 JPG、PNG、WEBP、GIF、SVG 等多种格式,能够进行批量处理,并提供可调节的压缩选项。它完全在浏览器中运行,免费使用,无需注册。
recommend-type

【scratch2.0少儿编程-游戏原型-动画-项目源码】键盘控制的画笔.zip

资源说明: 1:本资料仅用作交流学习参考,请切勿用于商业用途。更多精品资源请访问 https://blog.csdn.net/ashyyyy/article/details/146464041 2:一套精品实用scratch2.0少儿编程游戏、动画源码资源,无论是入门练手还是项目复用都超实用,省去重复开发时间,让开发少走弯路!
recommend-type

Node.js构建的运动咖啡馆RESTful API介绍

标题《sportscafeold:体育咖啡馆》指出了项目名称为“体育咖啡馆”,这个名字暗示了该项目可能是一个结合了运动和休闲主题的咖啡馆相关的网络服务平台。该项目运用了多种技术栈,核心的开发语言为JavaScript,这从标签中可以得到明确的信息。 从描述中可以提取以下知识点: 1. **Node.js**:体育咖啡馆项目使用了Node.js作为服务器端运行环境。Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它能够使得JavaScript应用于服务器端开发。Node.js的事件驱动、非阻塞I/O模型使其适合处理大量并发连接,这对于RESTFUL API的构建尤为重要。 2. **Express Framework**:项目中使用了Express框架来创建RESTFUL API。Express是基于Node.js平台,快速、灵活且极简的Web应用开发框架。它提供了构建Web和移动应用的强大功能,是目前最流行的Node.js Web应用框架之一。RESTFUL API是一组遵循REST原则的应用架构,其设计宗旨是让Web服务通过HTTP协议进行通信,并且可以使用各种语言和技术实现。 3. **Mongoose ORM**:这个项目利用了Mongoose作为操作MongoDB数据库的接口。Mongoose是一个对象文档映射器(ODM),它为Node.js提供了MongoDB数据库的驱动。通过Mongoose可以定义数据模型,进行数据库操作和查询,从而简化了对MongoDB数据库的操作。 4. **Passport.js**:项目中采用了Passport.js库来实现身份验证系统。Passport是一个灵活的Node.js身份验证中间件,它支持多种验证策略,例如用户名和密码、OAuth等。它提供了标准化的方法来为用户登录提供认证,是用户认证功能的常用解决方案。 5. **版权信息**:项目的版权声明表明了Sportscafe 2015是版权所有者,这表明项目或其相关内容最早发布于2015年或之前。这可能表明该API背后有商业实体的支持或授权使用。 从【压缩包子文件的文件名称列表】中我们可以了解到,该文件的版本控制仓库使用的是“master”分支。在Git版本控制系统中,“master”分支通常用于存放当前可部署的稳定版本代码。在“master”分支上进行的更改通常都是经过测试且准备发布到生产环境的。 综上所述,我们可以知道体育咖啡馆项目是一个利用现代JavaScript技术栈搭建的后端服务。它包含了处理HTTP请求的Express框架、连接MongoDB数据库的Mongoose库和实现用户身份验证的Passport.js中间件。该项目可用于构建提供体育信息、咖啡馆菜单信息、预约服务等的Web应用或API服务,这为体育咖啡馆的营销、用户体验和数据管理提供了可能。 考虑到文档资料的提及,该项目的安装和API文档应该包含在项目资料中,可能在项目的README文件或其他说明文档中。对于项目的使用者或者开发者而言,这部分文档非常重要,因为它们可以提供详细的信息和指导,帮助用户快速部署和使用该API。 总结来说,这是一套针对体育咖啡馆相关业务的后端解决方案,它使用了流行的开源技术栈,可以灵活地应用于体育咖啡馆的网络服务中,如信息发布、在线预约、会员管理等。
recommend-type

【LNR优化与用户体验】:一文看透互操作优化如何提升用户感知

# 摘要 LNR(邻区关系优化)是提升移动通信网络性能与用户体验的关键技术。本文系统阐述了LNR优化的基本概念、理论基础与技术实现路径,构建了包括信道质量评估、干扰建模及关键性能指标分析在内的数学模型,并探讨了参数配置、邻区管理及AI与大数据在LNR优化中的应用。通过量化用户感知指标,分析了LNR优化对视频流畅度、页面加载速度及切换性能的实际改善效果,并结合典型场景验证了其优化成效。本文进一步展望了LNR技术在5G及未来网络中的发展趋势与技术挑战。
recommend-type

Java1.8 的编程语言、使用场景、版本号、厂商、是否开源、发行日期、终止日期、可替代产品、推荐产品是什么

以下是关于 **Java 1.8** 的全面信息汇总,涵盖其编程语言特性、使用场景、版本号、厂商、是否开源、发行日期、终止支持日期、可替代产品和推荐产品。 --- ## ✅ Java 1.8 综合信息表 | 项目 | 内容 | |------|------| | **编程语言** | Java | | **版本号** | Java 1.8(也称为 Java 8) | | **厂商** | Oracle、Adoptium、Amazon(Corretto)、Azul(Zulu)、Red Hat、IBM 等 | | **是否开源** | ✅ 是(OpenJDK 1.8 是开源的,Oracle
recommend-type

Java开发的教区牧民支持系统介绍

根据给定文件信息,下面将详细阐述相关知识点: ### 标题知识点 #### catecumenus-java: 教区牧民支持系统 - **Java技术栈应用**:标题提到的“catecumenus-java”表明这是一个使用Java语言开发的系统。Java是目前最流行的编程语言之一,广泛应用于企业级应用、Web开发、移动应用等,尤其是在需要跨平台运行的应用中。Java被设计为具有尽可能少的实现依赖,所以它可以在多种处理器上运行。 - **教区牧民支持系统**:从标题来看,这个系统可能面向的是教会管理或教区管理,用来支持牧民(教会领导者或牧师)的日常管理工作。具体功能可能包括教友信息管理、教区活动安排、宗教教育资料库、财务管理、教堂资源调配等。 ### 描述知识点 #### 儿茶类 - **儿茶素(Catechin)**:描述中提到的“儿茶类”可能与“catecumenus”(新信徒、教徒)有关联,暗示这个系统可能与教会或宗教教育相关。儿茶素是一类天然的多酚类化合物,常见于茶、巧克力等植物中,具有抗氧化、抗炎等多种生物活性,但在系统标题中可能并无直接关联。 - **系统版本号**:“0.0.1”表示这是一个非常初期的版本,意味着该系统可能刚刚开始开发,功能尚不完善。 ### 标签知识点 #### Java - **Java语言特点**:标签中明确提到了“Java”,这暗示了整个系统都是用Java编程语言开发的。Java的特点包括面向对象、跨平台(即一次编写,到处运行)、安全性、多线程处理能力等。系统使用Java进行开发,可能看重了这些特点,尤其是在构建可扩展、稳定的后台服务。 - **Java应用领域**:Java广泛应用于企业级应用开发中,包括Web应用程序、大型系统后台、桌面应用以及移动应用(Android)。所以,此系统可能也会涉及这些技术层面。 ### 压缩包子文件的文件名称列表知识点 #### catecumenus-java-master - **Git项目结构**:文件名称中的“master”表明了这是Git版本控制系统中的一个主分支。在Git中,“master”分支通常被用作项目的主干,是默认的开发分支,所有开发工作都是基于此分支进行的。 - **项目目录结构**:在Git项目中,“catecumenus-java”文件夹应该包含了系统的源代码、资源文件、构建脚本、文档等。文件夹可能包含各种子文件夹和文件,比如src目录存放Java源代码,lib目录存放相关依赖库,以及可能的build.xml文件用于构建过程(如Ant或Maven构建脚本)。 ### 结合以上信息的知识点整合 综合以上信息,我们可以推断“catecumenus-java: 教区牧民支持系统”是一个使用Java语言开发的系统,可能正处于初级开发阶段。这个系统可能是为了支持教会内部管理,提供信息管理、资源调度等功能。其使用Java语言的目的可能是希望利用Java的多线程处理能力、跨平台特性和强大的企业级应用支持能力,以实现一个稳定和可扩展的系统。项目结构遵循了Git版本控制的规范,并且可能采用了模块化的开发方式,各个功能模块的代码和资源文件都有序地组织在不同的子文件夹内。 该系统可能采取敏捷开发模式,随着版本号的增加,系统功能将逐步完善和丰富。由于是面向教会的内部支持系统,对系统的用户界面友好性、安全性和数据保护可能会有较高的要求。此外,考虑到宗教性质的敏感性,系统的开发和使用可能还需要遵守特定的隐私和法律法规。
recommend-type

LNR切换成功率提升秘籍:参数配置到网络策略的全面指南

# 摘要 LNR(LTE to NR)切换技术是5G网络部署中的关键环节,直接影