index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. <script setup lang="ts">
  2. import { onMounted, reactive, ref } from 'vue'
  3. import { clientDownloadExcel, clientGet, clientPost } from '@/utils/request.ts'
  4. import type { FormInstance, UploadProps } from 'element-plus'
  5. import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
  6. import { Delete, Download, Edit, Plus, Refresh, Search, Upload } from '@element-plus/icons-vue'
  7. // 基础类型定义
  8. interface BaseResponse {
  9. code: number
  10. msg: string
  11. }
  12. interface PageType<T> {
  13. records: T[]
  14. total: number
  15. size: number
  16. current: number
  17. pages: number
  18. }
  19. // 燃气信息相关接口定义
  20. interface Property {
  21. id: string // 主键id
  22. company?: string // 企业名称
  23. uniCode?: string // 统一社会信用代码
  24. tableName?: string // 事项名称
  25. tableCode?: string // 事项编码
  26. userInfo?: string // 燃气用户信息(户名户号)
  27. paymentDetail?: string // 缴纳燃气费明细
  28. monthlyConsumption3?: number // 近3个月月均燃气用量
  29. monthlyConsumption6?: number // 近6个月月均燃气用量
  30. isInArrears?: string // 当前是否欠费
  31. entityZtlb?: string // 类别
  32. openDate?: string // 开户日期 (改为string用于显示)
  33. userAddress?: string // 用户地址
  34. advanceAmount?: string // 预交金额
  35. arrearageAmount?: string // 欠费金额
  36. onehouseOnemeter?: string // 是否一户一表
  37. paymentPeriod?: string // 缴费所属期
  38. statisticalDate?: string // 统计日期
  39. remark?: string // 备注
  40. createTime?: string // 创建时间 (改为string用于显示)
  41. createBy?: string // 创建用户
  42. updateTime?: string // 更新时间 (改为string用于显示)
  43. updateBy?: string // 更新用户
  44. }
  45. interface PropertyOneResponse extends BaseResponse {
  46. data: Property
  47. }
  48. interface PropertyListResponse extends BaseResponse {
  49. data: PageType<Property>
  50. }
  51. interface AddProperty {
  52. company?: string // 企业名称
  53. uniCode?: string // 统一社会信用代码
  54. tableName?: string // 事项名称
  55. tableCode?: string // 事项编码
  56. userInfo?: string // 燃气用户信息(户名户号)
  57. paymentDetail?: string // 缴纳燃气费明细
  58. monthlyConsumption3?: number // 近3个月月均燃气用量
  59. monthlyConsumption6?: number // 近6个月月均燃气用量
  60. isInArrears?: string // 当前是否欠费
  61. entityZtlb?: string // 类别
  62. openDate?: string // 开户日期
  63. userAddress?: string // 用户地址
  64. advanceAmount?: string // 预交金额
  65. arrearageAmount?: string // 欠费金额
  66. onehouseOnemeter?: string // 是否一户一表
  67. paymentPeriod?: string // 缴费所属期
  68. statisticalDate?: string // 统计日期
  69. remark?: string // 备注
  70. }
  71. interface UpdateProperty {
  72. id?: string
  73. company?: string // 企业名称
  74. uniCode?: string // 统一社会信用代码
  75. tableName?: string // 事项名称
  76. tableCode?: string // 事项编码
  77. userInfo?: string // 燃气用户信息(户名户号)
  78. paymentDetail?: string // 缴纳燃气费明细
  79. monthlyConsumption3?: number // 近3个月月均燃气用量
  80. monthlyConsumption6?: number // 近6个月月均燃气用量
  81. isInArrears?: string // 当前是否欠费
  82. entityZtlb?: string // 类别
  83. openDate?: string // 开户日期
  84. userAddress?: string // 用户地址
  85. advanceAmount?: string // 预交金额
  86. arrearageAmount?: string // 欠费金额
  87. onehouseOnemeter?: string // 是否一户一表
  88. paymentPeriod?: string // 缴费所属期
  89. statisticalDate?: string // 统计日期
  90. remark?: string // 备注
  91. }
  92. // 响应式状态变量
  93. const tableData = ref<Property[]>([])
  94. const total = ref(0)
  95. const pageSize = ref(10)
  96. const pageNum = ref(1)
  97. const searchForm = reactive({
  98. userInfo: '',
  99. company: '',
  100. })
  101. const dialogVisible = ref(false)
  102. const dialogTitle = ref('')
  103. const isEdit = ref(false)
  104. const formData = reactive<Property>({})
  105. const formRef = ref<FormInstance>()
  106. const selectedIds = ref<string[]>([])
  107. // 欠费状态选项
  108. const arrearOptions = [
  109. { label: '是', value: '是' },
  110. { label: '否', value: '否' },
  111. ]
  112. // 一户一表选项
  113. const onehouseOptions = [
  114. { label: '是', value: '是' },
  115. { label: '否', value: '否' },
  116. ]
  117. // 获取欠费状态标签
  118. const getArrearLabel = (value: string | undefined) => {
  119. return arrearOptions.find((option) => option.value === value)?.label || '-'
  120. }
  121. // 获取一户一表标签
  122. const getOnehouseLabel = (value: string | undefined) => {
  123. return onehouseOptions.find((option) => option.value === value)?.label || '-'
  124. }
  125. // 接口调用函数
  126. const add = async (data: AddProperty) => {
  127. const res = await clientPost<AddProperty, BaseResponse>('/egas/save', {
  128. ...data,
  129. })
  130. if (res.code !== 200) {
  131. ElMessage.error(res.msg)
  132. return false
  133. }
  134. ElMessage.success(res.msg)
  135. return true
  136. }
  137. const getById = async (id: string) => {
  138. const res = await clientGet<null, PropertyOneResponse>('/egas/getById/' + id)
  139. if (res.code !== 200) {
  140. ElMessage.error(res.msg)
  141. return null
  142. }
  143. return res.data
  144. }
  145. const getList = async () => {
  146. const loading = ElLoading.service({ text: '加载中...' })
  147. try {
  148. const res = await clientGet<
  149. {
  150. pageNum: number
  151. pageSize: number
  152. userInfo?: string
  153. company?: string
  154. },
  155. PropertyListResponse
  156. >('/egas/findByPage', {
  157. params: {
  158. pageNum: pageNum.value,
  159. pageSize: pageSize.value,
  160. userInfo: searchForm.userInfo || undefined,
  161. company: searchForm.company || undefined,
  162. },
  163. })
  164. if (res.code !== 200) {
  165. ElMessage.error(res.msg)
  166. return
  167. }
  168. tableData.value = res.data.records
  169. total.value = res.data.total
  170. } catch (error) {
  171. ElMessage.error('获取数据失败')
  172. } finally {
  173. loading.close()
  174. }
  175. }
  176. const delBatch = async (ids: string[]) => {
  177. const res = await clientPost<string, BaseResponse>('/egas/deleteBatch', JSON.stringify(ids))
  178. if (res.code !== 200) {
  179. ElMessage.error(res.msg)
  180. return false
  181. }
  182. ElMessage.success(res.msg)
  183. return true
  184. }
  185. const update = async (data: UpdateProperty) => {
  186. const res = await clientPost<UpdateProperty, BaseResponse>('/egas/update', {
  187. ...data,
  188. })
  189. if (res.code !== 200) {
  190. ElMessage.error(res.msg)
  191. return false
  192. }
  193. ElMessage.success(res.msg)
  194. return true
  195. }
  196. const exportExcel = async () => {
  197. const loading = ElLoading.service({
  198. lock: true,
  199. text: '导出中,请稍候...',
  200. background: 'rgba(0, 0, 0, 0.1)',
  201. fullscreen: true,
  202. })
  203. try {
  204. await clientDownloadExcel('/egas/exportData', {
  205. params: {
  206. userInfo: searchForm.userInfo || undefined,
  207. company: searchForm.company || undefined,
  208. },
  209. })
  210. ElMessage.success('导出成功')
  211. } catch (error) {
  212. console.error('导出失败:', error)
  213. ElMessage.error('导出失败')
  214. } finally {
  215. loading.close()
  216. }
  217. }
  218. const importExcel = async (file: File) => {
  219. const formData = new FormData()
  220. formData.append('file', file)
  221. const res = await clientPost<FormData, BaseResponse>('/egas/importData', formData, {
  222. headers: {
  223. 'Content-Type': 'multipart/form-data',
  224. },
  225. })
  226. if (res.code !== 200) {
  227. ElMessage.error(res.msg)
  228. return false
  229. }
  230. ElMessage.success(res.msg)
  231. return true
  232. }
  233. // 事件处理函数
  234. const handleAdd = () => {
  235. isEdit.value = false
  236. dialogTitle.value = '新增燃气信息'
  237. // 重置表单数据
  238. Object.keys(formData).forEach((key) => delete formData[key as keyof Property])
  239. dialogVisible.value = true
  240. }
  241. const handleEdit = async (row: Property) => {
  242. isEdit.value = true
  243. dialogTitle.value = '编辑燃气信息'
  244. const data = await getById(row.id)
  245. if (data) {
  246. Object.assign(formData, data)
  247. dialogVisible.value = true
  248. }
  249. }
  250. const handleDelete = async (id: string) => {
  251. ElMessageBox.confirm('确定删除此条记录吗?', '提示', {
  252. confirmButtonText: '确定',
  253. cancelButtonText: '取消',
  254. type: 'warning',
  255. })
  256. .then(async () => {
  257. const success = await delBatch([id])
  258. if (success) {
  259. getList()
  260. }
  261. })
  262. .catch(() => {
  263. ElMessage.info('已取消删除')
  264. })
  265. }
  266. const handleBatchDelete = () => {
  267. if (selectedIds.value.length === 0) {
  268. ElMessage.warning('请选择要删除的记录')
  269. return
  270. }
  271. ElMessageBox.confirm(`确定删除选中的 ${selectedIds.value.length} 条记录吗?`, '提示', {
  272. confirmButtonText: '确定',
  273. cancelButtonText: '取消',
  274. type: 'warning',
  275. })
  276. .then(async () => {
  277. const success = await delBatch(selectedIds.value)
  278. if (success) {
  279. getList()
  280. }
  281. })
  282. .catch(() => {
  283. ElMessage.info('已取消批量删除')
  284. })
  285. }
  286. const handleSearch = () => {
  287. pageNum.value = 1
  288. getList()
  289. }
  290. const handleResetSearch = () => {
  291. searchForm.userInfo = ''
  292. searchForm.company = ''
  293. pageNum.value = 1
  294. getList()
  295. }
  296. const handleConfirm = async () => {
  297. if (!formRef.value) return
  298. await formRef.value.validate(async (valid) => {
  299. if (valid) {
  300. let success = false
  301. const dataToSend = { ...formData }
  302. if (isEdit.value) {
  303. success = await update(dataToSend)
  304. } else {
  305. success = await add(dataToSend)
  306. }
  307. if (success) {
  308. dialogVisible.value = false
  309. getList()
  310. }
  311. } else {
  312. ElMessage.error('请检查表单填写')
  313. }
  314. })
  315. }
  316. const handleCurrentChange = (val: number) => {
  317. pageNum.value = val
  318. getList()
  319. }
  320. const handleSizeChange = (val: number) => {
  321. pageSize.value = val
  322. pageNum.value = 1
  323. getList()
  324. }
  325. const handleSelectionChange = (selection: Property[]) => {
  326. selectedIds.value = selection.map((item) => item.id)
  327. }
  328. const handleUploadSuccess: UploadProps['onSuccess'] = async (response) => {
  329. if (response && response.code === 200) {
  330. ElMessage.success('文件导入成功')
  331. getList()
  332. } else {
  333. ElMessage.error(`文件导入失败: ${response?.msg || '未知错误'}`)
  334. }
  335. }
  336. const handleUploadError: UploadProps['onError'] = (error) => {
  337. ElMessage.error(`文件导入失败: ${error.message}`)
  338. }
  339. const handleBeforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
  340. const isExcel =
  341. rawFile.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
  342. rawFile.type === 'application/vnd.ms-excel'
  343. if (!isExcel) {
  344. ElMessage.error('导入文件只能是 Excel 格式!')
  345. return false
  346. }
  347. return true
  348. }
  349. // 初始化
  350. const init = () => {
  351. getList()
  352. }
  353. onMounted(() => {
  354. init()
  355. })
  356. </script>
  357. <template>
  358. <div class="p-4">
  359. <h1 class="text-2xl font-bold mb-6">燃气信息管理</h1>
  360. <!-- 搜索和操作栏 -->
  361. <div class="mb-6 p-4 bg-white rounded-lg shadow-sm flex flex-wrap items-center gap-4">
  362. <el-form :inline="true" :model="searchForm" class="flex-grow flex flex-wrap gap-x-4">
  363. <el-form-item label="燃气用户信息">
  364. <el-input v-model="searchForm.userInfo" placeholder="输入户名或户号" clearable />
  365. </el-form-item>
  366. <el-form-item label="企业名称">
  367. <el-input v-model="searchForm.company" placeholder="输入企业名称" clearable />
  368. </el-form-item>
  369. <el-form-item>
  370. <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
  371. <el-button :icon="Refresh" @click="handleResetSearch">重置</el-button>
  372. </el-form-item>
  373. </el-form>
  374. <div class="flex gap-2">
  375. <el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
  376. <el-button
  377. type="danger"
  378. :icon="Delete"
  379. @click="handleBatchDelete"
  380. :disabled="selectedIds.length === 0"
  381. >批量删除</el-button
  382. >
  383. <el-button type="success" :icon="Download" @click="exportExcel">导出</el-button>
  384. <el-upload
  385. class="inline-block"
  386. action="/api/egas/importData"
  387. :show-file-list="false"
  388. :on-success="handleUploadSuccess"
  389. :on-error="handleUploadError"
  390. :before-upload="handleBeforeUpload"
  391. :http-request="(options) => importExcel(options.file)"
  392. >
  393. <el-button type="info" :icon="Upload">导入</el-button>
  394. </el-upload>
  395. </div>
  396. </div>
  397. <!-- 表格 -->
  398. <div class="bg-white rounded-lg shadow-sm p-4 overflow-x-auto">
  399. <el-table
  400. :data="tableData"
  401. style="width: 100%"
  402. border
  403. @selection-change="handleSelectionChange"
  404. >
  405. <el-table-column type="selection" width="55" fixed="left" />
  406. <el-table-column prop="company" label="企业名称" width="180" />
  407. <el-table-column prop="uniCode" label="统一社会信用代码" width="200" />
  408. <el-table-column prop="userInfo" label="燃气用户信息" width="180" />
  409. <el-table-column prop="isInArrears" label="是否欠费" width="100">
  410. <template #default="{ row }">
  411. <el-tag :type="row.isInArrears === '是' ? 'danger' : 'success'" v-if="row.isInArrears">
  412. {{ row.isInArrears }}
  413. </el-tag>
  414. <el-tag type="info" v-else>未知</el-tag>
  415. </template>
  416. </el-table-column>
  417. <el-table-column prop="arrearageAmount" label="欠费金额" width="120" />
  418. <el-table-column prop="monthlyConsumption3" label="近3个月月均用量" width="150" />
  419. <el-table-column prop="monthlyConsumption6" label="近6个月月均用量" width="150" />
  420. <el-table-column prop="onehouseOnemeter" label="是否一户一表" width="120">
  421. <template #default="{ row }">
  422. <el-tag
  423. :type="row.onehouseOnemeter === '是' ? 'success' : 'info'"
  424. v-if="row.onehouseOnemeter"
  425. >
  426. {{ row.onehouseOnemeter }}
  427. </el-tag>
  428. <el-tag type="info" v-else>未知</el-tag>
  429. </template>
  430. </el-table-column>
  431. <el-table-column prop="openDate" label="开户日期" width="150" />
  432. <el-table-column prop="userAddress" label="用户地址" width="200" />
  433. <el-table-column label="操作" width="180" fixed="right">
  434. <template #default="{ row }">
  435. <el-button :icon="Edit" size="small" @click="handleEdit(row)">编辑</el-button>
  436. <el-button type="danger" :icon="Delete" size="small" @click="handleDelete(row.id)"
  437. >删除</el-button
  438. >
  439. </template>
  440. </el-table-column>
  441. </el-table>
  442. <!-- 分页 -->
  443. <div class="mt-4 flex justify-end">
  444. <el-pagination
  445. @size-change="handleSizeChange"
  446. @current-change="handleCurrentChange"
  447. :current-page="pageNum"
  448. :page-sizes="[10, 20, 50, 100]"
  449. :page-size="pageSize"
  450. layout="total, sizes, prev, pager, next, jumper"
  451. :total="total"
  452. />
  453. </div>
  454. </div>
  455. <!-- 新增/编辑对话框 -->
  456. <el-dialog
  457. v-model="dialogVisible"
  458. :title="dialogTitle"
  459. width="800px"
  460. :close-on-click-modal="false"
  461. :close-on-press-escape="false"
  462. >
  463. <el-form
  464. :model="formData"
  465. ref="formRef"
  466. label-width="120px"
  467. class="grid grid-cols-1 md:grid-cols-2 gap-4"
  468. >
  469. <el-form-item
  470. label="企业名称"
  471. prop="company"
  472. :rules="[{ required: true, message: '请输入企业名称', trigger: 'blur' }]"
  473. >
  474. <el-input v-model="formData.company" placeholder="请输入企业名称" />
  475. </el-form-item>
  476. <el-form-item label="统一社会信用代码" prop="uniCode">
  477. <el-input v-model="formData.uniCode" placeholder="请输入统一社会信用代码" />
  478. </el-form-item>
  479. <el-form-item label="事项名称" prop="tableName">
  480. <el-input v-model="formData.tableName" placeholder="请输入事项名称" />
  481. </el-form-item>
  482. <el-form-item label="事项编码" prop="tableCode">
  483. <el-input v-model="formData.tableCode" placeholder="请输入事项编码" />
  484. </el-form-item>
  485. <el-form-item
  486. label="燃气用户信息"
  487. prop="userInfo"
  488. :rules="[{ required: true, message: '请输入燃气用户信息', trigger: 'blur' }]"
  489. >
  490. <el-input v-model="formData.userInfo" placeholder="请输入户名户号" />
  491. </el-form-item>
  492. <el-form-item label="缴纳燃气费明细" prop="paymentDetail">
  493. <el-input v-model="formData.paymentDetail" placeholder="请输入缴纳燃气费明细" />
  494. </el-form-item>
  495. <el-form-item label="近3个月月均用量" prop="monthlyConsumption3">
  496. <el-input
  497. v-model.number="formData.monthlyConsumption3"
  498. placeholder="请输入近3个月月均用量"
  499. />
  500. </el-form-item>
  501. <el-form-item label="近6个月月均用量" prop="monthlyConsumption6">
  502. <el-input
  503. v-model.number="formData.monthlyConsumption6"
  504. placeholder="请输入近6个月月均用量"
  505. />
  506. </el-form-item>
  507. <el-form-item label="当前是否欠费" prop="isInArrears">
  508. <el-radio-group v-model="formData.isInArrears">
  509. <el-radio :label="'是'">是</el-radio>
  510. <el-radio :label="'否'">否</el-radio>
  511. </el-radio-group>
  512. </el-form-item>
  513. <el-form-item label="欠费金额" prop="arrearageAmount">
  514. <el-input v-model="formData.arrearageAmount" placeholder="请输入欠费金额" />
  515. </el-form-item>
  516. <el-form-item label="类别" prop="entityZtlb">
  517. <el-input v-model="formData.entityZtlb" placeholder="请输入类别" />
  518. </el-form-item>
  519. <el-form-item label="开户日期" prop="openDate">
  520. <el-date-picker
  521. v-model="formData.openDate"
  522. type="date"
  523. placeholder="选择开户日期"
  524. value-format="YYYY-MM-DD"
  525. style="width: 100%"
  526. />
  527. </el-form-item>
  528. <el-form-item label="用户地址" prop="userAddress">
  529. <el-input v-model="formData.userAddress" placeholder="请输入用户地址" />
  530. </el-form-item>
  531. <el-form-item label="是否一户一表" prop="onehouseOnemeter">
  532. <el-radio-group v-model="formData.onehouseOnemeter">
  533. <el-radio :label="'是'">是</el-radio>
  534. <el-radio :label="'否'">否</el-radio>
  535. </el-radio-group>
  536. </el-form-item>
  537. <el-form-item label="缴费所属期" prop="paymentPeriod">
  538. <el-input v-model="formData.paymentPeriod" placeholder="请输入缴费所属期" />
  539. </el-form-item>
  540. <el-form-item label="统计日期" prop="statisticalDate">
  541. <el-date-picker
  542. v-model="formData.statisticalDate"
  543. type="date"
  544. placeholder="选择统计日期"
  545. value-format="YYYY-MM-DD"
  546. style="width: 100%"
  547. />
  548. </el-form-item>
  549. <el-form-item label="备注" prop="remark">
  550. <el-input v-model="formData.remark" placeholder="请输入备注" type="textarea" :rows="2" />
  551. </el-form-item>
  552. </el-form>
  553. <template #footer>
  554. <span class="dialog-footer">
  555. <el-button @click="dialogVisible = false">取消</el-button>
  556. <el-button type="primary" @click="handleConfirm">确定</el-button>
  557. </span>
  558. </template>
  559. </el-dialog>
  560. </div>
  561. </template>
  562. <style scoped>
  563. /* 如有特定样式需求,可在此添加,否则UnoCSS会处理大部分样式 */
  564. </style>