index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. <template>
  2. <div class="page-container row-container">
  3. <section class="list-part-container" style="flex: 2">
  4. <!-- 搜索区 -->
  5. <el-form
  6. class="list-search-container"
  7. :model="queryParams"
  8. ref="queryRef"
  9. :inline="true"
  10. >
  11. <el-form-item label="工艺文件制作错误次数统计"> </el-form-item>
  12. <el-form-item>
  13. <el-button
  14. v-if="tabname == 'second'"
  15. type="success"
  16. @click="handleAdd"
  17. v-hasPermi="['business:processErrorSatistics:add']"
  18. >添加</el-button
  19. >
  20. <el-button v-if="tabname == 'second'" type="warning" @click="getList"
  21. >刷新</el-button
  22. >
  23. </el-form-item>
  24. <el-form-item label="年份:" v-if="tabname == 'second'">
  25. <el-date-picker
  26. v-model="queryParams.startYear"
  27. type="year"
  28. value-format="YYYY"
  29. :editable="false"
  30. :clearable="true"
  31. placeholder="请选择开始年份"
  32. style="width: 150px"
  33. />
  34. <span>To</span>
  35. <el-date-picker
  36. v-model="queryParams.endYear"
  37. type="year"
  38. value-format="YYYY"
  39. :editable="false"
  40. :clearable="true"
  41. placeholder="请选择结束年份"
  42. style="width: 150px"
  43. />
  44. </el-form-item>
  45. <el-form-item v-if="tabname == 'second'">
  46. <el-button type="info" icon="Search" @click="handleQuery"
  47. >搜索</el-button
  48. >
  49. </el-form-item>
  50. </el-form>
  51. <!-- 列表区 -->
  52. <el-tabs
  53. v-model="activeName"
  54. class="demo-tabs"
  55. @tab-click="handleTabClick"
  56. height="100%"
  57. style="margin-left: 10px; margin-right: 10px"
  58. >
  59. <el-tab-pane label="图表" name="first">
  60. <div
  61. style="
  62. position: relative;
  63. width: 100%;
  64. height: 100%;
  65. overflow-x: auto;
  66. margin-left: 20px;
  67. "
  68. >
  69. <div
  70. ref="echartDom"
  71. id="echartDom"
  72. :style="{ height: chartHeight, width: chartWidth }"
  73. />
  74. </div>
  75. </el-tab-pane>
  76. <el-tab-pane label="表格" name="second">
  77. <el-table
  78. ref="ErrorStatisticsTable"
  79. v-loading="loading"
  80. :data="errorStatisticsList"
  81. row-key="id"
  82. height="100%"
  83. border
  84. highlight-current-row
  85. >
  86. <el-table-column
  87. label="序号"
  88. type="index"
  89. width="50"
  90. align="center"
  91. ></el-table-column>
  92. <el-table-column label="年份" width="110" align="center">
  93. <template #default="scope">
  94. <el-date-picker
  95. v-if="scope.row.editStatus"
  96. v-model="scope.row.year"
  97. type="year"
  98. :clearable="false"
  99. placeholder="选择年"
  100. style="width: 100%"
  101. @change="(arg) => handleRowYearChange(arg, scope.row)"
  102. />
  103. <div v-else>{{ scope.row.year }}</div>
  104. </template>
  105. </el-table-column>
  106. <el-table-column label="季度" width="100" align="center">
  107. <template #default="scope">
  108. <el-select
  109. v-model="scope.row.quarter"
  110. v-if="scope.row.editStatus"
  111. >
  112. <el-option label="1" value="1" />
  113. <el-option label="2" value="2" />
  114. <el-option label="3" value="3" />
  115. <el-option label="4" value="4" />
  116. </el-select>
  117. <div v-else>{{ scope.row.quarter }}</div>
  118. </template>
  119. </el-table-column>
  120. <el-table-column label="日期" width="120" align="center">
  121. <template #default="scope">
  122. <el-date-picker
  123. v-if="scope.row.editStatus"
  124. v-model="scope.row.date"
  125. type="date"
  126. format="YYYY.M.D"
  127. :clearable="false"
  128. value-format="YYYY-MM-DD"
  129. placeholder="选择日期"
  130. style="width: 100%"
  131. />
  132. <div v-else>{{ scope.row.date }}</div>
  133. </template>
  134. </el-table-column>
  135. <el-table-column label="客户" align="center" width="150">
  136. <template #default="scope">
  137. <el-input
  138. v-model="scope.row.companyAlias"
  139. placeholder="请选择客户"
  140. readonly
  141. v-if="scope.row.editStatus"
  142. style="width: 120px; margin-left: 8px"
  143. >
  144. <template #append>
  145. <el-button
  146. icon="Search"
  147. @click="handleSelectComplany(scope.row, scope.$index)"
  148. /> </template
  149. ></el-input>
  150. <div v-else>{{ scope.row.companyAlias }}</div>
  151. </template>
  152. </el-table-column>
  153. <el-table-column label="规格" width="120" align="center">
  154. <template #default="scope">
  155. <div>{{ scope.row.specification }}</div>
  156. </template>
  157. </el-table-column>
  158. <el-table-column label="图号" width="120" align="center">
  159. <template #default="scope">
  160. <div>{{ scope.row.drawingNumber }}</div>
  161. </template>
  162. </el-table-column>
  163. <el-table-column label="类型" width="160" align="center">
  164. <template #default="scope">
  165. <el-select
  166. v-model="scope.row.type"
  167. placeholder="请选择客户类型"
  168. style="width: 160px"
  169. v-if="scope.row.editStatus"
  170. >
  171. <el-option
  172. v-for="dict in process_error_company_type"
  173. :key="dict.value"
  174. :label="dict.label"
  175. :value="dict.value"
  176. ></el-option>
  177. </el-select>
  178. <div v-else>
  179. <dict-tag
  180. :options="process_error_company_type"
  181. :value="scope.row.type"
  182. />
  183. </div>
  184. </template>
  185. </el-table-column>
  186. <el-table-column label="投诉内容" align="center">
  187. <template #default="scope">
  188. <el-input
  189. v-if="scope.row.editStatus"
  190. placeholder="请输入投诉内容"
  191. v-model.trim="scope.row.complaintContent"
  192. clearable
  193. />
  194. <div v-else>{{ scope.row.complaintContent }}</div>
  195. </template>
  196. </el-table-column>
  197. <el-table-column label="工艺错误次数" prop="errorQuantity" width="120" align="center">
  198. <template #default="scope">
  199. <el-input-number
  200. v-if="scope.row.editStatus"
  201. v-model="scope.row.errorQuantity"
  202. controls-position="right"
  203. style="width: 100px"
  204. @input="(val) => {handleUpdateErrorQuantity(val, scope.row,scope.$index)}"
  205. :min="0"
  206. />
  207. <div v-else>{{ scope.row.errorQuantity }}</div>
  208. </template>
  209. </el-table-column>
  210. <el-table-column label="季度目标(≤)" width="120" prop="quarterTarget" align="center">
  211. <template #default="scope">
  212. <el-input-number
  213. v-if="scope.row.editStatus"
  214. v-model="scope.row.quarterTarget"
  215. controls-position="right"
  216. style="width: 100px"
  217. @input="(val) => {handleUpdateQuarterTarget(val, scope.row,scope.$index)}"
  218. :min="0"
  219. />
  220. <div v-else>{{ scope.row.quarterTarget }}</div>
  221. </template>
  222. </el-table-column>
  223. <el-table-column label="目标完成情况" width="100" align="center" >
  224. <template #default="scope">
  225. <dict-tag :options="process_error_status" :value="scope.row.status" />
  226. </template>
  227. </el-table-column>
  228. <el-table-column label="操作" width="160" align="center">
  229. <template #default="scope">
  230. <el-button
  231. v-if="scope.row.editStatus"
  232. type="success"
  233. @click="handleSave(scope.row)"
  234. >保存</el-button
  235. >
  236. <el-button
  237. v-else
  238. type="primary"
  239. @click="() => (scope.row.editStatus = true)"
  240. v-hasPermi="['business:processErrorSatistics:edit']"
  241. >编辑</el-button
  242. >
  243. <el-button
  244. v-if="scope.row.editStatus"
  245. type="danger"
  246. @click="handleCancel(scope.row, scope.$index)"
  247. >取消</el-button
  248. >
  249. <el-button v-else type="danger" @click="handleDel(scope.row)"
  250. v-hasPermi="['business:processErrorSatistics:delete']"
  251. >删除</el-button
  252. >
  253. </template>
  254. </el-table-column>
  255. </el-table>
  256. </el-tab-pane>
  257. </el-tabs>
  258. </section>
  259. <company-dialog
  260. ref="companyDialogRef"
  261. :single-selected="handleSingleSelected"
  262. />
  263. </div>
  264. </template>
  265. <script setup name="processErrorStatistics">
  266. import { listProcessErrorStatistics,addProcessErrorStatistics,updateProcessErrorStatistics,delProcessErrorStatistics } from '@/api/business/processErrorStatistics'
  267. import { ref, onMounted, reactive, computed } from 'vue'
  268. import companyDialog from './DialogCompany.vue'
  269. import * as echarts from 'echarts'
  270. import { markRaw } from "vue"
  271. const { proxy } = getCurrentInstance();
  272. const echartInstance = ref(null)
  273. const echartDom = ref(null)
  274. const tabname = ref('first')
  275. const currentIndex = ref(null)
  276. const chartHeight = ref(`${window.innerHeight - 200}px`)
  277. const chartWidth = ref(`${window.innerWidth - 200}px`)
  278. /** 字典 */
  279. const { process_error_company_type } = proxy.useDict("process_error_company_type");
  280. const { process_error_status } = proxy.useDict("process_error_status");
  281. const data = reactive({
  282. queryParams: {
  283. startYear:null,
  284. endYear:null
  285. }
  286. })
  287. const loading = ref(false)
  288. const errorStatisticsList = ref([])
  289. const activeName = ref('first')
  290. const { queryParams } = toRefs(data)
  291. const getWindowInfo = () => {
  292. initGrophSize();
  293. };
  294. const debounce = (fn, delay) => {
  295. let timer;
  296. return function () {
  297. if (timer) {
  298. clearTimeout(timer);
  299. }
  300. timer = setTimeout(() => {
  301. fn();
  302. }, delay);
  303. }
  304. };
  305. const cancalDebounce = debounce(getWindowInfo, 500);
  306. onUnmounted(() => {
  307. //移除监听事件
  308. window.removeEventListener('resize', cancalDebounce);
  309. })
  310. //打开客户弹窗
  311. function handleSelectComplany(row,index) {
  312. currentIndex.value = index
  313. proxy.$refs.companyDialogRef.open();
  314. }
  315. //input事件
  316. function handleUpdateErrorQuantity(val,row,index) {
  317. const errorQuantity = val
  318. if(row.quarterTarget !=null && errorQuantity !=null) {
  319. if(row.quarterTarget >= errorQuantity) {
  320. errorStatisticsList.value[index].status = 0
  321. } else {
  322. errorStatisticsList.value[index].status = 1
  323. }
  324. }
  325. }
  326. function handleUpdateQuarterTarget(val,row,index) {
  327. const quarterTarget = val
  328. if(quarterTarget !=null && row.errorQuantity !=null) {
  329. if(quarterTarget >= row.errorQuantity) {
  330. errorStatisticsList.value[index].status = 0
  331. }else {
  332. errorStatisticsList.value[index].status = 1
  333. }
  334. }
  335. }
  336. //搜索事件
  337. function handleQuery() {
  338. getList()
  339. }
  340. //单选带回
  341. function handleSingleSelected(selection) {
  342. errorStatisticsList.value[currentIndex.value].companyAlias = selection.companyAlias
  343. errorStatisticsList.value[currentIndex.value].specification = selection.specification
  344. errorStatisticsList.value[currentIndex.value].drawingNumber = selection.drawingNumber
  345. errorStatisticsList.value[currentIndex.value].productionPlanDetailId = selection.id
  346. }
  347. function initGrophSize() {
  348. chartHeight.value = `${window.innerHeight - 200}px`
  349. chartWidth.value = errorStatisticsList.value.length * 200 + 'px'
  350. console.log(chartHeight.value, chartWidth.value)
  351. echartInstance.value.resize()
  352. }
  353. function handleDel(row) {
  354. proxy.$modal
  355. .confirm("确认删除吗?")
  356. .then(function () {
  357. delProcessErrorStatistics(row.id).then(res => {
  358. if (res.code === 200) {
  359. proxy.$modal.msgSuccess('删除成功')
  360. getList()
  361. }
  362. })
  363. })
  364. }
  365. function handleCancel(row,index) {
  366. errorStatisticsList.value[index].editStatus = false
  367. getList()
  368. }
  369. function handleSave(row) {
  370. console.log(row)
  371. //验证
  372. let flag = true
  373. if(row.year ==null) {
  374. flag = false
  375. proxy.$modal.msgError('年份不能为空')
  376. }
  377. if(row.quarter ==null) {
  378. flag = false
  379. proxy.$modal.msgError('季度不能为空')
  380. }
  381. if(row.date ==null) {
  382. flag = false
  383. proxy.$modal.msgError('日期不能为空')
  384. }
  385. if(row.companyAlias ==null) {
  386. flag = false
  387. proxy.$modal.msgError('客户不能为空')
  388. }
  389. if(row.errorQuantity ==null) {
  390. flag = false
  391. proxy.$modal.msgError('工艺错误次数不能为空')
  392. }
  393. if(row.quarterTarget ==null) {
  394. flag = false
  395. proxy.$modal.msgError('季度目标不能为空')
  396. }
  397. if(row.type ==null) {
  398. flag = false
  399. proxy.$modal.msgError('类型不能为空')
  400. }
  401. if(flag) {
  402. if (row.id != null) {
  403. updateProcessErrorStatistics(row).then(res => {
  404. if (res.code === 200) {
  405. proxy.$modal.msgSuccess('修改成功')
  406. getList()
  407. }
  408. })
  409. } else {
  410. addProcessErrorStatistics(row).then(res => {
  411. if (res.code === 200) {
  412. proxy.$modal.msgSuccess('添加成功')
  413. getList()
  414. }
  415. })
  416. }
  417. }
  418. }
  419. // 转换季度字符串为数字的映射
  420. const quarterToNumberMap = { '1': 1, '2': 2, '3': 3, '4': 4 };
  421. // 去重、转换和排序的方法
  422. function processMonths(list) {
  423. // 排序逻辑
  424. list.sort((a, b) => {
  425. const yearDiff = a.year - b.year; // 年份降序
  426. if (yearDiff !== 0) return yearDiff;
  427. // 季度升序
  428. const quarterDiff = quarterToNumberMap[a.quarter] - quarterToNumberMap[b.quarter];
  429. return quarterDiff;
  430. });
  431. return list
  432. };
  433. //获得日期数据
  434. function getYearData(data) {
  435. let yearData = []
  436. for(let i = 0; i < data.length; i++){
  437. let errorList = errorStatisticsList.value.filter(item => {
  438. return item.year == data[i].year +"" && data[i].quarter == item.quarter
  439. })
  440. yearData.push({yearQuarter:data[i].year + '年第' + data[i].quarter + '季度',
  441. totalErrorQuantity:errorList.reduce((acc, item) => acc + parseInt(item.errorQuantity), 0),
  442. totalQuarterTarget:errorList.reduce((acc, item) => acc + parseInt(item.quarterTarget), 0),
  443. })
  444. }
  445. return yearData
  446. }
  447. function initChartData() {
  448. //横坐标年份季度
  449. let list = processMonths(errorStatisticsList.value.map(item => ({
  450. year: parseInt(item.year),
  451. quarter: item.quarter
  452. })))
  453. let yearLabel = Array.from(new Set(list.map(item => item.year + '年第' + item.quarter + '季度')));
  454. const uniqueList = Array.from(new Set(list.map(item =>
  455. JSON.stringify({ year: item.year, quarter: item.quarter })
  456. )));
  457. // 去重后的数组包含字符串,需要转换回对象
  458. const dedupedObjects = uniqueList.map(str => JSON.parse(str));
  459. //展示数据
  460. let yearData = getYearData(dedupedObjects)
  461. //y轴对应数据
  462. let toData = yearData.map(e => e.totalErrorQuantity)
  463. updateCompleteChart(yearData, yearLabel, toData)
  464. }
  465. function updateCompleteChart(yearData, yearLabel, toData) {
  466. echartInstance.value.setOption({
  467. title: { text: '工艺文件制作错误次数统计' },
  468. tooltip: {
  469. trigger: 'axis',
  470. axisPointer: {
  471. type: 'shadow'
  472. },
  473. formatter: function (params) {
  474. const data = yearData.find(e => e.yearQuarter == params[0].axisValue)
  475. return `${params[0].axisValue}:<br/>
  476. 本季度工艺错误次数${data.totalErrorQuantity};<br/>
  477. 本季度目标${data.totalQuarterTarget};<br/>
  478. `
  479. }
  480. },
  481. xAxis: {
  482. type: 'category',
  483. data: yearLabel
  484. },
  485. yAxis: {
  486. type: 'value'
  487. },
  488. dataset: {
  489. // 用 dimensions 指定了维度的顺序。直角坐标系中,如果 X 轴 type 为 category,
  490. // 默认把第一个维度映射到 X 轴上,后面维度映射到 Y 轴上。
  491. // 如果不指定 dimensions,也可以通过指定 series.encode
  492. // 完成映射,参见后文。
  493. dimensions: ['month', 'ratio'],
  494. source: yearData
  495. },
  496. series: [{
  497. name: '工艺文件制作错误次数',
  498. type: 'line',
  499. data: toData
  500. }]
  501. });
  502. // echartInstance.value.on('click', function (params) {
  503. // console.log(params)
  504. // }
  505. // )
  506. }
  507. function getList() {
  508. listProcessErrorStatistics(queryParams.value).then(res => {
  509. if (res.code === 200) {
  510. res.rows.forEach(item => {
  511. item.errorQuantity = parseInt(item.errorQuantity)
  512. item.quarterTarget = parseInt(item.quarterTarget)
  513. })
  514. errorStatisticsList.value = res.rows.map(v => ({ ...v, editStatus: false }))
  515. initGrophSize()
  516. initChartData();
  517. // updateCompleteChart(monthlyData, displayMonths, selectedYear, currentYear);
  518. }
  519. })
  520. }
  521. function handleTabClick(arg) {
  522. tabname.value = arg.props.name
  523. getList()
  524. }
  525. function handleAdd() {
  526. errorStatisticsList.value.unshift({year:null,quarter:null,date:null,companyAlias:null,specification:null,drawingNumber:null,
  527. type:null,complaintContent:null,errorQuantity:0,quarterTarget:null,status:null, editStatus: true })
  528. }
  529. function handleRowYearChange(arg, row) {
  530. row.year = proxy.moment(arg).format('YYYY')
  531. }
  532. function initChart() {
  533. echartInstance.value = markRaw(echarts.init(echartDom.value));
  534. }
  535. onMounted(() => {
  536. initChart();
  537. getList();
  538. });
  539. </script>
  540. <style lang="scss">
  541. .demo-tabs {
  542. height: 100%;
  543. display: flex;
  544. flex-direction: column;
  545. .el-tabs__content {
  546. height: 100%;
  547. #pane-first {
  548. height: 100%;
  549. }
  550. #pane-second {
  551. height: 100%;
  552. position: relative;
  553. }
  554. }
  555. }
  556. </style>