ListItem.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. <template>
  2. <div class="data-fill-list-box">
  3. <el-collapse v-model="activeName" accordion>
  4. <template v-for="(item,index) in listDatas" :key="item?.pkeyId">
  5. <el-collapse-item :name="`item-${index}-${item?.pkeyId}`" :disabled="item?.isBussShow == 2" :id="`item-${index}-${item?.pkeyId}`">
  6. <template #title>
  7. <div class="hc-collapse-item-header">
  8. <div class="text-lg truncate item-title">{{item.deptName}}</div>
  9. <div class="hc-extra-text-box">
  10. <el-tooltip :content="btn_del_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_del_table?.textInfo" v-if="btn_del_table && item?.isCopeTab === 2">
  11. <el-button type="danger" plain :disabled="item?.isBussShow == 2" @click.stop="delClick(item,index)">删除本表</el-button>
  12. </el-tooltip>
  13. <el-tooltip :content="btn_copy_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_copy_table?.textInfo" v-if="btn_copy_table">
  14. <el-button type="primary" plain :disabled="item?.isBussShow == 2" @click.stop="copyClick(item,index)">复制本表</el-button>
  15. </el-tooltip>
  16. <el-tooltip :content="btn_hide_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_hide_table?.textInfo" v-if="btn_hide_table">
  17. <el-button type="primary" plain @click.stop="hideClick(item,index)">
  18. <template v-if="item?.isBussShow == 1">隐藏本表</template>
  19. <template v-else>显示本表</template>
  20. </el-button>
  21. </el-tooltip>
  22. <el-tooltip :content="btn_pre_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_pre_table?.textInfo" v-if="btn_pre_table">
  23. <el-button type="primary" plain :disabled="item?.isBussShow == 2 || item?.isTabPdf === 1" @click.stop="previewClick(item,index)">预览</el-button>
  24. </el-tooltip>
  25. <el-tooltip :content="btn_up_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_up_table?.textInfo" v-if="btn_up_table">
  26. <el-button :type="item?.tabFileType === 2?'success':'primary'" plain :disabled="item?.isBussShow == 2" @click.stop="uploadClick(item,index)">
  27. <template v-if="item?.tabFileType === 2">已上传</template>
  28. <template v-else>上传</template>
  29. </el-button>
  30. </el-tooltip>
  31. </div>
  32. </div>
  33. </template>
  34. <div class="data-fill-list-item-content">
  35. <div class="data-fill-table-form-box">
  36. <div :id="`table-form-${item?.pkeyId}`"/>
  37. <div class="hc-no-table-form" v-if="item?.isTableForm === false">
  38. <div class="table-form-no">
  39. <img :src="notableform" alt=""/>
  40. <div class="desc">暂无表单数据</div>
  41. </div>
  42. </div>
  43. </div>
  44. <div class="data-fill-table-tip-box">
  45. <div class="text-orange tip-title">
  46. <HcIcon name="error" fill ui="text-2xl"/>
  47. <span class="ml-1">提示</span>
  48. </div>
  49. <div class="text-gray-400 tip-item">1、灰色框代表可通过系统识别计算,公式自动引用,可通过公式计算少量数据,(表头数据及简单),也可只填写白色框数据</div>
  50. <div class="text-gray-400 tip-item">2、系统支持键盘中,shift + tab键向上一个填报框切换,tab向下一个填报框切换。暂不支持上下按键切换输入框</div>
  51. <div class="table-tip-foot">
  52. <div class="tip-left-btn">
  53. <el-tooltip :content="btn_impo_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_impo_table?.textInfo" v-if="btn_impo_table">
  54. <div class="text-gray-400 dow-text">
  55. <HcIcon name="publish" ui="text-lg"/>
  56. <span class="ml-1">导入列表数据</span>
  57. </div>
  58. </el-tooltip>
  59. <el-tooltip :content="btn_down_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_down_table?.textInfo" v-if="btn_down_table">
  60. <div class="text-main dow-text">
  61. <HcIcon name="file_download" ui="text-lg"/>
  62. <span class="ml-1">下载导入模板</span>
  63. </div>
  64. </el-tooltip>
  65. </div>
  66. <div class="tip-right-btn">
  67. <el-tooltip :content="btn_save_table?.textInfo" placement="top" :disabled="!bubbleVal || !btn_save_table?.textInfo" v-if="btn_save_table">
  68. <el-button type="primary" hc-btn size="large" @click="tableFormSaveClick(item,index)">保存</el-button>
  69. </el-tooltip>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. </el-collapse-item>
  75. </template>
  76. </el-collapse>
  77. </div>
  78. </template>
  79. <script setup>
  80. import {ref,watch,nextTick} from "vue";
  81. import {createApp} from "vue/dist/vue.esm-bundler.js";
  82. import {useAppStore} from "~src/store/index";
  83. import notableform from '~src/assets/view/notableform.svg';
  84. import wbsApi from "~api/data-fill/wbs"
  85. import {isString} from "vue-utils-plus"
  86. import {ElInput,ElDatePicker} from 'element-plus'
  87. //初始
  88. const useAppState = useAppStore()
  89. const props = defineProps({
  90. datas: {
  91. type: Array,
  92. default: () => ([])
  93. },
  94. projectId: {
  95. type: [String,Number],
  96. default: ''
  97. },
  98. contractId: {
  99. type: [String,Number],
  100. default: ''
  101. },
  102. classify: {
  103. type: [String,Number],
  104. default: ''
  105. },
  106. })
  107. //按钮气泡开关
  108. const bubbleVal = ref(useAppState.getBubble);
  109. const projectId = ref(props.projectId)
  110. const contractId = ref(props.contractId)
  111. const listDatas = ref(props.datas)
  112. const classify = ref(props.classify)
  113. //监听
  114. watch(() => [
  115. props.datas,
  116. props.projectId,
  117. props.contractId,
  118. useAppState.getBubble,
  119. props.classify,
  120. ], ([datas, pid, cid, Bubble,classifyVal]) => {
  121. listDatas.value = datas
  122. bubbleVal.value = Bubble
  123. projectId.value = pid
  124. contractId.value = cid
  125. classify.value = classifyVal
  126. //setFormDataNum(datas)
  127. })
  128. //获取气泡数据
  129. const getButtonsVal = (value) => {
  130. return useAppState.getButtonsVal(value)
  131. }
  132. const btn_copy_table = ref(getButtonsVal('wbs_copy_table')) //复制本表
  133. const btn_hide_table = ref(getButtonsVal('wbs_hide_table')) //隐藏本表
  134. const btn_pre_table = ref(getButtonsVal('wbs_preview_table')) //预览本表
  135. const btn_up_table = ref(getButtonsVal('wbs_upload_table')) //上传
  136. const btn_del_table = ref(getButtonsVal('wbs_del_table')) //删除
  137. const btn_down_table = ref(getButtonsVal('wbs_download_table')) //下载导入模板
  138. const btn_impo_table = ref(getButtonsVal('wbs_import_table')) //导入列表数据
  139. const btn_save_table = ref(getButtonsVal('wbs_save_table'))//保存本表
  140. //事件
  141. const emit = defineEmits(['uploadTap','itemTap','copyTap','hideTap','previewTap','offsetTop'])
  142. nextTick(() => {
  143. //setFormDataNum(props.datas)
  144. })
  145. const activeName = ref('')
  146. //展开事件
  147. const updateExpandedNames = (name) => {
  148. const keys = name.length > 0 ? name[0]:'';
  149. const names = name.length > 0 ? name[0].split('-'): []
  150. if (names.length > 0) {
  151. getOffsetTop(keys)
  152. const index = names[1]
  153. const item = listDatas.value[index]
  154. if (!item.isTableFormRender) {
  155. getExcelHtml(item,index)
  156. }
  157. } else {
  158. emit('offsetTop', {offsetTop: 0, dom: null})
  159. }
  160. }
  161. //获取模板标签数据
  162. const getExcelHtml = (item,index) => {
  163. if (item.pkeyId !== 0) {
  164. wbsApi.getExcelHtml({
  165. pkeyId: item.pkeyId + ''
  166. }).then(({data}) => {
  167. const res = isString(data.data)?data.data:''
  168. if (res) {
  169. item.isTableForm = true
  170. HTableForm(res, item, index)
  171. } else {
  172. item.isTableForm = false
  173. window?.$message?.warning('清表内容是空的')
  174. }
  175. }).catch(() => {
  176. item.isTableForm = false
  177. })
  178. } else {
  179. item.isTableForm = false
  180. window?.$message?.warning('pkeyId为空')
  181. }
  182. }
  183. //渲染模板标签
  184. const formData = ref([])
  185. //设置表单对象的数量
  186. const setFormDataNum = (datas) => {
  187. if(datas.length > 0) {
  188. datas.forEach(item => {
  189. formData.value.push({
  190. projectId: projectId.value,
  191. contractId: contractId.value
  192. })
  193. })
  194. }
  195. }
  196. //渲染表格表单
  197. const HTableForm = (templateData,item,index) => {
  198. const app = createApp({
  199. data() {
  200. return {
  201. formData: formData.value[index],
  202. }
  203. },
  204. template: templateData,
  205. components: {ElInput,ElDatePicker},
  206. methods: {
  207. RightClick(a,b,c,d,e,f,event) {
  208. event.preventDefault();
  209. const key = event?.target?.getAttribute('keyname') || ''
  210. onRightClick(event,key,index)
  211. },
  212. getInformation(a,b,c) {
  213. },
  214. }
  215. })
  216. app.mount(`#table-form-${item.pkeyId}`)
  217. item.isTableFormRender = true
  218. }
  219. //树的菜单相关变量
  220. const tableFormMenuShow = ref(false)
  221. const tableFormItemNode = ref({})
  222. const tableFormMenuX = ref(0);
  223. const tableFormMenuY = ref(0);
  224. //树的菜单数据
  225. const tableFormMenu = ref([
  226. {label: '插入设计值/频率', key: "IDVF"},
  227. {label: '插入特殊字符', key: "special"},
  228. {label: '关联试验数据', key: "CTD"},
  229. ])
  230. //鼠标右键事件
  231. const onRightClick = (event,key,index) => {
  232. //取光标位置
  233. let specialDom = document.getElementById(key + "");
  234. let startPos = specialDom?.selectionStart, endPos = specialDom?.selectionEnd;
  235. //存储临时信息
  236. tableFormItemNode.value = {key, index, startPos, endPos}
  237. //取坐标
  238. nextTick().then(() => {
  239. tableFormMenuShow.value = true;
  240. tableFormMenuX.value = event.clientX;
  241. tableFormMenuY.value = event.clientY;
  242. });
  243. }
  244. //鼠标右键菜单被点击
  245. const handleMenuSelect = (key) => {
  246. //const item = tableFormItemNode.value;
  247. tableFormMenuShow.value = false;
  248. if (key === 'IDVF') {
  249. IDVFModal.value = true
  250. } else if (key === 'special') {
  251. specialModalShow()
  252. } else if (key === 'CTD') {
  253. CTDModal.value = true
  254. }
  255. }
  256. const onClickoutside = () => {
  257. tableFormItemNode.value = {};
  258. tableFormMenuShow.value = false;
  259. }
  260. //单个保存
  261. const tableFormSaveClick = (item,index) => {
  262. wbsApi.saveExcelBussData({
  263. pkeyId: item.pkeyId,
  264. classify: classify.value,
  265. ...formData.value[index]
  266. }).then(({data}) => {
  267. if(data.code === 200) {
  268. window?.$message?.success('保存成功')
  269. } else {
  270. window?.$message?.warning(data.msg || '保存失败')
  271. }
  272. })
  273. }
  274. //删除本表
  275. const delClick = (item,index) => {
  276. wbsApi.removeBussTabInfo({
  277. pkeyid: item.pkeyId,
  278. classify: classify.value,
  279. }).then(({data}) => {
  280. if(data.code === 200) {
  281. window?.$message?.success('操作成功')
  282. emit('uploadData')
  283. emit('delTap', {item,index})
  284. } else {
  285. window?.$message?.warning(data.msg || '操作失败')
  286. }
  287. })
  288. }
  289. //复制本表
  290. const copyClick = (item,index) => {
  291. emit('copyTap', {item,index})
  292. }
  293. //隐藏本表
  294. const hideClick = (item,index) => {
  295. emit('hideTap', {item,index})
  296. }
  297. //预览
  298. const previewClick = (item,index) => {
  299. emit('previewTap', {item,index})
  300. }
  301. //上传被点击
  302. const uploadClick = (item,index) => {
  303. emit('uploadTap', {item,index})
  304. }
  305. //上传被点击
  306. const getOffsetTop = (key) => {
  307. const dom = document.getElementById(key)
  308. emit('offsetTop', dom.offsetTop)
  309. }
  310. //插入设计值/频率
  311. const IDVFModal = ref(false)
  312. const formIDVFRef = ref(null)
  313. const formIDVFModel = ref({
  314. designValue: '', frequency: ''
  315. })
  316. const formIDVFRules = {
  317. designValue: {
  318. required: true,
  319. trigger: ["blur", "input"],
  320. message: "请输入设计值"
  321. },
  322. frequency: {
  323. required: true,
  324. trigger: ["blur", "input"],
  325. message: "请输入频率"
  326. }
  327. }
  328. //插入特殊字符
  329. const specialModal = ref(false)
  330. const specialCharacters = ref([
  331. '&#57344;', "&#57345;", "&#57346;", "&#57347;", '&#8804;', '&#8805;', '&#8451;',
  332. '&#9312;', '&#9313;', '&#9314;', '&#9315;', '&#9316;', '&#9317;', '&#9318;', '&#9319;', '&#9320;', '&#9321;', '&#9322;', '&#9323;',
  333. '&#9324;', '&#9325;', '&#9326;', '&#9327;', '&#9328;', '&#9329;', '&#9330;', '&#9331;',
  334. "&#8544;", "&#8545;", "&#8546;", "&#8547;", "&#8548;", "&#8549;", "&#8550;", "&#8551;", "&#8552;", "&#8553;", "&#8554;", "&#8555;","K̅"
  335. ])
  336. const specialRef = ref(null)
  337. const specialValue = ref('')
  338. const specialModalShow = () => {
  339. specialValue.value = ''
  340. specialModal.value = true
  341. nextTick(() => {
  342. specialRef.value?.focus();
  343. })
  344. }
  345. const specialClick = (event) => {
  346. inputInsertValue("specialId", event.target.innerText)
  347. }
  348. //输入框插入内容
  349. const inputInsertValue = (Id,val) => {
  350. let specialDom = document.getElementById(Id + "").getElementsByTagName('input')[0];
  351. let startPos = specialDom?.selectionStart, endPos = specialDom?.selectionEnd;
  352. if (startPos || startPos === 0) {
  353. let specialVal = specialValue.value;
  354. specialValue.value = specialVal.substring(0, startPos) + val + specialVal.substring(endPos, specialVal.length)
  355. specialDom?.focus();
  356. //设置光标位置
  357. let posVal = startPos + val.length;
  358. nextTick(() => {
  359. specialDom?.setSelectionRange(posVal,posVal)
  360. })
  361. } else {
  362. specialValue.value += val;
  363. specialDom?.focus();
  364. }
  365. }
  366. //插入内容
  367. const inputPosInsert = (dom,startPos,endPos,special,value) => {
  368. let val = value || '', specialVal = special || ''
  369. console.log(startPos,endPos)
  370. if (startPos || startPos === 0) {
  371. val = val.substring(0, startPos) + specialVal + val.substring(endPos, val.length)
  372. dom?.focus();
  373. //设置光标位置
  374. let posVal = startPos + specialVal.length;
  375. nextTick(() => {
  376. dom?.setSelectionRange(posVal,posVal)
  377. })
  378. } else {
  379. val += specialVal;
  380. dom?.focus();
  381. }
  382. return val
  383. }
  384. //确认插入
  385. const specialNodeClick = () => {
  386. const item = tableFormItemNode.value;
  387. const form = formData.value[item.index]
  388. let keyDom = document.getElementById(item.key);
  389. const res = inputPosInsert(keyDom,item.startPos,item.endPos,specialValue.value,form[item.key])
  390. formData.value[item.index][item.key] = res
  391. specialModal.value = false
  392. }
  393. //关联试验数据
  394. const CTDModal = ref(false)
  395. </script>
  396. <style lang="scss" scoped>
  397. .data-fill-list-box {
  398. position: relative;
  399. .hc-collapse-item-header {
  400. flex: 1;
  401. position: relative;
  402. margin-left: 46px;
  403. display: flex;
  404. align-items: center;
  405. .item-title {
  406. flex: 1;
  407. position: relative;
  408. user-select: none;
  409. color: #50545E;
  410. font-size: 16px;
  411. font-weight: 400;
  412. cursor: pointer;
  413. }
  414. .hc-extra-text-box {
  415. position: relative;
  416. padding-right: 24px;
  417. }
  418. }
  419. .data-fill-list-item-content {
  420. position: relative;
  421. display: flex;
  422. height: calc(100vh - 386px);
  423. .data-fill-table-form-box {
  424. position: relative;
  425. padding: 24px 20px;
  426. height: 100%;
  427. overflow: auto;
  428. flex: 1;
  429. .hc-no-table-form {
  430. position: relative;
  431. height: 100%;
  432. display: flex;
  433. justify-content: center;
  434. align-items: center;
  435. .table-form-no {
  436. position: relative;
  437. img {
  438. width: 350px;
  439. }
  440. .desc {
  441. text-align: center;
  442. font-size: 20px;
  443. color: #aaa;
  444. }
  445. }
  446. }
  447. }
  448. .data-fill-table-tip-box {
  449. width: 240px;
  450. position: relative;
  451. border-left: 1px solid #E9E9E9;
  452. padding: 20px 15px 80px;
  453. .tip-title {
  454. font-size: 16px;
  455. margin-bottom: 10px;
  456. display: flex;
  457. align-items: center;
  458. }
  459. .tip-item {
  460. margin-bottom: 20px;
  461. }
  462. .table-tip-foot {
  463. position: absolute;
  464. bottom: 15px;
  465. right: 0;
  466. left: 0;
  467. display: flex;
  468. align-items: center;
  469. padding: 0 15px;
  470. .tip-left-btn {
  471. flex: 1;
  472. .dow-text {
  473. cursor: pointer;
  474. display: flex;
  475. align-items: center;
  476. }
  477. }
  478. }
  479. }
  480. }
  481. }
  482. .special-box {
  483. position: relative;
  484. display: flex;
  485. justify-content: center;
  486. align-items: center;
  487. border: 1px solid #eee;
  488. border-radius: 3px;
  489. height: 60px;
  490. cursor: default;
  491. user-select: none;
  492. transition: color .3s, background-color .3s;
  493. &:hover {
  494. color: var(--el-color-primary);
  495. background-color: var(--el-color-primary-light-3);
  496. }
  497. .font-EUDC {
  498. font-size: 30px;
  499. cursor: default;
  500. }
  501. }
  502. </style>
  503. <style lang="scss">
  504. .data-fill-list-box {
  505. .el-collapse {
  506. --el-collapse-header-height: 60px;
  507. border: 0;
  508. .el-collapse-item {
  509. margin: 0 0 16px;
  510. background-color: #f1f5f8;
  511. border: 1px solid #E9E9E9;
  512. border-radius: 4px;
  513. }
  514. .el-collapse-item__header {
  515. background-color: transparent;
  516. font-weight: 400;
  517. border-bottom: 0;
  518. cursor: default;
  519. font-size: 14px;
  520. .el-collapse-item__arrow {
  521. position: absolute;
  522. color: #50545E;
  523. cursor: pointer;
  524. left: 20px;
  525. margin: 0;
  526. }
  527. }
  528. .el-collapse-item.is-active .el-collapse-item__header.is-active {
  529. background-color: #E7EEF4;
  530. }
  531. .el-collapse-item__wrap {
  532. background-color: transparent;
  533. border-bottom: 0;
  534. .el-collapse-item__content {
  535. position: relative;
  536. padding-bottom: 0;
  537. font-size: 14px;
  538. color: #50545E;
  539. line-height: initial;
  540. }
  541. }
  542. }
  543. //设置表单样式
  544. .data-fill-table-form-box {
  545. td {
  546. padding: 6px;
  547. font-family: "EUDC", 宋体, v-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
  548. .el-input {
  549. background-color: #ffffff !important;
  550. border-radius: 3px;
  551. .el-input__wrapper {
  552. background-color: inherit;
  553. }
  554. .el-input__wrapper.is-focus, .el-input__wrapper:hover {
  555. box-shadow: 0 0 0 1.5px var(--el-input-focus-border-color) inset;
  556. background-color: #eddac4;
  557. }
  558. //公式 #dcdcdc
  559. //焦点 #eddac4
  560. }
  561. }
  562. }
  563. }
  564. </style>