backend.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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. 总公司园区
  6. <template #overlay>
  7. <a-menu @click="handleMenuClick">
  8. <a-menu-item key="1">
  9. <UserOutlined />
  10. 总公司园区
  11. </a-menu-item>
  12. </a-menu>
  13. </template>
  14. <template #icon><down-outlined /></template>
  15. </a-dropdown-button>
  16. <span style="margin: 15px 60px 15px 15px;"> 共计 {{dataSize}} 条数据</span>
  17. <a-table
  18. :columns="columns"
  19. :data-source="dataSource"
  20. :row-selection="rowSelection" bordered>
  21. <template #bodyCell="{ column, text, record }">
  22. <template v-if="['advice'].includes(column.dataIndex)">
  23. <div class="adviceBox">
  24. <span style="font-size: 1rem">{{ text }}</span>
  25. <br /><br />
  26. <span >{{record.createTime}}</span>
  27. </div>
  28. </template>
  29. <template v-else-if="column.dataIndex === 'image'">
  30. <div class="image-container">
  31. <a-image
  32. v-if="record.urlList!=0"
  33. v-for="(filename, index) in record.urlList"
  34. :width="150"
  35. :height="150"
  36. :src="`${imageBaseUrl}${filename}`"/>
  37. </div>
  38. </template>
  39. <template v-else-if="column.dataIndex === 'reply'">
  40. <a-textarea :rows="4"/>
  41. <a-button type="primary" style="float: right">回 复</a-button>
  42. </template>
  43. <template v-else-if="column.dataIndex === 'edit'">
  44. <a-popconfirm
  45. title="你确定要删除这条信息吗?"
  46. ok-text="是"
  47. cancel-text="否"
  48. @confirm="deleteAdviceInfo(record.key)"
  49. @cancel="cancel">
  50. <a-button type="link">删 除</a-button>
  51. </a-popconfirm>
  52. </template>
  53. </template>
  54. </a-table>
  55. </div>
  56. </template>
  57. <script lang="ts">
  58. import { cloneDeep } from 'lodash-es';
  59. import { reactive, ref, defineComponent} from 'vue';
  60. import type { UnwrapRef } from 'vue';
  61. import axios from 'axios';
  62. import {Image} from "ant-design-vue";
  63. import type { MenuProps } from 'ant-design-vue';
  64. import { DownOutlined } from '@ant-design/icons-vue';
  65. import {baseUrl, imageBaseUrl} from '@/config.js'
  66. interface DataItem {
  67. key: string;
  68. advice: string;
  69. urlList: any;
  70. createTime: string;
  71. }
  72. const columns = [
  73. {
  74. title: '意见及建议',
  75. dataIndex: 'advice',
  76. width: '20%',
  77. },
  78. {
  79. title: '图片',
  80. dataIndex: 'image',
  81. width: '40%',
  82. },
  83. {
  84. title: '回复',
  85. dataIndex: 'reply',
  86. width: '20%',
  87. },
  88. {
  89. title: '操作',
  90. dataIndex: 'edit',
  91. width: '15%'
  92. }
  93. ];
  94. const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
  95. let dataSource = ref()
  96. let data: DataItem[] = [];
  97. let dataSize = 0
  98. const edit = (key: string) => {
  99. editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
  100. };
  101. const save = (key: string) => {
  102. Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
  103. delete editableData[key];
  104. };
  105. const cancel = (key: string) => {
  106. delete editableData[key];
  107. };
  108. const handleButtonClick = (e: Event) => {
  109. console.log('click left button', e);
  110. };
  111. const handleMenuClick: MenuProps['onClick'] = e => {
  112. console.log('click', e);
  113. };
  114. const rowSelection = ref({
  115. checkStrictly: false,
  116. onChange: (selectedRowKeys: (string | number)[], selectedRows: DataItem[]) => {
  117. console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  118. },
  119. onSelect: (record: DataItem, selected: boolean, selectedRows: DataItem[]) => {
  120. console.log(record, selected, selectedRows);
  121. },
  122. onSelectAll: (selected: boolean, selectedRows: DataItem[], changeRows: DataItem[]) => {
  123. console.log(selected, selectedRows, changeRows);
  124. },
  125. });
  126. export default defineComponent({
  127. components: {
  128. 'a-image':Image,
  129. DownOutlined
  130. },
  131. data(){
  132. return{
  133. columns,
  134. editableData,
  135. edit,
  136. save,
  137. cancel,
  138. dataSource,
  139. imageBaseUrl,
  140. rowSelection,
  141. handleButtonClick,
  142. handleMenuClick,
  143. dataSize
  144. }
  145. },
  146. methods:{
  147. async mygetData(){
  148. try {
  149. const response = await axios.get(baseUrl+'/queryReview');
  150. this.dataSize = response.data.length
  151. for (let item of response.data){
  152. const urlList = item.imageInfoList.trim().split(/\s*,\s*/)
  153. data.push({
  154. key: item.id,
  155. advice: item.advice,
  156. urlList: urlList,
  157. createTime: item.createTime
  158. })
  159. }
  160. dataSource.value = data;
  161. } catch (error) {
  162. console.error('Error fetching data:', error);
  163. }
  164. },
  165. refreshData(){
  166. data = []
  167. this.mygetData()
  168. },
  169. async deleteAdviceInfo(key:string){
  170. await axios.get(baseUrl+'/deleteAdviceInfo',{params: {"id":key}})
  171. .then(response =>{
  172. // 删除成功后,刷新页面
  173. dataSource.value = dataSource.value.filter(item => item.key !== key);
  174. }).catch(error => {
  175. console.error('删除失败:', error);
  176. });
  177. }
  178. },
  179. mounted() {
  180. const now = Date.now();
  181. const expirationTime = parseInt(localStorage.getItem('hasPassedPasswordCheck_expiration'));
  182. const pass = localStorage.getItem('hasPassedPasswordCheck')
  183. if(pass && !(expirationTime && now > expirationTime)){
  184. this.mygetData()
  185. }else{
  186. // 数据已过期,从localStorage中移除
  187. localStorage.removeItem('hasPassedPasswordCheck');
  188. localStorage.removeItem('hasPassedPasswordCheck_expiration');
  189. uni.reLaunch({
  190. url: './checkPassword'
  191. });
  192. }
  193. }
  194. });
  195. </script>
  196. <style scoped>
  197. .adviceBox {
  198. display: flex;
  199. flex-direction: column;
  200. justify-content: space-between;
  201. width: 100%; /* 宽度与单元格相同 */
  202. height: 100%; /* 高度与单元格相同 */
  203. max-height: 20vh;
  204. overflow-y: auto; /* 当内容超出时显示垂直滚动条 */
  205. padding: 0.5rem; /* 内边距,可根据需要调整 */
  206. box-sizing: border-box; /* 确保padding和border包含在width和height内 */
  207. }
  208. .adviceBox span:first-child {
  209. text-indent: 2em; /* 首行缩进两格 */
  210. white-space: pre-line; /* 保留换行符和空格 */
  211. font-size: 1rem; /* 文字大小 */
  212. flex-grow: 1; /* 占据剩余空间,确保createTime位于底部 */
  213. }
  214. .adviceBox span:last-child {
  215. align-self: flex-end; /* 将createTime定位至右下角 */
  216. }
  217. .ant-upload-select-picture-card i {
  218. font-size: 32px;
  219. color: #999;
  220. }
  221. .ant-upload-select-picture-card .ant-upload-text {
  222. margin-top: 8px;
  223. color: #666;
  224. }
  225. .editable-row-operations a {
  226. margin-right: 8px;
  227. }
  228. .image-container {
  229. display: flex;
  230. flex-wrap: wrap;
  231. gap: 20px; /* 图片之间的间距 */
  232. }
  233. </style>