8
0

edit-formula.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <template>
  2. <hc-drawer v-model="isShow" ui="hc-project-list-edit-formula-drawer" to-id="hc-layout-box" is-close @close="drawerClose">
  3. <hc-card is-action-btn :scrollbar="isScrollBar">
  4. <div class="hc-project-list-edit-formula-card" :class="isScrollBar ? '' : 'is-no-scroll'">
  5. <!-- 顶部操作 -->
  6. <div class="hc-formula-card-box border-dashed-card hc-flex mb-14px h-58px">
  7. <div class="retain hc-flex h-full w-174px">
  8. <el-checkbox v-model="isRetain" size="large" />
  9. <span class="ml-5px text-14px">保留</span>
  10. <div class="relative ml-5px w-90px">
  11. <el-input-number v-model="retainNum" block :step="1" :min="0" :max="5" :disabled="!isRetain" controls-position="right" />
  12. </div>
  13. <span class="ml-5px text-14px">位</span>
  14. </div>
  15. <div class="range hc-flex h-full w-155px">
  16. <el-button :type="deviationRangeShow ? 'primary' : ''" @click="setDeviationRange">允许偏差值范围</el-button>
  17. </div>
  18. <div class="menu h-full flex-1">
  19. <hc-body padding="0">
  20. <el-menu :default-active="formulaMenuIndex" mode="horizontal" @select="handleFormulaMenu">
  21. <el-sub-menu v-for="(arr, key, index) in formulaMenuList" :key="key" :index="key">
  22. <template #title>{{ key }}</template>
  23. <el-menu-item v-for="(item, i) in arr" :key="i" :index="`${index + 1}-${i + 1}`">{{ item?.name }}</el-menu-item>
  24. </el-sub-menu>
  25. </el-menu>
  26. </hc-body>
  27. </div>
  28. <div class="hand hc-flex h-full w-100px">
  29. <el-button>手写模式</el-button>
  30. </div>
  31. </div>
  32. <!-- 函数公式 -->
  33. <div class="border-dashed-card hc-formula-card-math mb-14px">
  34. <div class="header hc-flex">
  35. <div class="name flex-1 text-14px">函数公式.</div>
  36. <div class="extra relative ml-24px">
  37. <el-button :type="isResetFun ? 'primary' : 'info'" size="small" @click="resetFunClick">重置函数</el-button>
  38. </div>
  39. </div>
  40. <div class="body relative">
  41. <template v-for="(item, index) in resultFormula" :key="index">
  42. <span class="element-class text-26px" :class="item.selected ? 'is-cur' : ''" @click="resultFormulaItem(item)">{{ item.name }}</span>
  43. </template>
  44. <span class="ml-10px mr-10px text-26px">=</span>
  45. <template v-for="(item, index) in processFormula" :key="index">
  46. <span class="element-class text-26px" :class="item.selected ? 'is-cur' : ''" @click="processFormulaItem(item)">{{ item.name }}</span>
  47. </template>
  48. </div>
  49. </div>
  50. <!-- 重置函数 -->
  51. <div v-if="isResetFun" class="hc-formula-reset-fun mb-14px">
  52. <hc-body split padding="0">
  53. <template #left>
  54. <hc-card class="reset-fun-left-card" scrollbar :loading="treeResetFunLoading">
  55. <template #header>
  56. <hc-search-input v-model="resetFunTree" @search="resetFunTreeSearch" />
  57. </template>
  58. <el-tree
  59. ref="treeResetFunLazyRef" :default-expanded-keys="treeResetFunLazyExpanded" node-key="id" :props="defaultProps"
  60. :expand-on-click-node="false" lazy highlight-current :load="treeResetFunLazyLoad" @node-click="treeResetFunLazyClick"
  61. />
  62. </hc-card>
  63. </template>
  64. <hc-card class="reset-fun-right-card">
  65. <template #header>
  66. <hc-search-input v-model="resetFunEle" placeholder="请输入你想搜索的元素字段" @search="resetFunEleSearch" />
  67. </template>
  68. <div class="body h-full">
  69. <div class="tag-box">
  70. <template v-for="(item, index) in resetFunEleData" :key="index">
  71. <el-button v-if="item.k" type="primary" plain size="small" @click="resetFunEleTagClick(item)">{{ item.name }}</el-button>
  72. <el-button v-else type="primary" plain size="small" @click="resetFunEleTagClick(item)">{{ item.eName }}</el-button>
  73. </template>
  74. </div>
  75. <div class="action-box hc-flex">
  76. <div class="left hc-flex flex-1">
  77. <div class="btn hc-flex-center" @click="resetFunText">
  78. <span class="text">输入值</span>
  79. </div>
  80. <div class="btn hc-flex-center" @click="resetFunBrackets('(')">
  81. <span class="symbol">(</span>
  82. </div>
  83. <div class="btn hc-flex-center" @click="resetFunBrackets(')')">
  84. <span class="symbol">)</span>
  85. </div>
  86. <div class="btn hc-flex-center" @click="resetFunOperator('+')">
  87. <i class="i-ri-add-line" />
  88. </div>
  89. <div class="btn hc-flex-center" @click="resetFunOperator('-')">
  90. <i class="i-ri-subtract-line" />
  91. </div>
  92. <div class="btn hc-flex-center" @click="resetFunOperator('*')">
  93. <i class="i-ri-close-line" />
  94. </div>
  95. <div class="btn hc-flex-center" @click="resetFunOperator('%')">
  96. <hc-icon name="divide" />
  97. </div>
  98. </div>
  99. <div class="right hc-flex">
  100. <el-tooltip content="删除元素" placement="top-end">
  101. <div class="btn hc-flex-center" @click="resetFunDel">
  102. <i class="i-ri-delete-back-2-line" />
  103. </div>
  104. </el-tooltip>
  105. <el-tooltip content="清空所有" placement="top-end">
  106. <div class="btn hc-flex-center" @click="resetFunClear">
  107. <i class="i-ri-delete-bin-3-line" />
  108. </div>
  109. </el-tooltip>
  110. </div>
  111. </div>
  112. <div class="input-box">
  113. <draggable v-model="selectEleFormula" item-key="index">
  114. <template #item="{ element }">
  115. <span class="element-class" :class="[`is-${element.type}`, element.selected ? 'is-cur' : '']" @click="selectEleFormulaItem(element)">{{ element.name }}</span>
  116. </template>
  117. </draggable>
  118. </div>
  119. </div>
  120. </hc-card>
  121. </hc-body>
  122. </div>
  123. </div>
  124. <template #action>
  125. <el-button @click="drawerClose">取消</el-button>
  126. <el-button type="primary" :loading="submitLoading" @click="submitClick">保存</el-button>
  127. </template>
  128. </hc-card>
  129. </hc-drawer>
  130. </template>
  131. <script setup>
  132. import { nextTick, ref, watch } from 'vue'
  133. import { useClick } from 'hc-vue3-ui'
  134. import { ElMessageBox } from 'element-plus'
  135. import {
  136. arrIndex, deepClone, getArrValue,
  137. getObjValue, getRandom, isNullES,
  138. } from 'js-fast-way'
  139. import draggable from 'vuedraggable'
  140. import mainApi from '~api/project/formula'
  141. import eleApi from '~api/project/element'
  142. import treeApi from '~api/wbs/tree'
  143. import priApi from '~api/wbs/private'
  144. const props = defineProps({
  145. data: {
  146. type: Object,
  147. default: () => ({}),
  148. },
  149. })
  150. //事件
  151. const emit = defineEmits(['close', 'finish'])
  152. //双向绑定
  153. const isShow = defineModel('modelValue', {
  154. default: false,
  155. })
  156. //监听数据
  157. const dataInfo = ref(props.data)
  158. watch(() => props.data, (data) => {
  159. dataInfo.value = getObjValue(data)
  160. }, { immediate: true, deep: true })
  161. //监听显示
  162. watch(isShow, (val) => {
  163. if (val) getDataApi()
  164. })
  165. //基础变量
  166. const symbolReg = /(\+|-|\*|\/)(.+)/ //加减乘除
  167. const operatorReg = /^\+|-|\*|%/ //加减乘除
  168. const startFCRegExp = /^FC\.([a-zA-Z\d]+)\(/ //匹配开始的FC.xxx(
  169. const isScrollBar = ref(true)
  170. //获取数据
  171. const getDataApi = async () => {
  172. console.log(dataInfo.value)
  173. getTypeMapApi().then()
  174. getWbsFormElementData().then()
  175. }
  176. //保留位数
  177. const isRetain = ref(false)
  178. const retainNum = ref(2)
  179. //允许偏差值范围
  180. const deviationRangeShow = ref(false)
  181. const setDeviationRange = () => {
  182. deviationRangeShow.value = !deviationRangeShow.value
  183. }
  184. //获取顶部菜单数据
  185. const formulaMenuMap = ref({})
  186. const formulaMenuIndex = ref(null)
  187. const formulaMenuList = ref({})
  188. const getTypeMapApi = async () => {
  189. const { data } = await mainApi.getTypeMap()
  190. const res = getObjValue(data)
  191. formulaMenuList.value = deepClone(res)
  192. //生成map,方便查找
  193. for (let key in res) {
  194. if (typeof(res[key]) === 'object') {
  195. res[key].forEach((formula)=>{
  196. formula.template = JSON.parse(formula.template)
  197. if (operatorReg.test(formula.template.ft)) {
  198. formulaMenuMap.value[formula.template.ft] = formula
  199. } else if (startFCRegExp.test(formula.template.ft)) {
  200. let regRes = formula.template.ft.match(startFCRegExp)
  201. formulaMenuMap.value[regRes[0]] = formula
  202. }
  203. })
  204. }
  205. }
  206. }
  207. //菜单被选择
  208. const handleFormulaMenu = (index, path) => {
  209. console.log(index, path)
  210. }
  211. //获取数据
  212. const resultFormula = ref([])
  213. const getWbsFormElementData = async () => {
  214. const { eleId } = getObjValue(dataInfo.value)
  215. resultFormula.value = []
  216. if (isNullES(eleId)) return
  217. const { data } = await eleApi.detail(eleId)
  218. const obj = getObjValue(data)
  219. resultFormula.value = [{
  220. type: 'Element',
  221. name: obj.eName,
  222. id: obj.id,
  223. selected: false,
  224. tableElementKey: obj.tableElementKey,
  225. children: [],
  226. }]
  227. }
  228. //被点击
  229. const resultFormulaItem = (item) => {
  230. //清除右边的选中
  231. processFormula.value.forEach((obj) => {
  232. obj.selected = false
  233. })
  234. //遍历左边的选中
  235. const arr = resultFormula.value
  236. for (let i = 0; i < arr.length; i++) {
  237. if (item.index === arr[i].index) {
  238. arr[i].selected = !arr[i].selected
  239. } else {
  240. arr[i].selected = false
  241. }
  242. }
  243. resultFormula.value = arr
  244. }
  245. //是否重置函数
  246. const isResetFun = ref(false)
  247. const resetFunClick = () => {
  248. isResetFun.value = !isResetFun.value
  249. isScrollBar.value = !isResetFun.value
  250. }
  251. //tree树配置
  252. const defaultProps = {
  253. label: 'title',
  254. children: 'children',
  255. isLeaf: (data) => {
  256. return !data.hasChildren
  257. },
  258. }
  259. //重置函数懒加载树
  260. const treeResetFunLazyRef = ref(null)
  261. const treeResetFunLazyExpanded = ref([])
  262. //获取重置函数懒加载树的数据
  263. const treeResetFunLoading = ref(false)
  264. const treeResetFunLazyLoad = async (node, resolve) => {
  265. const { level, data } = node
  266. let parentId = level !== 0 ? data.id : 12345678910
  267. const { eleType, node: dataNode, tableType } = getObjValue(dataInfo.value)
  268. const treeNode = getObjValue(dataNode)
  269. if (level === 0) treeResetFunLoading.value = true
  270. if (!eleType) {
  271. //获取接口数据
  272. const { data } = await priApi.tabTypeLazyTreeAll({
  273. parentId,
  274. current: 1,
  275. size: 99999,
  276. hasPartFormula: treeNode.hasPartFormula,
  277. })
  278. const res = getArrValue(data.records)
  279. treeResetFunLoading.value = false
  280. resolve(res)
  281. //处理返回的数据
  282. await nextTick()
  283. //处理展开
  284. try {
  285. const expandId = tableType ? Number(treeNode.tableType) - 1 : Number(treeNode.parentId) - 1
  286. if (!isNullES(expandId) && expandId >= 0) node.childNodes[expandId].expand()
  287. } catch { /* empty */ }
  288. //处理选中
  289. try {
  290. const paramsId = treeNode.initTableId
  291. if (!isNullES(paramsId)) {
  292. treeResetFunLazyRef.value?.setCurrentKey(paramsId)
  293. }
  294. } catch { /* empty */ }
  295. //获取节点详情
  296. await getNodeDetailApi(treeNode)
  297. } else if (eleType) {
  298. resolve([])
  299. }
  300. }
  301. //重置函数懒加载树点击
  302. const treeResetFunItemData = ref({})
  303. const treeResetFunNodeData = ref({})
  304. const treeResetFunLazyClick = async (data, node) => {
  305. treeResetFunItemData.value = data
  306. treeResetFunNodeData.value = node
  307. await getNodeDetailApi(data)
  308. }
  309. //重置函数树搜索
  310. const resetFunTree = ref('')
  311. const resetFunTreeSearch = () => {
  312. }
  313. //重置函数元素搜索
  314. const resetFunEle = ref('')
  315. const resetFunEleSearch = () => {
  316. }
  317. //获取节点详情
  318. const resetFunEleData = ref([])
  319. const getNodeDetailApi = async (item) => {
  320. await useClick(300)
  321. if (isNullES(item.initTableId)) {
  322. resetFunEleData.value = []
  323. return
  324. }
  325. if (item.hasChildren === false || item.isLinkTable === 2) {
  326. const { data } = await treeApi.getTableElments({
  327. id: item.initTableId,
  328. })
  329. resetFunEleData.value = getArrValue(data)
  330. } else {
  331. resetFunEleData.value = []
  332. }
  333. }
  334. //重置函数 元素字段被点击
  335. const selectEleFormula = ref([])
  336. const resetFunEleTagClick = (item) => {
  337. const arr = selectEleFormula.value
  338. let { type, name } = getObjValue(arr[arr.length - 1])
  339. if (type === 'Text') {
  340. window?.$message.warning('元素无法连续出现在输入值后面')
  341. return
  342. }
  343. if (type === 'Brackets' && name === ')') {
  344. window?.$message.warning('元素无法连续出现在右括号后面')
  345. return
  346. }
  347. if (arr.length === 0 || ['Operator', 'Brackets'].includes(type) || name === '(') {
  348. if (!isNullES(item.tableElementKey)) {
  349. selectEleFormula.value.push({
  350. type: 'Element',
  351. name: item.eName,
  352. id: item.id,
  353. selected: false,
  354. tableElementKey: item.tableElementKey,
  355. index: getRandom(5),
  356. children: [],
  357. })
  358. }
  359. } else {
  360. window?.$message.warning('当前操作不符合要求')
  361. }
  362. }
  363. //输入弹窗
  364. const promptMessageBox = async () => {
  365. return new Promise(resolve => {
  366. ElMessageBox.prompt('', '请输入值', {
  367. confirmButtonText: '确定',
  368. cancelButtonText: '取消',
  369. inputErrorMessage: '请输入内容',
  370. inputValidator: (value) => {
  371. return !isNullES(value)
  372. },
  373. }).then(({ value }) => {
  374. resolve(value)
  375. }).catch(() => {
  376. resolve()
  377. })
  378. })
  379. }
  380. //重置函数 输入值
  381. const resetFunText = async () => {
  382. const val = await promptMessageBox()
  383. if (isNullES(val)) return
  384. const arr = selectEleFormula.value
  385. if (arr.length > 0) {
  386. let { type, name } = getObjValue(arr[arr.length - 1])
  387. if (type === 'Element') {
  388. window?.$message.warning('输入值无法连续出现在元素后面')
  389. return
  390. }
  391. if (type === 'Text') {
  392. window?.$message.warning('输入值无法连续出现在输入值后面')
  393. return
  394. }
  395. if (type === 'Brackets' && name === ')') {
  396. window?.$message.warning('输入值无法连续出现在右括号后面')
  397. return
  398. }
  399. }
  400. selectEleFormula.value.push({
  401. type: 'Text',
  402. name: val,
  403. selected: false,
  404. index: getRandom(5),
  405. })
  406. }
  407. //重置函数 括号
  408. const resetFunBrackets = (val) => {
  409. selectEleFormula.value.push({
  410. type: 'Brackets',
  411. name: val,
  412. selected: false,
  413. index: getRandom(5),
  414. })
  415. }
  416. //重置函数 运算符
  417. const resetFunOperator = (val) => {
  418. const arr = selectEleFormula.value, map = formulaMenuMap.value
  419. if (arr.length <= 0) {
  420. window?.$message.warning('公式开头不能是运算符号')
  421. return
  422. }
  423. let { type, name } = getObjValue(arr[arr.length - 1])
  424. if (type === 'Operator') {
  425. window?.$message.warning('运算符号无法连续出现在运算符号后面')
  426. return
  427. }
  428. if (type === 'Brackets' && name === '(') {
  429. window?.$message.warning('运算符号无法连续出现在左括号后面')
  430. return
  431. }
  432. const obj = getObjValue(map[val])
  433. selectEleFormula.value.push({
  434. type: 'Operator',
  435. name: symbolReg.exec(obj.name)[1],
  436. selected: false,
  437. template: obj.template,
  438. index: getRandom(5),
  439. })
  440. }
  441. //重置函数 删除
  442. const resetFunDel = () => {
  443. const arr = deepClone(selectEleFormula.value)
  444. if (arr.length <= 0) {
  445. window?.$message.warning('请先添加相关元素')
  446. return
  447. }
  448. const index = arrIndex(arr, 'selected', true)
  449. if (index !== -1) {
  450. arr.splice(index, 1)
  451. selectEleFormula.value = arr
  452. } else {
  453. arr.splice(arr.length - 1, 1)
  454. selectEleFormula.value = arr
  455. }
  456. }
  457. //重置函数 清空
  458. const resetFunClear = () => {
  459. selectEleFormula.value = []
  460. }
  461. //被点击
  462. const selectEleFormulaItem = (item) => {
  463. const arr = selectEleFormula.value
  464. for (let i = 0; i < arr.length; i++) {
  465. if (item.index === arr[i].index) {
  466. arr[i].selected = !arr[i].selected
  467. } else {
  468. arr[i].selected = false
  469. }
  470. }
  471. selectEleFormula.value = arr
  472. }
  473. //保存
  474. const submitLoading = ref(false)
  475. const submitClick = async () => {
  476. //重置函数
  477. if (isResetFun.value) {
  478. setProcessFormula()
  479. }
  480. }
  481. //赋值给等号右边的数组
  482. const processFormula = ref([])
  483. const setProcessFormula = () => {
  484. const arr = selectEleFormula.value
  485. let leftNum = 0, rightNum = 0
  486. arr.forEach(({ type, name }) => {
  487. if (type === 'Brackets') {
  488. if (name === '(') {
  489. leftNum++
  490. } else if (name === ')') {
  491. rightNum++
  492. }
  493. }
  494. })
  495. if (leftNum !== rightNum) {
  496. window?.$message.warning('左右括号数量不相等,请先检查是否正确')
  497. return
  498. }
  499. processFormula.value = deepClone(arr)
  500. isResetFun.value = false
  501. isScrollBar.value = true
  502. }
  503. //右边被点击
  504. const processFormulaItem = (item) => {
  505. //清除左边边的选中
  506. resultFormula.value.forEach((obj) => {
  507. obj.selected = false
  508. })
  509. //遍历右边的选中
  510. const arr = processFormula.value
  511. for (let i = 0; i < arr.length; i++) {
  512. if (item.index === arr[i].index) {
  513. arr[i].selected = !arr[i].selected
  514. } else {
  515. arr[i].selected = false
  516. }
  517. }
  518. processFormula.value = arr
  519. }
  520. //关闭抽屉
  521. const drawerClose = () => {
  522. isShow.value = false
  523. isResetFun.value = false
  524. isScrollBar.value = true
  525. selectEleFormula.value = []
  526. emit('close')
  527. }
  528. </script>
  529. <style lang="scss">
  530. @import '~src/styles/view/project/edit-formula';
  531. </style>