index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <template>
  2. <FormDialog
  3. :width="width"
  4. class="parameter_setting_form_dialog"
  5. v-if="formVisible"
  6. :defaultFormData="{}"
  7. :visible="formVisible"
  8. :title="formTitle"
  9. :submitLoading="formSubmitLoading"
  10. @close="HandleFormClose"
  11. @confirm="HandleFormConfirm"
  12. >
  13. <template #form_pre>
  14. <div class="dictionary_box">
  15. <Table
  16. v-model:tableRef="configTableDom"
  17. :tableColumns="finalTableColumns"
  18. :tableData="tableData"
  19. :hidePagination="true">
  20. <template #moveable="{row,currentIndex}">
  21. <TableRowDragUpDown
  22. :startIndex="startDragIndex()"
  23. :endIndex="endDragIndex(tableData)"
  24. v-if="(currentIndex - 1) >= startDragIndex() && (currentIndex - 1) <= endDragIndex(tableData)"
  25. :currentIndex="currentIndex"
  26. :tableRow="row" :defaultTableData="tableData"
  27. @moveChange="HandleRowMove"/>
  28. </template>
  29. <template #num="{row,currentIndex}">
  30. {{ currentIndex }}
  31. </template>
  32. <template v-slot:[middleColumnItm?.name]="{row,currentIndex}">
  33. <slot :name="[middleColumnItm?.name]" :row="row" :currentIndex="currentIndex">
  34. <el-input v-model="row[middleColumnItm.name]" v-if="row.tmpId" placeholder="请输入内容" />
  35. <span v-else>{{ row?.[middleColumnItm?.name] }}</span>
  36. </slot>
  37. </template>
  38. <template #operation="{row,currentIndex}">
  39. <div class="table_row_option">
  40. <!-- row.defaultStatus ? -->
  41. <span :class="disabledEditRowScene ?
  42. (disabledEditRowScene(row,currentIndex,tableData) ? 'editor_disable' : 'el_button_editor') :
  43. 'el_button_editor'"
  44. @click="disabledEditRowScene ?
  45. (disabledEditRowScene(row,currentIndex,tableData) ? '' : HandleConfigEditOrSave(row,currentIndex)) :
  46. HandleConfigEditOrSave(row,currentIndex)"
  47. >{{row.tmpId ? '保存' : '编辑'}}</span>
  48. <span v-if="row.tmpId" class="el_button_editor" @click="HandleConfigCancel(row)">取消</span>
  49. <!-- row.defaultStatus -->
  50. <span v-else
  51. :class="disabledDeleteRowScene ?
  52. (disabledDeleteRowScene(row,currentIndex,tableData) ? 'editor_disable' : 'el_button_delete') :
  53. 'el_button_delete'"
  54. @click="disabledDeleteRowScene ?
  55. (disabledDeleteRowScene(row,currentIndex,tableData) ? '' : HandleConfigDelete(row)) :
  56. HandleConfigDelete(row)"
  57. >删除</span>
  58. </div>
  59. </template>
  60. </Table>
  61. <div class="add_option" @click="HandleAddConfig">
  62. <el-icon><CloseBold /></el-icon>
  63. <span>{{ addOperationName }}</span>
  64. </div>
  65. </div>
  66. </template>
  67. </FormDialog>
  68. <!-- 删除弹窗 -->
  69. <Dialog
  70. style="flex-grow: 0;"
  71. v-if="dialogVisible"
  72. :submitLoading="dialogSubmitLoading"
  73. :visible="dialogVisible"
  74. @confirm="HandleDialogConfirm"
  75. @close="HandleDialogClose">
  76. 是否确认删除?
  77. </Dialog>
  78. </template>
  79. <script setup lang="ts">
  80. import { ref,watch, nextTick } from 'vue';
  81. import {ElMessage} from 'element-plus'
  82. import FormDialog from '@/baseComponents/FormDialog.vue';
  83. import Dialog from '@/baseComponents/Dialog.vue';
  84. import TableRowDragUpDown from '@/baseComponents/TableRowDragUpDown.vue';
  85. import Table from '../Table.vue';
  86. import {tableColumns} from './table'
  87. interface MiddleColumnItm {
  88. name:string,
  89. label:string
  90. }
  91. interface TableItem {
  92. editFlag:string, // 存在 则 行 处于编辑-待保存状态 优先级最高 (存在 则 tmpId 必存在)
  93. tmpId:string, // 存在 则 行 处于新增-待保存状态
  94. [key:string]:string
  95. }
  96. const props = defineProps({
  97. startDragIndex:{
  98. type:Function,
  99. required:true
  100. },
  101. endDragIndex:{
  102. type:Function,
  103. required:true
  104. },
  105. width:{ //弹窗宽度
  106. type:String,
  107. default:'680px'
  108. },
  109. formTitle:{ //弹窗标题
  110. type:String,
  111. },
  112. middleColumnItm:{ //表列中间的动态列
  113. type:Object as ()=>MiddleColumnItm
  114. },
  115. defaultTableData:{
  116. type:Array,
  117. default:()=>[]
  118. },
  119. disabledDeleteRowScene:{ // 禁止删除行 的条件: true 禁止 false 允许
  120. type:Function
  121. },
  122. disabledEditRowScene:{ // 禁止编辑行 的条件 :true 禁止 false 允许
  123. type:Function
  124. },
  125. addOperationName:{
  126. type:String,
  127. default:'增加'
  128. }
  129. })
  130. const emit = defineEmits([
  131. 'editConfirm', // 执行到最后要添加 row.editFlag = '' ; row.tmpId = ''
  132. 'addConfirm', // 执行到最后要添加 row.tmpId = ''
  133. 'deleteConfirm',
  134. 'entiretyConfirm',
  135. 'tableDrag',
  136. ])
  137. /**
  138. * 1. 参数配置: 表单弹窗信息
  139. */
  140. const formVisible = defineModel('formVisible') //
  141. const deleteFlag = defineModel('deleteFlag') // 当下 删除弹窗确认 是否成功的标识 // success | fail | none
  142. const formDialogConfirmFlag = defineModel('formDialogConfirmFlag') //当下表单是否成功执行操作的标识 // success | fail | none
  143. const formSubmitLoading = ref(false)
  144. const finalTableColumns = ref([...tableColumns.value]) //最终渲染的表头
  145. const tableData = ref<TableItem[]>([]) //表数据
  146. const configTableDom = ref()
  147. // 处理表行的上下移动
  148. const HandleRowMove = (val)=>{
  149. // 移动前要保证 没有编辑中的数据
  150. // 整体点击确认前,要保证单条编辑已经全部完成
  151. let result = val.find(item=>{
  152. return (item.editFlag || item.tmpId)
  153. })
  154. if(result){
  155. ElMessage.warning('请先保存未保存的数据')
  156. return
  157. }
  158. emit('tableDrag',val)
  159. // tableData.value = val
  160. }
  161. // 处理参数配置的增删改查
  162. // 配置项添加
  163. const HandleAddConfig = async ()=>{
  164. if(props.middleColumnItm?.label === '跟进状态'){
  165. tableData.value.splice(tableData.value.length - 3,0,{
  166. // 临时id _ 待完善
  167. id:`tmp_${new Date().getTime()}_${tableData.value.length + 1}`,
  168. tmpId:(Number(tableData.value[tableData.value.length - 1]?.tmpId) || 0) + 1, //新增时候的临时id
  169. [props.middleColumnItm?.name]:'',
  170. defaultStatus:1, // defaultStatus: 0默认项 1非默认项 ,后续可能会用
  171. })
  172. }else{
  173. tableData.value.push({
  174. // 临时id _ 待完善
  175. id:`tmp_${new Date().getTime()}_${tableData.value.length + 1}`,
  176. tmpId:(Number(tableData.value[tableData.value.length - 1]?.tmpId) || 0) + 1, //新增时候的临时id
  177. [props.middleColumnItm?.name]:'',
  178. defaultStatus:1, // defaultStatus: 0默认项 1非默认项 ,后续可能会用
  179. })
  180. }
  181. await nextTick()
  182. // 新增元素后滚动到表格最底层
  183. configTableDom.value.setScrollTop(configTableDom.value.layout.table.refs.bodyWrapper.firstElementChild.firstElementChild.scrollHeight)
  184. }
  185. // 配置项保存 /编辑
  186. const HandleConfigEditOrSave = async (row,sortCode)=>{
  187. // 初次新增确认 或者 准备变成编辑状态
  188. if(!row.editFlag){
  189. if(row.tmpId){
  190. // 初次新增确认 调用新增接口
  191. if(!(row?.[props?.middleColumnItm?.name]) ){
  192. ElMessage.warning('请填写内容')
  193. return
  194. }
  195. let params = {
  196. [props.middleColumnItm.name]:row?.[props?.middleColumnItm?.name],
  197. }
  198. // 调用新增接口
  199. emit('addConfirm',row,params,sortCode)
  200. }else{
  201. // 准备变成编辑状态
  202. row.editFlag = 'edit'
  203. row.tmpId = row.id
  204. }
  205. }else{
  206. // 准备保存修改 调用修改接口
  207. if(!row?.[props?.middleColumnItm?.name]){
  208. ElMessage.warning('请填写类别内容')
  209. return
  210. }
  211. let params = {
  212. id:row.id,
  213. [props.middleColumnItm.name]:row?.[props?.middleColumnItm?.name],
  214. }
  215. emit('editConfirm',row,params,sortCode)
  216. }
  217. }
  218. // 取消配置项操作
  219. const HandleConfigCancel = (row)=>{
  220. if(!row.editFlag){
  221. // 不是已有的数据,要删除
  222. tableData.value = tableData.value.filter(item=>{
  223. return item.id !== row.id
  224. })
  225. }else{
  226. row.tmpId = ''
  227. row.editFlag = ''
  228. }
  229. }
  230. // 配置项删除
  231. const HandleConfigDelete = async (row)=>{
  232. dialogVisible.value = true
  233. currentEditRow.value = row
  234. }
  235. const HandleFormConfirm = ()=>{
  236. formSubmitLoading.value = true
  237. // 整体表单提交
  238. emit('entiretyConfirm',tableData.value)
  239. }
  240. const HandleFormClose = ()=>{
  241. formVisible.value = false
  242. formSubmitLoading.value = false
  243. }
  244. watch(()=>deleteFlag.value,(newVal)=>{
  245. if(newVal == 'success'){
  246. HandleDialogClose()
  247. dialogSubmitLoading.value = false
  248. deleteFlag.value = 'none'
  249. }else if(newVal == 'fail'){
  250. dialogSubmitLoading.value = false
  251. deleteFlag.value = 'none'
  252. }
  253. })
  254. watch(()=>formDialogConfirmFlag.value,(newVal)=>{
  255. if(newVal == 'success'){
  256. HandleFormClose()
  257. formDialogConfirmFlag.value = 'none'
  258. }else if(newVal == 'fail'){
  259. formSubmitLoading.value = false
  260. formDialogConfirmFlag.value = 'none'
  261. }
  262. })
  263. /**
  264. * 2. 参数配置: 删除弹窗 信息
  265. */
  266. const dialogVisible = ref(false)
  267. const dialogSubmitLoading = ref(false)
  268. const currentEditRow = ref()
  269. const HandleDialogClose = ()=>{
  270. dialogVisible.value = false
  271. currentEditRow.value = null
  272. }
  273. const HandleDialogConfirm = async ()=>{
  274. dialogSubmitLoading.value = true
  275. let row = currentEditRow.value
  276. emit('deleteConfirm',row)
  277. }
  278. // 动态变更最终表列
  279. watch(()=>props.middleColumnItm,(newVal)=>{
  280. if(newVal){
  281. finalTableColumns.value = tableColumns.value.map((item,index)=>{
  282. if(index == 2){
  283. return [
  284. {
  285. name:props.middleColumnItm?.name,
  286. label:props.middleColumnItm?.label,
  287. width:'350',
  288. fixed:'',
  289. align:'left',
  290. minWdth:'',
  291. custom:true,
  292. },
  293. item
  294. ]
  295. }
  296. return item
  297. }).flat()
  298. }
  299. },{immediate:true,deep:true})
  300. // 动态变更表数据
  301. watch(()=>props.defaultTableData,(val)=>{
  302. tableData.value = JSON.parse(JSON.stringify(val)) || []
  303. },{immediate:true,deep:true})
  304. </script>
  305. <style lang="scss" scoped>
  306. .dictionary_box{
  307. width:100%;
  308. height: 480px;
  309. overflow: auto;
  310. display: flex;
  311. flex-direction: column;
  312. gap:10px;
  313. :deep(.page_table){
  314. overflow: auto;
  315. }
  316. .add_option{
  317. flex-shrink: 0;
  318. display: flex;
  319. justify-content: center;
  320. align-items: center;
  321. gap:8px;
  322. background-color: rgba(46, 100, 250,0.1);
  323. color:#2E64FA;
  324. padding:4px auto;
  325. text-align: center;
  326. border-radius: 6px;
  327. cursor: pointer;
  328. :deep(.el-icon){
  329. transform: rotate(45deg);
  330. }
  331. }
  332. }
  333. .parameter_setting_form_dialog{
  334. flex-grow: 0 !important;
  335. :deep(.el-dialog__body){
  336. min-height: 500px !important;
  337. max-height: 500px !important;
  338. padding:20px !important
  339. }
  340. }
  341. </style>