list.vue 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <script setup lang="ts">
  2. import { reactive, onMounted, ref, nextTick } from "vue";
  3. import { useRouter } from "vue-router";
  4. import { getStationList, removeStation } from "@/api/station";
  5. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  6. import { ElMessage, ElMessageBox } from "element-plus";
  7. import { ExtDLabel, ExtDSelect } from "@/components/ExtForm";
  8. import StationDialog from "./dialog.vue";
  9. defineOptions({
  10. name: "AdminStationList"
  11. });
  12. const router = useRouter();
  13. const queryRef = ref();
  14. const tableRef = ref();
  15. const dialogRef = ref();
  16. const qrDialogVisible = ref(false);
  17. const currentQrCode = ref("");
  18. const currentStationName = ref("");
  19. const state = reactive({
  20. formQuery: {
  21. stationName: "",
  22. stationStatus: "",
  23. stationType: "",
  24. address: ""
  25. },
  26. pageQuery: {
  27. pageNum: 1,
  28. pageSize: 10,
  29. total: 0
  30. },
  31. tableData: {
  32. height: 500,
  33. data: [] as Array<any>,
  34. loading: false,
  35. columns: [
  36. { label: "站点名称", prop: "stationName", width: 200 },
  37. { label: "站点照片", prop: "pictures", width: 120 },
  38. { label: "站点状态", prop: "stationStatus", width: 100 },
  39. { label: "站点类型", prop: "stationType", width: 100 },
  40. { label: "地址", prop: "address", width: 250 },
  41. { label: "服务电话", prop: "serviceTel", width: 130 },
  42. { label: "营业时间", prop: "businessHours", width: 150 },
  43. { label: "工位数量", prop: "parkingNum", width: 90 },
  44. { label: "停车费用", prop: "parkingFee", width: 200 },
  45. { label: "停车减免二维码", prop: "parkingQrCode", width: 150 },
  46. { label: "创建时间", prop: "createTime", width: 160 }
  47. ]
  48. }
  49. });
  50. onMounted(() => {
  51. loadData();
  52. nextTick(() => {
  53. const bodyHeight = document.body.clientHeight;
  54. const queryHeight = queryRef.value?.$el?.clientHeight || 0;
  55. state.tableData.height = bodyHeight - queryHeight - 280;
  56. });
  57. });
  58. const loadData = (refresh: boolean = false) => {
  59. if (refresh) {
  60. state.pageQuery.pageNum = 1;
  61. }
  62. state.tableData.loading = true;
  63. getStationList({ ...state.formQuery, ...state.pageQuery })
  64. .then((res: any) => {
  65. const { list, total } = res || {};
  66. state.tableData.data = list || [];
  67. state.pageQuery.total = total || 0;
  68. })
  69. .catch(() => {
  70. state.tableData.data = [];
  71. ElMessage.error("加载站点列表失败");
  72. })
  73. .finally(() => {
  74. state.tableData.loading = false;
  75. });
  76. };
  77. const handleSizeChange = (size: number) => {
  78. state.pageQuery.pageSize = size;
  79. loadData(true);
  80. };
  81. const handleCurrentChange = (page: number) => {
  82. state.pageQuery.pageNum = page;
  83. loadData();
  84. };
  85. const handleSearch = () => {
  86. loadData(true);
  87. };
  88. const handleReset = () => {
  89. state.formQuery = {
  90. stationName: "",
  91. stationStatus: "",
  92. stationType: "",
  93. address: ""
  94. };
  95. loadData(true);
  96. };
  97. const handleView = (row: any) => {
  98. dialogRef.value?.open("view", row);
  99. };
  100. const handleEdit = (row: any) => {
  101. dialogRef.value?.open("edit", row);
  102. };
  103. const handleAdd = () => {
  104. dialogRef.value?.open("add");
  105. };
  106. const handleDelete = (row: any) => {
  107. ElMessageBox.confirm(`此操作将永久删除站点『${row.stationName}』,是否继续?`, "提示", {
  108. confirmButtonText: "确定",
  109. cancelButtonText: "取消",
  110. type: "warning"
  111. }).then(() => {
  112. removeStation(row.id)
  113. .then(() => {
  114. ElMessage.success("删除成功");
  115. loadData(true);
  116. })
  117. .catch(() => {
  118. ElMessage.error("删除失败,请稍后重试");
  119. });
  120. });
  121. };
  122. const handleShowQrCode = (row: any) => {
  123. currentQrCode.value = row.parkingQrCode;
  124. currentStationName.value = row.stationName;
  125. qrDialogVisible.value = true;
  126. };
  127. const handleGotoDevice = (row: any) => {
  128. router.push(`/admin/station/device?stationId=${row.stationId}`);
  129. };
  130. </script>
  131. <template>
  132. <div class="page-container">
  133. <el-card shadow="hover">
  134. <template #header>
  135. <span class="card-header">站点清单</span>
  136. </template>
  137. <el-form
  138. ref="queryRef"
  139. :model="state.formQuery"
  140. inline
  141. class="search-form"
  142. >
  143. <el-form-item label="站点名称">
  144. <el-input
  145. v-model="state.formQuery.stationName"
  146. placeholder="请输入站点名称"
  147. clearable
  148. @keyup.enter="handleSearch"
  149. />
  150. </el-form-item>
  151. <el-form-item label="站点状态">
  152. <ExtDSelect
  153. v-model="state.formQuery.stationStatus"
  154. type="Station.status"
  155. placeholder="请选择站点状态"
  156. @on-change="handleSearch"
  157. />
  158. </el-form-item>
  159. <el-form-item label="站点类型">
  160. <ExtDSelect
  161. v-model="state.formQuery.stationType"
  162. type="Station.type"
  163. placeholder="请选择站点类型"
  164. @on-change="handleSearch"
  165. />
  166. </el-form-item>
  167. <el-form-item label="地址">
  168. <el-input
  169. v-model="state.formQuery.address"
  170. placeholder="请输入地址"
  171. clearable
  172. @keyup.enter="handleSearch"
  173. />
  174. </el-form-item>
  175. <el-form-item>
  176. <el-button
  177. type="primary"
  178. :icon="useRenderIcon('ri/search-line')"
  179. @click="handleSearch"
  180. >
  181. 查询
  182. </el-button>
  183. <el-button
  184. :icon="useRenderIcon('ri/refresh-line')"
  185. @click="handleReset"
  186. >
  187. 重置
  188. </el-button>
  189. <el-button
  190. type="success"
  191. :icon="useRenderIcon('ri/add-line')"
  192. @click="handleAdd"
  193. >
  194. 新增
  195. </el-button>
  196. </el-form-item>
  197. </el-form>
  198. <el-table
  199. ref="tableRef"
  200. v-loading="state.tableData.loading"
  201. :data="state.tableData.data"
  202. :height="state.tableData.height"
  203. border
  204. stripe
  205. >
  206. <template #empty>
  207. <el-empty description="暂无站点数据,点击「新增」按钮添加站点" />
  208. </template>
  209. <el-table-column
  210. v-for="col in state.tableData.columns"
  211. :key="col.prop"
  212. :prop="col.prop"
  213. :label="col.label"
  214. :width="col.width"
  215. show-overflow-tooltip
  216. >
  217. <template #default="{ row }">
  218. <template v-if="col.prop === 'stationName'">
  219. <div class="station-name-cell">
  220. <span class="station-id">{{ row.stationId }}</span>
  221. <span class="station-divider"></span>
  222. <span class="station-name">{{ row.stationName }}</span>
  223. </div>
  224. </template>
  225. <template v-else-if="col.prop === 'pictures'">
  226. <el-image
  227. v-if="row.pictures && row.pictures.length > 0"
  228. :src="row.pictures[0]"
  229. :preview-src-list="row.pictures"
  230. style="width: 60px; height: 60px"
  231. fit="cover"
  232. />
  233. <span v-else>-</span>
  234. </template>
  235. <template v-else-if="col.prop === 'stationStatus'">
  236. <ExtDLabel type="Station.status" :model-value="row.stationStatus" />
  237. </template>
  238. <template v-else-if="col.prop === 'stationType'">
  239. <ExtDLabel type="Station.type" :model-value="row.stationType" />
  240. </template>
  241. <template v-else-if="col.prop === 'parkingQrCode'">
  242. <el-button
  243. v-if="row.parkingQrCode"
  244. type="primary"
  245. link
  246. size="small"
  247. @click="handleShowQrCode(row)"
  248. >
  249. 查看二维码
  250. </el-button>
  251. <span v-else>-</span>
  252. </template>
  253. <template v-else>
  254. {{ row[col.prop] }}
  255. </template>
  256. </template>
  257. </el-table-column>
  258. <el-table-column label="操作" width="280" fixed="right">
  259. <template #default="{ row }">
  260. <el-button type="primary" link size="small" @click="handleGotoDevice(row)">
  261. 设备清单
  262. </el-button>
  263. <el-button type="primary" link size="small" @click="handleView(row)">
  264. 详情
  265. </el-button>
  266. <el-button type="warning" link size="small" @click="handleEdit(row)">
  267. 编辑
  268. </el-button>
  269. <el-button type="danger" link size="small" @click="handleDelete(row)">
  270. 删除
  271. </el-button>
  272. </template>
  273. </el-table-column>
  274. </el-table>
  275. <div class="pagination-container">
  276. <el-pagination
  277. v-model:current-page="state.pageQuery.pageNum"
  278. v-model:page-size="state.pageQuery.pageSize"
  279. :total="state.pageQuery.total"
  280. :page-sizes="[10, 20, 50, 100]"
  281. layout="total, sizes, prev, pager, next, jumper"
  282. @size-change="handleSizeChange"
  283. @current-change="handleCurrentChange"
  284. />
  285. </div>
  286. </el-card>
  287. <StationDialog ref="dialogRef" @refresh="loadData" />
  288. <el-dialog
  289. v-model="qrDialogVisible"
  290. :title="`${currentStationName} - 停车减免二维码`"
  291. width="400px"
  292. >
  293. <div class="qr-code-container">
  294. <img :src="currentQrCode" alt="停车减免二维码" style="width: 100%" />
  295. </div>
  296. </el-dialog>
  297. </div>
  298. </template>
  299. <style scoped lang="scss">
  300. .page-container {
  301. padding: 20px;
  302. }
  303. .card-header {
  304. font-size: 16px;
  305. font-weight: 600;
  306. }
  307. .search-form {
  308. margin-bottom: 16px;
  309. }
  310. .pagination-container {
  311. display: flex;
  312. justify-content: flex-end;
  313. margin-top: 20px;
  314. }
  315. .station-name-cell {
  316. display: flex;
  317. flex-direction: column;
  318. align-items: center;
  319. gap: 4px;
  320. .station-id {
  321. color: var(--el-text-color-secondary);
  322. font-size: 12px;
  323. }
  324. .station-divider {
  325. display: block;
  326. width: 40px;
  327. height: 1px;
  328. background: var(--el-border-color-light);
  329. }
  330. .station-name {
  331. font-weight: 500;
  332. }
  333. }
  334. .qr-code-container {
  335. display: flex;
  336. justify-content: center;
  337. align-items: center;
  338. padding: 20px;
  339. }
  340. </style>