Няма описание
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.

safeMoneyBudget.vue 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <script setup>
  2. import { getCurrentInstance, onMounted, ref, computed } from 'vue';
  3. import { useRouter } from 'vue-router';
  4. import { showFailToast, showSuccessToast, showDialog, showLoadingToast } from 'vant';
  5. import tools from "@/tools/index.js";
  6. const { proxy } = getCurrentInstance()
  7. const router = useRouter()
  8. /* 通用方法: 重置list数据 */
  9. const basicReset = () => {
  10. finished.value = false;
  11. loading.value = true;
  12. pageNum.value = 1
  13. resultData.value = []
  14. }
  15. /* 查询数据 */
  16. const pageNum = ref(1)
  17. const pageSize = ref(10)
  18. const total = ref(0)
  19. const resultData = ref([])
  20. const queryList = ref([])
  21. // 年份选择
  22. const currentYear = new Date().getFullYear();
  23. const yearOptions = Array.from({ length: 10 }, (_, i) => {
  24. const year = currentYear - 5 + i;
  25. return {
  26. text: String(year),
  27. value: String(year)
  28. };
  29. }).reverse();
  30. const showYearPicker = ref(false);
  31. const formData = ref({
  32. year: String(currentYear),
  33. quarter: null
  34. })
  35. // 获取用户部门信息
  36. const jsonArray = localStorage.getItem('dept')
  37. const deptInformation = ref([])
  38. try {
  39. deptInformation.value = jsonArray ? JSON.parse(jsonArray) : [];
  40. } catch (error) {
  41. deptInformation.value = [];
  42. }
  43. const jsonArrayTree = localStorage.getItem('deptTree')
  44. const deptInformationTree = ref([])
  45. try {
  46. deptInformationTree.value = jsonArrayTree ? JSON.parse(jsonArrayTree) : [];
  47. } catch (error) {
  48. deptInformationTree.value = [];
  49. }
  50. const deptNameTree = deptInformationTree.value[0]?.deptName || ''
  51. const deptCode = deptInformation.value[0]?.deptCode?.substring(0, 5) || ''
  52. const tableData = ref([])
  53. const loading = ref(false)
  54. // 格式化数字显示(0 显示为空)
  55. const formatterZero = (value) => {
  56. return value === 0 || value === '0' || value === null || value === undefined ? '' : value
  57. }
  58. // 计算 1-9 月合计
  59. const formatterSum1To9 = (row) => {
  60. const months = [
  61. row.oneMonth,
  62. row.twoMonth,
  63. row.threeMonth,
  64. row.fourMonth,
  65. row.fiveMonth,
  66. row.sixMonth,
  67. row.sevenMonth,
  68. row.eightMonth,
  69. row.nineMonth
  70. ]
  71. const sum = months
  72. .map(val => {
  73. const num = Number(val)
  74. return isNaN(num) ? 0 : num
  75. })
  76. .reduce((a, b) => a + b, 0)
  77. return sum === 0 && months.every(v => !v) ? '' : sum
  78. }
  79. // 年份选择确认
  80. const onConfirmYear = (value) => {
  81. let selectedYear = '';
  82. if (value && value.selectedOptions && value.selectedOptions.length > 0) {
  83. const selectedOption = value.selectedOptions[0];
  84. selectedYear = typeof selectedOption === 'string' ? selectedOption : selectedOption.value || selectedOption.text;
  85. } else if (typeof value === 'string') {
  86. selectedYear = value;
  87. }
  88. formData.value.year = selectedYear;
  89. showYearPicker.value = false;
  90. search();
  91. }
  92. // 查询
  93. const search = async () => {
  94. const now = new Date()
  95. const currentYear = now.getFullYear()
  96. const currentMonth = now.getMonth()
  97. const currentQuarter = Math.ceil((currentMonth + 1) / 3)
  98. if (!formData.value.year) {
  99. formData.value.year = String(currentYear)
  100. }
  101. if (!formData.value.quarter) {
  102. formData.value.quarter = currentQuarter
  103. }
  104. loading.value = true
  105. await getTableData()
  106. }
  107. // 重置
  108. const reset = () => {
  109. formData.value.year = String(currentYear)
  110. formData.value.quarter = null
  111. search()
  112. }
  113. // 获取表格数据
  114. const getTableData = async () => {
  115. try {
  116. const res = await proxy.$axios.post('/sgsafe/expect/query', {
  117. params: JSON.stringify(formData.value)
  118. })
  119. if (res.data.code == '0') {
  120. tableData.value = res.data.data || []
  121. resultData.value = tableData.value
  122. total.value = tableData.value.length
  123. } else {
  124. showFailToast('加载失败:' + res.data.msg)
  125. }
  126. } catch (error) {
  127. console.error('请求失败:', error)
  128. showFailToast('网络错误,请重试')
  129. } finally {
  130. loading.value = false
  131. finished.value = true
  132. }
  133. }
  134. // 新增行
  135. const addRow = () => {
  136. router.push({
  137. path: "/safeMoneyBudgetList",
  138. query: {
  139. mark: -1,
  140. year: formData.value.year
  141. }
  142. });
  143. }
  144. // 编辑行
  145. const editRow = (row) => {
  146. router.push({
  147. path: "/safeMoneyBudgetList",
  148. query: {
  149. mark: 1,
  150. data: JSON.stringify(row),
  151. year: formData.value.year
  152. }
  153. });
  154. }
  155. // 删除行
  156. const deleteRow = async (row) => {
  157. try {
  158. await showDialog({
  159. title: '删除确认',
  160. message: '确定要删除该条记录吗?此操作不可恢复。',
  161. showCancelButton: true,
  162. confirmButtonText: '确定',
  163. cancelButtonText: '取消',
  164. })
  165. loading.value = true
  166. const deleteDataItem = { ...row }
  167. deleteDataItem.cancelFlag = '1'
  168. const res = await proxy.$axios.post('/sgsafe/expect/save', {
  169. json: JSON.stringify(deleteDataItem)
  170. })
  171. if (res.data.code == '0') {
  172. showSuccessToast('删除成功')
  173. tableData.value = tableData.value.filter(r => r.id !== row.id)
  174. resultData.value = tableData.value
  175. } else {
  176. showFailToast('删除失败:' + res.data.msg)
  177. }
  178. } catch (error) {
  179. if (error !== 'cancel') {
  180. showFailToast('删除操作异常')
  181. }
  182. } finally {
  183. loading.value = false
  184. }
  185. }
  186. // 列表加载与下拉刷新
  187. const refreshing = ref(false)
  188. const finished = ref(false)
  189. const onRefresh = async () => {
  190. refreshing.value = true
  191. basicReset()
  192. await search()
  193. refreshing.value = false
  194. };
  195. const onLoad = async () => {
  196. // 如果是下拉刷新,已经在 onRefresh 中处理了
  197. if (refreshing.value) {
  198. return
  199. }
  200. // 上拉加载更多(如果需要分页的话)
  201. if (finished.value) {
  202. return
  203. }
  204. await search()
  205. };
  206. // 获取费用类型字典
  207. const moneyTypeMap = ref({})
  208. const getMoneyTypeName = (value) => {
  209. if (!value) return ''
  210. if (moneyTypeMap.value[value]) {
  211. return moneyTypeMap.value[value]
  212. }
  213. const strValue = String(value)
  214. if (moneyTypeMap.value[strValue]) {
  215. return moneyTypeMap.value[strValue]
  216. }
  217. return value
  218. }
  219. const getDicList = () => {
  220. tools.dic.getDicList(['MONEY_TYPE']).then((response => {
  221. if (response.data && response.data.code === 0 && response.data.data) {
  222. const moneyTypeList = response.data.data.MONEY_TYPE || []
  223. moneyTypeMap.value = {}
  224. moneyTypeList.forEach(item => {
  225. if (item.dicCode && item.dicName) {
  226. moneyTypeMap.value[item.dicCode] = item.dicName
  227. moneyTypeMap.value[String(item.dicCode)] = item.dicName
  228. }
  229. if (item.dicName) {
  230. moneyTypeMap.value[item.dicName] = item.dicName
  231. }
  232. })
  233. }
  234. })).catch(error => {
  235. console.error('获取字典数据出错:', error)
  236. })
  237. }
  238. onMounted(() => {
  239. getDicList()
  240. search()
  241. })
  242. </script>
  243. <template>
  244. <div class="page-container">
  245. <van-sticky>
  246. <van-nav-bar title="安全费用预算" @click-right="addRow">
  247. <template #right>
  248. <van-icon name="add" size="25" color="#000" />
  249. </template>
  250. </van-nav-bar>
  251. </van-sticky>
  252. <!-- 查询区域 -->
  253. <div class="query-area">
  254. <van-field
  255. :model-value="formData.year"
  256. is-link
  257. readonly
  258. name="year"
  259. label="年份"
  260. placeholder="点击选择年份"
  261. @click="showYearPicker = true"
  262. />
  263. <div class="query-buttons">
  264. <van-button type="primary" size="small" @click="search">查询</van-button>
  265. <van-button size="small" @click="reset">重置</van-button>
  266. </div>
  267. </div>
  268. <van-popup v-model:show="showYearPicker" position="bottom">
  269. <van-picker
  270. :columns="yearOptions"
  271. @confirm="onConfirmYear"
  272. @cancel="showYearPicker = false"
  273. />
  274. </van-popup>
  275. <div class="scroll-container">
  276. <van-pull-refresh
  277. v-model="refreshing"
  278. success-text="刷新成功"
  279. @refresh="onRefresh"
  280. >
  281. <van-list
  282. v-model:loading="loading"
  283. :finished="finished"
  284. :immediate-check="false"
  285. finished-text="没有更多了"
  286. @load="onLoad"
  287. >
  288. <div v-for="item in resultData" :key="item.id || item.tempId" class="card">
  289. <van-swipe-cell>
  290. <template #default>
  291. <van-cell @click="editRow(item)" :border="false" is-link>
  292. <template #title>
  293. <div class="card-title">
  294. <div class="title-row">
  295. <span class="label">费用类型:</span>
  296. <span class="value">{{ getMoneyTypeName(item.moneyType) || '未设置' }}</span>
  297. </div>
  298. </div>
  299. </template>
  300. <template #label>
  301. <div class="card-content">
  302. <div class="content-row">
  303. <span class="label">预计投入费用:</span>
  304. <span class="value">{{ formatterZero(item.moneyNumber) || '0' }}</span>
  305. </div>
  306. <div class="content-row">
  307. <span class="label">1-9月合计:</span>
  308. <span class="value">{{ formatterSum1To9(item) || '0' }}</span>
  309. </div>
  310. <!-- <div class="months-grid">-->
  311. <!-- <div class="month-item" v-for="(month, index) in [-->
  312. <!-- { key: 'oneMonth', label: '1月' },-->
  313. <!-- { key: 'twoMonth', label: '2月' },-->
  314. <!-- { key: 'threeMonth', label: '3月' },-->
  315. <!-- { key: 'fourMonth', label: '4月' },-->
  316. <!-- { key: 'fiveMonth', label: '5月' },-->
  317. <!-- { key: 'sixMonth', label: '6月' },-->
  318. <!-- { key: 'sevenMonth', label: '7月' },-->
  319. <!-- { key: 'eightMonth', label: '8月' },-->
  320. <!-- { key: 'nineMonth', label: '9月' },-->
  321. <!-- { key: 'tenMonth', label: '10月' },-->
  322. <!-- { key: 'elevenMonth', label: '11月' },-->
  323. <!-- { key: 'twelveMonth', label: '12月' }-->
  324. <!-- ]" :key="month.key">-->
  325. <!-- <span class="month-label">{{ month.label }}:</span>-->
  326. <!-- <span class="month-value">{{ formatterZero(item[month.key]) || '0' }}</span>-->
  327. <!-- </div>-->
  328. <!-- </div>-->
  329. </div>
  330. </template>
  331. </van-cell>
  332. </template>
  333. <template #right>
  334. <van-button
  335. square
  336. class="delete-button"
  337. text="删除"
  338. @click="deleteRow(item)"
  339. />
  340. </template>
  341. </van-swipe-cell>
  342. </div>
  343. </van-list>
  344. </van-pull-refresh>
  345. </div>
  346. </div>
  347. </template>
  348. <style scoped>
  349. .page-container {
  350. height: 100vh;
  351. display: flex;
  352. flex-direction: column;
  353. }
  354. .query-area {
  355. background-color: #f8f9fa;
  356. padding: 12px;
  357. border-bottom: 1px solid #ebedf0;
  358. }
  359. .query-buttons {
  360. display: flex;
  361. gap: 8px;
  362. margin-top: 12px;
  363. }
  364. .query-buttons .van-button {
  365. flex: 1;
  366. }
  367. .scroll-container {
  368. flex: 1;
  369. overflow: auto;
  370. -webkit-overflow-scrolling: touch;
  371. }
  372. .scroll-container::-webkit-scrollbar {
  373. display: none;
  374. }
  375. .card {
  376. margin: 10px;
  377. border-radius: 8px;
  378. overflow: hidden;
  379. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  380. background-color: #fff;
  381. }
  382. .card-title {
  383. font-weight: bold;
  384. color: #333;
  385. }
  386. .title-row {
  387. display: flex;
  388. align-items: center;
  389. margin-bottom: 8px;
  390. }
  391. .card-content {
  392. margin-top: 8px;
  393. }
  394. .content-row {
  395. display: flex;
  396. align-items: center;
  397. margin-bottom: 8px;
  398. font-size: 14px;
  399. }
  400. .label {
  401. color: #969799;
  402. margin-right: 8px;
  403. min-width: 80px;
  404. }
  405. .value {
  406. color: #323233;
  407. font-weight: 500;
  408. }
  409. .months-grid {
  410. display: grid;
  411. grid-template-columns: repeat(3, 1fr);
  412. gap: 8px;
  413. margin-top: 12px;
  414. padding-top: 12px;
  415. border-top: 1px solid #ebedf0;
  416. }
  417. .month-item {
  418. display: flex;
  419. flex-direction: column;
  420. font-size: 12px;
  421. }
  422. .month-label {
  423. color: #969799;
  424. margin-bottom: 4px;
  425. }
  426. .month-value {
  427. color: #323233;
  428. font-weight: 500;
  429. }
  430. .delete-button {
  431. height: 100%;
  432. border: none;
  433. color: #fff;
  434. background-color: #ee0a24;
  435. font-size: 16px;
  436. padding: 0 20px;
  437. }
  438. </style>