No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

project.vue 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. <template>
  2. <div class="h5-container">
  3. <van-nav-bar title="项目案例" @click-left="onClickLeft" @click-right="handAdd">
  4. <template #right>
  5. <van-icon name="add" size="25" color="#000" />
  6. </template>
  7. </van-nav-bar>
  8. <van-search
  9. v-model="query.projectName"
  10. show-action
  11. placeholder="请输入项目名称"
  12. @search="onRefresh"
  13. @cancel="handleClearSearch"
  14. />
  15. <!-- 项目列表 -->
  16. <van-pull-refresh v-model="isRefreshing" success-text="刷新成功" @refresh="onRefresh">
  17. <van-list
  18. v-model:loading="isLoading"
  19. :finished="isFinished"
  20. finished-text="没有更多了"
  21. offset="200"
  22. @load="onLoad"
  23. >
  24. <div v-for="(item, idx) in resultData" :key="item.id">
  25. <van-swipe-cell
  26. title-style="color: #007aff"
  27. style="height: 80px;"
  28. :ref="el => getSwipeCellRef(el, idx)"
  29. >
  30. <template #default>
  31. <div class="swipe-cell-default">
  32. <van-cell
  33. style="min-height: 120px; padding: 0 0 0 0; display: flex; align-items: flex-start;"
  34. @click="edits(item)"
  35. >
  36. <template #title>
  37. <div class="cell-title">
  38. {{ item.projectName }}
  39. </div>
  40. </template>
  41. <template #label>
  42. <div>案例编号:{{ item.caseNumber }}</div>
  43. <div>案例类型:{{ item.caseType }}</div>
  44. <div>浏览量:{{ item.viewCount }}</div>
  45. </template>
  46. </van-cell>
  47. <div class="swipe-cell-default-icon">
  48. <van-icon
  49. v-if="openStatus[idx]"
  50. name="arrow-double-left"
  51. @click.stop="openSwipe(idx)"
  52. />
  53. <van-icon
  54. v-else
  55. name="arrow-double-right"
  56. @click.stop="closeSwipe(idx)"
  57. />
  58. </div>
  59. </div>
  60. </template>
  61. <template #right>
  62. <van-button
  63. v-if="item.canDelete"
  64. square
  65. class="delete-button"
  66. text="删除"
  67. @click="handleDelete(item)"
  68. />
  69. </template>
  70. </van-swipe-cell>
  71. </div>
  72. </van-list>
  73. </van-pull-refresh>
  74. </div>
  75. </template>
  76. <script setup>
  77. import { ref, getCurrentInstance } from 'vue';
  78. import { useRouter } from 'vue-router';
  79. import { showDialog, showSuccessToast, showToast } from 'vant';
  80. const { proxy } = getCurrentInstance();
  81. const router = useRouter();
  82. const onClickLeft = () => {
  83. history.back();
  84. };
  85. const query = ref({
  86. caseNumber: '',
  87. projectName: ''
  88. });
  89. const isRefreshing = ref(false);
  90. const isLoading = ref(false);
  91. const isFinished = ref(false);
  92. const currentPage = ref(1);
  93. const pageSize = ref(10);
  94. const totalRows = ref(0);
  95. const resultData = ref([]);
  96. const tableData = ref([]);
  97. const currentUserId = String(localStorage.getItem('userId'));
  98. // 获取列表数据
  99. const getTableData = async () => {
  100. const url = '/sgsafe/Manager/queryProject';
  101. const param = {
  102. page: currentPage.value,
  103. rows: pageSize.value,
  104. params: JSON.stringify(query.value)
  105. };
  106. const response = await proxy.$axios.get(url, param);
  107. if (response.data.code == 0) {
  108. tableData.value = response.data.data.records.map(item => ({
  109. ...item,
  110. canDelete: String(item.addId) === currentUserId
  111. }));
  112. console.log('列表数据', tableData.value);
  113. totalRows.value = response.data.data.total;
  114. } else {
  115. showToast({
  116. type: 'error',
  117. message: '操作失败!' + response.data.msg
  118. });
  119. }
  120. };
  121. // 刷新
  122. const onRefresh = () => {
  123. basicReset();
  124. onLoad();
  125. };
  126. // 加载数据
  127. const onLoad = async () => {
  128. if (isRefreshing.value) {
  129. resultData.value = [];
  130. currentPage.value = 1;
  131. isRefreshing.value = false;
  132. }
  133. try {
  134. await getTableData();
  135. if (pageSize.value * currentPage.value < totalRows.value) {
  136. resultData.value = [...resultData.value, ...tableData.value];
  137. openStatus.value = new Array(resultData.value.length).fill(true);
  138. currentPage.value++;
  139. } else {
  140. resultData.value = [...resultData.value, ...tableData.value];
  141. openStatus.value = new Array(resultData.value.length).fill(true);
  142. isFinished.value = true;
  143. }
  144. console.log('resultData', resultData.value);
  145. } catch (error) {
  146. console.log(error);
  147. isFinished.value = true;
  148. } finally {
  149. isLoading.value = false;
  150. }
  151. };
  152. // 重置列表
  153. const basicReset = () => {
  154. isFinished.value = false;
  155. isLoading.value = true;
  156. currentPage.value = 1;
  157. resultData.value = [];
  158. };
  159. // 清空搜索
  160. const handleClearSearch = () => {
  161. query.value.projectName = '';
  162. onRefresh();
  163. };
  164. // 新增
  165. const handAdd = () => {
  166. router.push({
  167. path: "/projectList",
  168. query: { mark: -1 }
  169. });
  170. };
  171. // 编辑/查看
  172. const edits = async (row) => {
  173. const currentUserId = localStorage.getItem('userId');
  174. const addId = row.addId;
  175. const isOwner = String(addId).trim().toLowerCase() === String(currentUserId).trim().toLowerCase();
  176. // 更新浏览量
  177. await updateViewCount(row);
  178. router.push({
  179. path: "/projectList",
  180. query: {
  181. mark: 1,
  182. data: JSON.stringify(row),
  183. readOnly: !isOwner ? 'true' : undefined
  184. }
  185. });
  186. };
  187. // 更新浏览量
  188. const updateViewCount = async (item) => {
  189. try {
  190. const payload = { ...item };
  191. payload.viewCount = String((Number(payload.viewCount) || 0) + 1);
  192. const url = '/sgsafe/Manager/saveProject';
  193. const param = {
  194. json: JSON.stringify(payload)
  195. };
  196. const response = await proxy.$axios.post(url, param);
  197. if (response.data.code === '0' || response.data.code === 0) {
  198. const index = resultData.value.findIndex(data => data.id === item.id);
  199. if (index !== -1) {
  200. resultData.value[index].viewCount = payload.viewCount;
  201. }
  202. }
  203. } catch (error) {
  204. console.error('更新浏览量失败:', error);
  205. }
  206. };
  207. // 删除
  208. const handleDelete = (item) => {
  209. const currentUserId = localStorage.getItem('userId');
  210. const addId = item.addId;
  211. if (!currentUserId || !addId || String(addId).trim() !== String(currentUserId).trim()) {
  212. showToast({
  213. type: 'warning',
  214. message: '无权限删除!只能删除自己添加的案例。'
  215. });
  216. return;
  217. }
  218. showDialog({
  219. title: '删除确认',
  220. message: `确定要删除项目"${item.projectName}"吗?删除后将无法恢复。`,
  221. showCancelButton: true,
  222. confirmButtonText: '确定删除',
  223. cancelButtonText: '取消',
  224. }).then(() => {
  225. executeDelete(item);
  226. }).catch(() => {
  227. console.log('取消删除');
  228. });
  229. };
  230. // 执行删除
  231. const executeDelete = (item) => {
  232. const deleteData = { ...item };
  233. const now = new Date();
  234. const year = now.getFullYear();
  235. const month = String(now.getMonth() + 1).padStart(2, '0');
  236. const day = String(now.getDate()).padStart(2, '0');
  237. const hours = String(now.getHours()).padStart(2, '0');
  238. const minutes = String(now.getMinutes()).padStart(2, '0');
  239. const seconds = String(now.getSeconds()).padStart(2, '0');
  240. deleteData.cancelTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  241. deleteData.cancelFlag = '1';
  242. const url = '/sgsafe/Manager/saveProject';
  243. const param = {
  244. json: JSON.stringify(deleteData)
  245. };
  246. proxy.$axios.post(url, param).then(response => {
  247. if (response.data.code == '0' || response.data.code === 0) {
  248. showSuccessToast("删除成功");
  249. onRefresh();
  250. } else {
  251. showToast({
  252. type: 'fail',
  253. message: '删除失败:' + (response.data.msg || '未知错误')
  254. });
  255. }
  256. }).catch(error => {
  257. showToast({
  258. type: 'fail',
  259. message: '删除失败:网络错误'
  260. });
  261. console.error('删除失败:', error);
  262. });
  263. };
  264. // 滑动单元格控制
  265. const openStatus = ref([]);
  266. const swipeCellRefs = ref([]);
  267. const getSwipeCellRef = (el, index) => {
  268. if (el) {
  269. swipeCellRefs.value[index] = el;
  270. }
  271. };
  272. const openSwipe = (idx) => {
  273. openStatus.value = new Array(resultData.value.length).fill(true);
  274. if (idx >= 0 && idx < swipeCellRefs.value.length) {
  275. openStatus.value[idx] = false;
  276. swipeCellRefs.value[idx].open('right');
  277. }
  278. };
  279. const closeSwipe = (idx) => {
  280. if (idx >= 0 && idx < swipeCellRefs.value.length) {
  281. openStatus.value[idx] = true;
  282. swipeCellRefs.value[idx].close();
  283. }
  284. };
  285. </script>
  286. <style scoped>
  287. .h5-container {
  288. width: 100%;
  289. padding: 5px;
  290. box-sizing: border-box;
  291. }
  292. .cell-title {
  293. display: -webkit-box;
  294. -webkit-box-orient: vertical;
  295. -webkit-line-clamp: 2;
  296. overflow: hidden;
  297. text-overflow: ellipsis;
  298. line-height: 1.5;
  299. max-height: calc(1.5em * 2);
  300. font-size: 16px;
  301. font-weight: bold;
  302. color: #333;
  303. }
  304. .swipe-cell-default {
  305. display: flex;
  306. background-color: #ffffff;
  307. justify-content: center;
  308. align-items: center;
  309. }
  310. .swipe-cell-default-icon {
  311. width: 60px;
  312. display: flex;
  313. justify-content: center;
  314. }
  315. .delete-button {
  316. height: 100%;
  317. border: none;
  318. color: #ff0000;
  319. background-image: url('@/assets/img/del.png');
  320. background-size: auto 100%;
  321. background-repeat: no-repeat;
  322. }
  323. </style>