backend.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. <template>
  2. <div>
  3. <a-button @click="refreshData" type="primary" style="margin: 15px 30px 15px 15px;">重 置</a-button>
  4. <a-dropdown-button @click="handleButtonClick">
  5. {{belongPark}}
  6. <template #overlay>
  7. <a-menu @click="handleMenuClick">
  8. <a-menu-item key="1">
  9. <UserOutlined />
  10. 总公司园区
  11. </a-menu-item>
  12. <a-menu-item key="2">
  13. <UserOutlined />
  14. 西区
  15. </a-menu-item>
  16. <a-menu-item key="3">
  17. <UserOutlined />
  18. 电厂
  19. </a-menu-item>
  20. <a-menu-item key="4">
  21. <UserOutlined />
  22. 东区
  23. </a-menu-item>
  24. <a-menu-item key="5">
  25. <UserOutlined />
  26. 高青
  27. </a-menu-item>
  28. <a-menu-item key="6">
  29. <UserOutlined />
  30. 制衣
  31. </a-menu-item>
  32. </a-menu>
  33. </template>
  34. <template #icon><down-outlined /></template>
  35. </a-dropdown-button>
  36. <span style="margin: 15px 60px 15px 15px;"> 共计 {{dataSize}} 条数据</span>
  37. <a-button type="primary" @click="showStatisticsPanel" style="margin: 15px 30px 15px 250px;" :disabled='true'>展开数据统计面板(暂未开放)</a-button>
  38. <div class="dataAnalysis" v-show="isshowPanel">
  39. <div class="panelItem" id="adviceNum" style="width: 28%;"></div>
  40. <div class="panelItem" id="adviceNumYear" style="width: 40%;"></div>
  41. <div class="panelItem" id="adviceNumBread" style="width: 28%;"></div>
  42. </div>
  43. <a-table
  44. :columns="columns"
  45. :data-source="dataSource"
  46. :row-selection="rowSelection" bordered>
  47. <template #bodyCell="{ column, text, record }">
  48. <template v-if="['advice'].includes(column.dataIndex)">
  49. <div class="adviceBox">
  50. <span style="font-size: 1rem">{{ text }}</span>
  51. <br /><br />
  52. <span>{{record.type}}园区 - {{record.createTime}}</span>
  53. </div>
  54. </template>
  55. <template v-else-if="column.dataIndex === 'image'">
  56. <div class="image-container">
  57. <a-image
  58. v-if="record.urlList!=0"
  59. v-for="(filename, index) in record.urlList"
  60. :width="120"
  61. :height="120"
  62. :src="`${imageBaseUrl}${filename}`"/>
  63. </div>
  64. </template>
  65. <template v-else-if="column.dataIndex === 'reply'">
  66. <a-textarea :rows="4"/>
  67. <a-button type="primary" style="float: right">回 复</a-button>
  68. </template>
  69. <template v-else-if="column.dataIndex === 'edit'">
  70. <a-popconfirm
  71. title="你确定要删除这条信息吗?"
  72. ok-text="是"
  73. cancel-text="否"
  74. @confirm="deleteAdviceInfo(record.key)"
  75. @cancel="cancel">
  76. <a-button type="link">删 除</a-button>
  77. </a-popconfirm>
  78. </template>
  79. </template>
  80. </a-table>
  81. </div>
  82. </template>
  83. <script lang="ts" setup>
  84. import { cloneDeep } from 'lodash-es';
  85. import { reactive, ref, onMounted} from 'vue';
  86. import type { UnwrapRef } from 'vue';
  87. import axios from 'axios';
  88. import {Image} from "ant-design-vue";
  89. import type { MenuProps } from 'ant-design-vue';
  90. import { DownOutlined } from '@ant-design/icons-vue';
  91. import {baseUrl, imageBaseUrl} from '@/config.js'
  92. import * as echarts from 'echarts';
  93. interface DataItem {
  94. key: string;
  95. advice: string;
  96. urlList: any;
  97. createTime: string;
  98. type: string
  99. }
  100. const columns = [
  101. {
  102. title: '意见及建议',
  103. dataIndex: 'advice',
  104. width: '20%',
  105. },
  106. {
  107. title: '图片',
  108. dataIndex: 'image',
  109. width: '40%',
  110. },
  111. {
  112. title: '回复',
  113. dataIndex: 'reply',
  114. width: '20%',
  115. },
  116. {
  117. title: '操作',
  118. dataIndex: 'edit',
  119. width: '15%'
  120. }
  121. ];
  122. const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
  123. let dataSource = ref()
  124. let data: DataItem[] = [];
  125. let dataSize = 0
  126. const edit = (key: string) => {
  127. editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
  128. };
  129. const save = (key: string) => {
  130. Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
  131. delete editableData[key];
  132. };
  133. const cancel = (key: string) => {
  134. delete editableData[key];
  135. };
  136. const handleButtonClick = (e: Event) => {
  137. console.log('click left button', e);
  138. };
  139. const belongPark = ref('全部园区')
  140. const type = ref('')
  141. const handleMenuClick: MenuProps['onClick'] = e => {
  142. console.log('click', e.key);
  143. switch(e.key){
  144. case '1':
  145. belongPark.value = '总公司园区';
  146. type.value = '1'
  147. break;
  148. case '2':
  149. belongPark.value = '西区';
  150. type.value = '2'
  151. break;
  152. case '3':
  153. belongPark.value = '电厂';
  154. type.value = '3'
  155. break;
  156. case '4':
  157. belongPark.value = '东区';
  158. type.value = '4'
  159. break;
  160. case '5':
  161. belongPark.value = '高青';
  162. type.value = '5'
  163. break;
  164. case '6':
  165. belongPark.value = '制衣';
  166. type.value = '6'
  167. break;
  168. }
  169. mygetData()
  170. };
  171. const rowSelection = ref({
  172. checkStrictly: false,
  173. onChange: (selectedRowKeys: (string | number)[], selectedRows: DataItem[]) => {
  174. console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  175. },
  176. onSelect: (record: DataItem, selected: boolean, selectedRows: DataItem[]) => {
  177. console.log(record, selected, selectedRows);
  178. },
  179. onSelectAll: (selected: boolean, selectedRows: DataItem[], changeRows: DataItem[]) => {
  180. console.log(selected, selectedRows, changeRows);
  181. },
  182. });
  183. async function mygetData(){
  184. try {
  185. // 查询前清空数据
  186. data = []
  187. const response = await axios.get(baseUrl+'/queryReview?type='+type.value);
  188. dataSize = response.data.length
  189. for (let item of response.data){
  190. const urlList = item.imageInfoList.trim().split(/\s*,\s*/)
  191. switch(item.type){
  192. case '1':
  193. item.type = '总公司'
  194. break;
  195. case '2':
  196. item.type = '西区'
  197. break;
  198. case '3':
  199. item.type = '电厂'
  200. break;
  201. case '4':
  202. item.type = '东区'
  203. break;
  204. case '5':
  205. item.type = '高青'
  206. break;
  207. case '6':
  208. item.type = '制衣'
  209. break;
  210. }
  211. data.push({
  212. key: item.id,
  213. advice: item.advice,
  214. urlList: urlList,
  215. createTime: item.createTime,
  216. type: item.type
  217. })
  218. }
  219. dataSource.value = data;
  220. } catch (error) {
  221. console.error('Error fetching data:', error);
  222. }
  223. };
  224. function refreshData(){
  225. belongPark.value = '全部园区'
  226. type.value = ''
  227. data = []
  228. mygetData()
  229. };
  230. async function deleteAdviceInfo(key:string){
  231. await axios.get(baseUrl+'/deleteAdviceInfo',{params: {"id":key}})
  232. .then(response =>{
  233. // 删除成功后,刷新页面
  234. dataSource.value = dataSource.value.filter(item => item.key !== key);
  235. }).catch(error => {
  236. console.error('删除失败:', error);
  237. });
  238. };
  239. const isshowPanel = ref(false)
  240. function showStatisticsPanel(){
  241. console.log("展示面板")
  242. isshowPanel.value = !isshowPanel.value
  243. setTimeout(()=>{
  244. if(isshowPanel.value){
  245. // 数据可视化区域
  246. // 月度
  247. var chartDom = document.getElementById('adviceNum');
  248. var myChart = echarts.init(chartDom);
  249. var option;
  250. option = {
  251. title: {
  252. text: '本月各园区意见征集数量统计',
  253. textStyle: {
  254. fontSize: '20px',
  255. color: '#333',
  256. top: "10%"
  257. }
  258. },
  259. grid: {
  260. height:"70%",
  261. width: "80%",
  262. top: "22%"
  263. },
  264. xAxis: {
  265. type: 'category',
  266. data: ['总公司园区', '西区', '电厂', '东区', '高青', '制衣'],
  267. axisLabel: {
  268. interval: 0 // 强制显示所有标签
  269. }
  270. },
  271. yAxis: {
  272. type: 'value',
  273. min: 0,
  274. max: 50,
  275. interval: 5,
  276. },
  277. series: [
  278. {
  279. data: [
  280. {
  281. value: 35,
  282. itemStyle: {
  283. color: '#a90000'
  284. }
  285. },
  286. 20,
  287. 15,
  288. 8,
  289. 7,
  290. 11,
  291. 13
  292. ],
  293. type: 'bar'
  294. }
  295. ]
  296. };
  297. option && myChart.setOption(option);
  298. //年度
  299. var chartDom2 = document.getElementById('adviceNumYear');
  300. var myChart2 = echarts.init(chartDom2);
  301. var option2;
  302. option2 = {
  303. title: {
  304. text: '各园区年度统计'
  305. },
  306. tooltip: {
  307. trigger: 'axis'
  308. },
  309. legend: {
  310. data: ['总公司', '西区', '电厂', '东区', '高青', '制衣']
  311. },
  312. grid: {
  313. left: '3%',
  314. right: '4%',
  315. bottom: '3%',
  316. containLabel: true,
  317. width: '90%'
  318. },
  319. toolbox: {
  320. feature: {
  321. saveAsImage: {}
  322. }
  323. },
  324. xAxis: {
  325. type: 'category',
  326. boundaryGap: false,
  327. data: ['一月', '二月', '三月', '四月', '五月', '六月', '七月','八月', '九月','十月','十一月','十二月']
  328. },
  329. yAxis: {
  330. type: 'value'
  331. },
  332. series: [
  333. {
  334. name: '总公司',
  335. type: 'line',
  336. stack: 'Total',
  337. data: [120, 132, 101, 134, 90, 230, 210]
  338. },
  339. {
  340. name: '西区',
  341. type: 'line',
  342. stack: 'Total',
  343. data: [220, 182, 191, 234, 290, 330, 310]
  344. },
  345. {
  346. name: '电厂',
  347. type: 'line',
  348. stack: 'Total',
  349. data: [150, 232, 201, 154, 190, 330, 410]
  350. },
  351. {
  352. name: '东区',
  353. type: 'line',
  354. stack: 'Total',
  355. data: [320, 332, 301, 334, 390, 330, 320]
  356. },
  357. {
  358. name: '高青',
  359. type: 'line',
  360. stack: 'Total',
  361. data: [820, 932, 901, 934, 1290, 1330, 1320]
  362. },
  363. {
  364. name: '制衣',
  365. type: 'line',
  366. stack: 'Total',
  367. data: [720, 732, 801, 234, 190, 1330, 1320]
  368. }
  369. ]
  370. };
  371. option2 && myChart2.setOption(option2);
  372. // 饼图
  373. var chartDom3 = document.getElementById('adviceNumBread');
  374. var myChart3 = echarts.init(chartDom3);
  375. var option3;
  376. option3 = {
  377. grid: {
  378. left: '3%',
  379. right: '4%',
  380. bottom: '3%',
  381. width: '90%',
  382. height: '90%'
  383. },
  384. title: {
  385. text: '占比情况统计',
  386. subtext: 'Fake Data',
  387. left: 'center'
  388. },
  389. tooltip: {
  390. trigger: 'item'
  391. },
  392. legend: {
  393. orient: 'vertical',
  394. left: 'left'
  395. },
  396. series: [
  397. {
  398. name: 'Access From',
  399. type: 'pie',
  400. radius: '50%',
  401. data: [
  402. { value: 148, name: '总公司' },
  403. { value: 75, name: '西区' },
  404. { value: 50, name: '电厂' },
  405. { value: 44, name: '东区' },
  406. { value: 30, name: '高青' },
  407. { value: 66, name: '制衣' }
  408. ],
  409. emphasis: {
  410. itemStyle: {
  411. shadowBlur: 10,
  412. shadowOffsetX: 0,
  413. shadowColor: 'rgba(0, 0, 0, 0.5)'
  414. }
  415. }
  416. }
  417. ]
  418. };
  419. option3 && myChart3.setOption(option3);
  420. }
  421. },1000)
  422. }
  423. onMounted(()=>{
  424. document.title = '膳食科意见征集后台管理系统'
  425. const now = Date.now();
  426. const expirationTime = parseInt(localStorage.getItem('hasPassedPasswordCheck_expiration'));
  427. const pass = localStorage.getItem('hasPassedPasswordCheck')
  428. if(pass && !(expirationTime && now > expirationTime)){
  429. mygetData()
  430. }else{
  431. // 数据已过期,从localStorage中移除
  432. localStorage.removeItem('hasPassedPasswordCheck');
  433. localStorage.removeItem('hasPassedPasswordCheck_expiration');
  434. uni.reLaunch({
  435. url: './checkPassword'
  436. });
  437. }
  438. })
  439. </script>
  440. <style scoped>
  441. .adviceBox {
  442. display: flex;
  443. flex-direction: column;
  444. justify-content: space-between;
  445. width: 100%;
  446. height: auto;
  447. max-height: 20vh;
  448. overflow-y: auto;
  449. padding: 0.5rem;
  450. box-sizing: border-box;
  451. }
  452. .adviceBox span:first-child {
  453. text-indent: 2em; /* 首行缩进两格 */
  454. white-space: pre-line; /* 保留换行符和空格 */
  455. font-size: 1rem; /* 文字大小 */
  456. flex-grow: 1; /* 占据剩余空间,确保createTime位于底部 */
  457. }
  458. .adviceBox span:last-child {
  459. align-self: flex-end; /* 将createTime定位至右下角 */
  460. }
  461. .ant-upload-select-picture-card i {
  462. font-size: 32px;
  463. color: #999;
  464. }
  465. .ant-upload-select-picture-card .ant-upload-text {
  466. margin-top: 8px;
  467. color: #666;
  468. }
  469. .editable-row-operations a {
  470. margin-right: 8px;
  471. }
  472. .image-container {
  473. display: flex;
  474. flex-wrap: wrap;
  475. gap: 20px; /* 图片之间的间距 */
  476. }
  477. .dataAnalysis{
  478. width: 100%;
  479. height: 40vh;
  480. display: flex;
  481. justify-content: space-around;
  482. }
  483. .panelItem{
  484. margin-top: 1vh;
  485. height: 90%;
  486. background-color: azure;
  487. }
  488. </style>