Selaa lähdekoodia

@author liuqing
@commit 修改架构为基于 Wails v2 构建的单进程桌面应用

lq 1 kuukausi sitten
vanhempi
commit
26af645cbd

+ 12 - 0
.gitignore

@@ -32,3 +32,15 @@ yarn-error.log*
 *.iml
 web/.pnpm-debug.log
 web/pnpm-lock.yaml
+
+# Wails build output
+/build/
+/frontend/wailsjs/
+/frontend/package.json.md5
+
+# Go binary
+/server/server.exe
+/server/server.exe~
+
+# Runtime
+/log/

+ 222 - 0
config.yaml

@@ -0,0 +1,222 @@
+aliyun-oss:
+    endpoint: yourEndpoint
+    access-key-id: yourAccessKeyId
+    access-key-secret: yourAccessKeySecret
+    bucket-name: yourBucketName
+    bucket-url: yourBucketUrl
+    base-path: yourBasePath
+autocode:
+    server-model: /model/%s
+    server-router: /router/%s
+    server: /server
+    server-api: /api/v1/%s
+    server-plug: /plugin/%s
+    server-initialize: /initialize
+    root: E:\GoWorks\src\lc_finance
+    web-table: /view
+    web: /web/src
+    server-service: /service/%s
+    server-request: /model/%s/request/
+    web-api: /api
+    web-form: /view
+    transfer-restart: true
+aws-s3:
+    bucket: xxxxx-10005608
+    region: ap-shanghai
+    endpoint: ""
+    secret-id: your-secret-id
+    secret-key: your-secret-key
+    base-url: https://gin.vue.admin
+    path-prefix: github.com/flipped-aurora/gin-vue-admin/server
+    s3-force-path-style: false
+    disable-ssl: false
+captcha:
+    key-long: 6
+    img-width: 240
+    img-height: 80
+    open-captcha: 0
+    open-captcha-timeout: 3600
+cors:
+    mode: allow-all
+    whitelist:
+        - allow-origin: example1.com
+          allow-methods: POST, GET
+          allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
+          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+          allow-credentials: true
+        - allow-origin: example2.com
+          allow-methods: GET, POST
+          allow-headers: content-type
+          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+          allow-credentials: true
+db-list:
+    - type: ""
+      alias-name: ""
+      prefix: ""
+      port: ""
+      config: ""
+      db-name: ""
+      username: ""
+      password: ""
+      path: ""
+      engine: ""
+      log-mode: ""
+      max-idle-conns: 10
+      max-open-conns: 100
+      singular: false
+      log-zap: false
+      disable: true
+email:
+    to: xxx@qq.com
+    from: xxx@163.com
+    host: smtp.163.com
+    secret: xxx
+    nickname: test
+    port: 465
+    is-ssl: true
+excel:
+    dir: ./resource/excel/
+hua-wei-obs:
+    path: you-path
+    bucket: you-bucket
+    endpoint: you-endpoint
+    access-key: you-access-key
+    secret-key: you-secret-key
+jwt:
+    signing-key: 23cb7052-8c96-4acf-9136-578347b2d4c8
+    expires-time: 7d
+    buffer-time: 1d
+    issuer: qmPlus
+local:
+    path: uploads/file
+    store-path: uploads/file
+mongo:
+    coll: ""
+    options: ""
+    database: ""
+    username: ""
+    password: ""
+    auth-source: ""
+    min-pool-size: 0
+    max-pool-size: 100
+    socket-timeout-ms: 0
+    connect-timeout-ms: 0
+    is-zap: false
+    hosts:
+        - host: ""
+          port: ""
+mssql:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+mysql:
+    prefix: ""
+    port: "3306"
+    config: charset=utf8mb4&parseTime=True&loc=Local
+    db-name: lc_garage
+    username: root
+    password: root
+
+    path: 127.0.0.1
+    engine: ""
+    log-mode: error
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+oracle:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+pgsql:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+qiniu:
+    zone: ZoneHuaDong
+    bucket: ""
+    img-path: ""
+    access-key: ""
+    secret-key: ""
+    use-https: false
+    use-cdn-domains: false
+redis:
+    addr: 127.0.0.1:6379
+    password: ""
+    db: 0
+    useCluster: false
+    clusterAddrs:
+        - 172.21.0.3:7000
+        - 172.21.0.4:7001
+        - 172.21.0.2:7002
+sqlite:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+system:
+    db-type: mysql
+    oss-type: local
+    router-prefix: ""
+    addr: 8888
+    iplimit-count: 15000
+    iplimit-time: 3600
+    use-multipoint: false
+    use-redis: false
+    use-mongo: false
+tencent-cos:
+    bucket: xxxxx-10005608
+    region: ap-shanghai
+    secret-id: your-secret-id
+    secret-key: your-secret-key
+    base-url: https://gin.vue.admin
+    path-prefix: github.com/flipped-aurora/gin-vue-admin/server
+zap:
+    level: info
+    prefix: '[github.com/flipped-aurora/gin-vue-admin/server]'
+    format: console
+    director: log
+    encode-level: LowercaseColorLevelEncoder
+    stacktrace-key: stacktrace
+    max-age: 0
+    show-line: true
+    log-in-console: true

+ 88 - 0
doc/PROJECT.md

@@ -0,0 +1,88 @@
+# 智慧停车管理系统 (Smart Parking) — Wails 桌面应用
+
+> **项目名称**:lc_garage
+> **框架**:Wails v2.13 + GIN-VUE-ADMIN v2.6.3
+> **最后更新**:2026-07-10
+
+---
+
+## 架构概述
+
+单进程 Wails 桌面应用:Go 后端(Gin HTTP)在 goroutine 运行,Vue 3 前端通过 WebView2 加载,`/api/*` 请求经内置反向代理转发到 Gin。
+
+```
+Wails .exe
+  ├─ goroutine: Gin HTTP Server (:8888)
+  └─ WebView2: Vue 3 SPA
+       └─ /api/* → reverse proxy → Gin (strip /api prefix)
+```
+
+---
+
+## 技术栈
+
+| 层级 | 技术 |
+|------|------|
+| 桌面框架 | Wails v2.13 |
+| 后端 | Go 1.25 + Gin + GORM + Casbin + JWT |
+| 前端 | Vue 3 + Vite 4 + Element Plus + Pinia + ECharts |
+| 数据库 | MySQL 5.7+(外部) |
+
+---
+
+## 项目结构
+
+```
+lc_garage/
+├── main.go                   # Wails 入口
+├── wails.json                # Wails 配置
+├── go.mod                    # 根模块 (wails-app)
+├── config.yaml               # 后端配置
+├── server/                   # Go 后端(独立模块 "server")
+├── frontend/                 # Vue 3 前端
+├── build/bin/                # 构建输出
+├── deploy/                   # Docker/K8s
+├── lc_garage.sql             # 数据库初始化
+└── doc/                      # 文档
+```
+
+---
+
+## 后端改动(相对于原始 GVA)
+
+| 文件 | 改动 | 说明 |
+|------|------|------|
+| `server/main.go` | `package main` → `package server`,导出 `InitBackend()` | 供 Wails 入口调用 |
+| `server/core/viper.go` | 新增 exe 目录 fallback | 双击 exe 时能找到 config.yaml |
+
+其余 `api/` `service/` `dao/` `router/` `middleware/` 等全部不变。
+
+---
+
+## 前端改动(相对于 lc_garage/web)
+
+| 文件 | 改动 |
+|------|------|
+| `view/parking/parkingInfo/` | 新增:停车场左列表+右Tab联动布局 |
+| `pinia/parkArea.js` | 新增:停车场联动状态 |
+| `view/layout/aside/index.vue` | 新增:角色菜单筛选(operator 隐藏 admin 菜单) |
+| `.env.production` | `VITE_BASE_API = /api`(Wails 内置代理处理) |
+| `vite.config.js` | Dev server 端口 5173 |
+
+---
+
+## 启动
+
+```bash
+# 开发
+cd lc_garage && wails dev
+
+# 构建
+cd lc_garage && wails build
+# 产物: build/bin/smart-parking.exe
+
+# 运行
+双击 build/bin/smart-parking.exe
+```
+
+管理员:`admin` / `123456`

+ 1 - 2
frontend/.env.production

@@ -1,7 +1,6 @@
 ENV = 'production'
 
-#下方为上线需要用到的程序代理前缀,一般用于nginx代理转发
+# Wails 生产模式:前端通过内置反向代理调后端
 VITE_BASE_API = /api
 VITE_FILE_API = /api
-#下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量)
 VITE_BASE_PATH = http://127.0.0.1

+ 15 - 0
frontend/src/pinia/parkArea.js

@@ -0,0 +1,15 @@
+import { defineStore } from 'pinia'
+
+export const useParkAreaStore = defineStore('parkArea', {
+  state: () => ({
+    parkId: 0,
+    parkList: [],
+  }),
+  actions: {
+    changeId(id) { this.parkId = id },
+    changeList(list) {
+      this.parkList = list
+      if (list.length > 0 && this.parkId === 0) this.parkId = list[0].ID
+    },
+  },
+})

+ 10 - 1
frontend/src/view/layout/aside/index.vue

@@ -16,7 +16,7 @@
           unique-opened
           @select="selectMenuItem"
         >
-          <template v-for="item in routerStore.asyncRouters[0].children">
+          <template v-for="item in filteredRoutes">
             <aside-component
               v-if="!item.hidden"
               :key="item.name"
@@ -39,10 +39,19 @@ import { useRoute, useRouter } from 'vue-router'
 import { useUserStore } from '@/pinia/modules/user'
 import { useRouterStore } from '@/pinia/modules/router'
 
+import { computed } from 'vue'
+
 defineOptions({
   name: 'Aside',
 })
 
+// Role-based menu filtering: operator (618) can't see superAdmin
+const filteredRoutes = computed(() => {
+  const routes = routerStore.asyncRouters[0]?.children || []
+  if (userStore.userInfo?.authorityId === 888) return routes // Admin sees all
+  return routes.filter(r => r.name !== 'superAdmin')
+})
+
 const route = useRoute()
 const router = useRouter()
 

+ 196 - 0
frontend/src/view/parking/parkingInfo/booth.vue

@@ -0,0 +1,196 @@
+<template>
+  <div v-loading="loading">
+    <el-form inline>
+      <el-form-item>
+        <el-button type="success" icon="Plus" @click="openDialog('add')">新增</el-button>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Refresh" @click="fetchData">刷新</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table :data="list" size="small" height="505" border stripe>
+      <el-table-column label="编码" align="center" width="120" prop="booth_code" />
+      <el-table-column label="名称" align="center" width="200" prop="booth_name" />
+      <el-table-column label="IP地址" align="center" width="200" prop="ip_address" />
+      <el-table-column label="备注" align="center" prop="description" />
+      <el-table-column label="操作" align="center" width="200" fixed="right">
+        <template #default="scope">
+          <el-button text type="primary" icon="Edit" @click="openDialog('edit', scope.row)">修改</el-button>
+          <el-button text type="primary" icon="Delete" @click="handleDelete(scope.row.ID)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div style="display: flex; justify-content: flex-end; margin-top: 10px">
+      <el-pagination
+        v-model:current-page="query.page"
+        v-model:page-size="query.page_size"
+        :page-sizes="[10, 30, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @current-change="fetchData"
+        @size-change="onSizeChange"
+      />
+    </div>
+
+    <el-dialog
+      v-model="dialogVisible"
+      :title="isEdit ? '编辑岗亭' : '新增岗亭'"
+      width="600"
+    >
+      <el-form label-width="120">
+        <el-form-item label="岗亭编码:">
+          <el-input v-model="form.booth_code" />
+        </el-form-item>
+        <el-form-item label="岗亭名称:">
+          <el-input v-model="form.booth_name" />
+        </el-form-item>
+        <el-form-item label="IP地址:">
+          <el-input v-model="form.ip_address" />
+        </el-form-item>
+        <el-form-item label="备注:">
+          <el-input v-model="form.description" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确认</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch } from 'vue'
+import {
+  obtainGuardBoothList,
+  addGuardBooth,
+  editGuardBooth,
+  deleteGuardBooth,
+} from '@/api/guardBooth'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const props = defineProps({
+  parkId: {
+    type: Number,
+    default: 0,
+  },
+})
+
+const loading = ref(false)
+const list = ref([])
+const total = ref(0)
+const query = reactive({
+  parking_lot_id: 0,
+  page: 1,
+  page_size: 10,
+})
+
+const fetchData = async () => {
+  if (!props.parkId || props.parkId === 0) {
+    list.value = []
+    total.value = 0
+    return
+  }
+  loading.value = true
+  query.parking_lot_id = props.parkId
+  try {
+    const res = await obtainGuardBoothList({ ...query })
+    if (res.code === 0) {
+      list.value = res.data.list || []
+      total.value = res.data.total || 0
+    }
+  } finally {
+    loading.value = false
+  }
+}
+
+const onSizeChange = (val) => {
+  query.page_size = val
+  query.page = 1
+  fetchData()
+}
+
+// Dialog
+const dialogVisible = ref(false)
+const isEdit = ref(false)
+const form = reactive({
+  id: 0,
+  booth_code: '',
+  booth_name: '',
+  ip_address: '',
+  description: '',
+  parking_lot_id: 0,
+})
+
+const openDialog = (mode, row) => {
+  isEdit.value = mode === 'edit'
+  if (mode === 'edit' && row) {
+    form.id = row.ID
+    form.booth_code = row.booth_code
+    form.booth_name = row.booth_name
+    form.ip_address = row.ip_address
+    form.description = row.description
+    form.parking_lot_id = row.parking_lot_id
+  } else {
+    form.id = 0
+    form.booth_code = ''
+    form.booth_name = ''
+    form.ip_address = ''
+    form.description = ''
+    form.parking_lot_id = props.parkId
+  }
+  dialogVisible.value = true
+}
+
+const submitForm = async () => {
+  if (!form.booth_code || !form.booth_name || !form.ip_address) {
+    ElMessage.error('请填写完整信息')
+    return
+  }
+  form.parking_lot_id = props.parkId
+  if (isEdit.value) {
+    const res = await editGuardBooth(form)
+    if (res.code === 0) {
+      ElMessage.success('编辑成功')
+      dialogVisible.value = false
+      await fetchData()
+    }
+  } else {
+    const res = await addGuardBooth(form)
+    if (res.code === 0) {
+      ElMessage.success('新增成功')
+      dialogVisible.value = false
+      await fetchData()
+    }
+  }
+}
+
+const handleDelete = (id) => {
+  ElMessageBox.confirm('确定删除该岗亭吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(async () => {
+      const res = await deleteGuardBooth(id)
+      if (res.code === 0) {
+        ElMessage.success('删除成功')
+        await fetchData()
+      }
+    })
+    .catch(() => {
+      ElMessage.info('已取消删除')
+    })
+}
+
+watch(
+  () => props.parkId,
+  (val) => {
+    query.page = 1
+    fetchData()
+  },
+  { immediate: true }
+)
+</script>

+ 240 - 0
frontend/src/view/parking/parkingInfo/channel.vue

@@ -0,0 +1,240 @@
+<template>
+  <div v-loading="loading">
+    <el-form inline>
+      <el-form-item>
+        <el-button type="success" icon="Plus" @click="openDialog('add')">新增</el-button>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Refresh" @click="fetchData">刷新</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table :data="list" size="small" height="505" border stripe>
+      <el-table-column label="编码" align="center" width="120" prop="channel_code" />
+      <el-table-column label="名称" align="center" width="200" prop="channel_name" />
+      <el-table-column label="方向" align="center" width="120">
+        <template #default="scope">
+          <el-tag :type="scope.row.direction === 'in' ? 'success' : scope.row.direction === 'out' ? 'danger' : 'warning'">
+            {{ scope.row.direction === 'in' ? '进场' : scope.row.direction === 'out' ? '出场' : '进出' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="允许临时车" align="center" width="140">
+        <template #default="scope">
+          <el-tag :type="scope.row.allow_temporary ? 'success' : 'danger'">
+            {{ scope.row.allow_temporary ? '允许' : '禁止' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="关联岗亭ID" align="center" width="120" prop="booth_id" />
+      <el-table-column label="备注" align="center" prop="description" />
+      <el-table-column label="操作" align="center" width="200" fixed="right">
+        <template #default="scope">
+          <el-button text type="primary" icon="Edit" @click="openDialog('edit', scope.row)">修改</el-button>
+          <el-button text type="primary" icon="Delete" @click="handleDelete(scope.row.ID)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div style="display: flex; justify-content: flex-end; margin-top: 10px">
+      <el-pagination
+        v-model:current-page="query.page"
+        v-model:page-size="query.page_size"
+        :page-sizes="[10, 30, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @current-change="fetchData"
+        @size-change="onSizeChange"
+      />
+    </div>
+
+    <el-dialog
+      v-model="dialogVisible"
+      :title="isEdit ? '编辑通道' : '新增通道'"
+      width="600"
+    >
+      <el-form label-width="140">
+        <el-form-item label="通道编码:">
+          <el-input v-model="form.channel_code" />
+        </el-form-item>
+        <el-form-item label="通道名称:">
+          <el-input v-model="form.channel_name" />
+        </el-form-item>
+        <el-form-item label="方向:">
+          <el-radio-group v-model="form.direction">
+            <el-radio value="in">进场</el-radio>
+            <el-radio value="out">出场</el-radio>
+            <el-radio value="inout">进出</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="允许临时车:">
+          <el-switch v-model="form.allow_temporary" />
+        </el-form-item>
+        <el-form-item label="关联岗亭:">
+          <el-select v-model="form.booth_id" placeholder="请选择岗亭" clearable>
+            <el-option
+              v-for="item in boothList"
+              :key="item.ID"
+              :label="item.booth_name"
+              :value="item.ID"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注:">
+          <el-input v-model="form.description" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确认</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch } from 'vue'
+import {
+  obtainChannelList,
+  addChannel,
+  editChannel,
+  deleteChannel,
+} from '@/api/channel'
+import { obtainGuardBoothList } from '@/api/guardBooth'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const props = defineProps({
+  parkId: {
+    type: Number,
+    default: 0,
+  },
+})
+
+const loading = ref(false)
+const list = ref([])
+const total = ref(0)
+const boothList = ref([])
+const query = reactive({
+  parking_lot_id: 0,
+  page: 1,
+  page_size: 10,
+})
+
+const fetchData = async () => {
+  if (!props.parkId || props.parkId === 0) {
+    list.value = []
+    total.value = 0
+    return
+  }
+  loading.value = true
+  query.parking_lot_id = props.parkId
+  try {
+    const res = await obtainChannelList({ ...query })
+    if (res.code === 0) {
+      list.value = res.data.list || []
+      total.value = res.data.total || 0
+    }
+  } finally {
+    loading.value = false
+  }
+}
+
+const fetchBoothList = async () => {
+  if (!props.parkId || props.parkId === 0) return
+  const res = await obtainGuardBoothList({ parking_lot_id: props.parkId, page: 1, page_size: 999 })
+  if (res.code === 0) {
+    boothList.value = res.data.list || []
+  }
+}
+
+const onSizeChange = (val) => {
+  query.page_size = val
+  query.page = 1
+  fetchData()
+}
+
+// Dialog
+const dialogVisible = ref(false)
+const isEdit = ref(false)
+const form = reactive({
+  id: 0,
+  channel_code: '',
+  channel_name: '',
+  direction: 'in',
+  allow_temporary: false,
+  description: '',
+  booth_id: null,
+})
+
+const openDialog = (mode, row) => {
+  isEdit.value = mode === 'edit'
+  fetchBoothList()
+  if (mode === 'edit' && row) {
+    form.id = row.ID
+    form.channel_code = row.channel_code
+    form.channel_name = row.channel_name
+    form.direction = row.direction
+    form.allow_temporary = row.allow_temporary
+    form.description = row.description
+    form.booth_id = row.booth_id || null
+  } else {
+    form.id = 0
+    form.channel_code = ''
+    form.channel_name = ''
+    form.direction = 'in'
+    form.allow_temporary = false
+    form.description = ''
+    form.booth_id = null
+  }
+  dialogVisible.value = true
+}
+
+const submitForm = async () => {
+  if (!form.channel_code || !form.channel_name) {
+    ElMessage.error('请填写完整信息')
+    return
+  }
+  if (isEdit.value) {
+    const res = await editChannel(form)
+    if (res.code === 0) {
+      ElMessage.success('编辑成功')
+      dialogVisible.value = false
+      await fetchData()
+    }
+  } else {
+    const res = await addChannel(form)
+    if (res.code === 0) {
+      ElMessage.success('新增成功')
+      dialogVisible.value = false
+      await fetchData()
+    }
+  }
+}
+
+const handleDelete = (id) => {
+  ElMessageBox.confirm('确定删除该通道吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(async () => {
+      const res = await deleteChannel(id)
+      if (res.code === 0) {
+        ElMessage.success('删除成功')
+        await fetchData()
+      }
+    })
+    .catch(() => {
+      ElMessage.info('已取消删除')
+    })
+}
+
+watch(
+  () => props.parkId,
+  (val) => {
+    query.page = 1
+    fetchData()
+  },
+  { immediate: true }
+)
+</script>

+ 287 - 0
frontend/src/view/parking/parkingInfo/device.vue

@@ -0,0 +1,287 @@
+<template>
+  <div v-loading="loading">
+    <el-form inline>
+      <el-form-item>
+        <el-button type="success" icon="Plus" @click="openDialog('add')">新增</el-button>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Refresh" @click="fetchData">刷新</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table :data="list" size="small" height="505" border stripe>
+      <el-table-column label="编码" align="center" width="120" prop="device_code" />
+      <el-table-column label="名称" align="center" width="180" prop="device_name" />
+      <el-table-column label="连接方式" align="center" width="120">
+        <template #default="scope">
+          <el-tag :type="scope.row.connect_type === 'tcp' ? 'primary' : 'warning'">
+            {{ scope.row.connect_type === 'tcp' ? 'TCP' : '串口' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="IP地址" align="center" width="150" prop="ip_address" />
+      <el-table-column label="端口" align="center" width="100" prop="port" />
+      <el-table-column label="状态" align="center" width="100">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === 'online' ? 'success' : 'danger'">
+            {{ scope.row.status === 'online' ? '在线' : '离线' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="启用" align="center" width="100">
+        <template #default="scope">
+          <el-tag :type="scope.row.is_active ? 'success' : 'danger'">
+            {{ scope.row.is_active ? '是' : '否' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="280" fixed="right">
+        <template #default="scope">
+          <el-button text type="success" icon="Link" @click="handleConnect(scope.row)">连接</el-button>
+          <el-button text type="primary" icon="Edit" @click="openDialog('edit', scope.row)">修改</el-button>
+          <el-button text type="primary" icon="Delete" @click="handleDelete(scope.row.ID)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div style="display: flex; justify-content: flex-end; margin-top: 10px">
+      <el-pagination
+        v-model:current-page="query.page"
+        v-model:page-size="query.page_size"
+        :page-sizes="[10, 30, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @current-change="fetchData"
+        @size-change="onSizeChange"
+      />
+    </div>
+
+    <el-dialog
+      v-model="dialogVisible"
+      :title="isEdit ? '编辑设备' : '新增设备'"
+      width="600"
+    >
+      <el-form label-width="150">
+        <el-form-item label="设备编码:">
+          <el-input v-model="form.device_code" />
+        </el-form-item>
+        <el-form-item label="设备名称:">
+          <el-input v-model="form.device_name" />
+        </el-form-item>
+        <el-form-item label="关联通道:">
+          <el-select v-model="form.channel_id" placeholder="请选择通道" clearable>
+            <el-option
+              v-for="item in channelList"
+              :key="item.ID"
+              :label="item.channel_name"
+              :value="item.ID"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="连接方式:">
+          <el-select v-model="form.connect_type">
+            <el-option label="TCP" value="tcp" />
+            <el-option label="串口" value="serial" />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="form.connect_type === 'tcp'" label="IP地址:">
+          <el-input v-model="form.ip_address" />
+        </el-form-item>
+        <el-form-item v-if="form.connect_type === 'tcp'" label="端口:">
+          <el-input v-model="form.port" type="number" />
+        </el-form-item>
+        <el-form-item v-if="form.connect_type === 'serial'" label="串口名:">
+          <el-input v-model="form.com_port" />
+        </el-form-item>
+        <el-form-item v-if="form.connect_type === 'serial'" label="波特率:">
+          <el-input v-model="form.baud_rate" type="number" />
+        </el-form-item>
+        <el-form-item label="启用:">
+          <el-switch v-model="form.is_active" />
+        </el-form-item>
+        <el-form-item label="备注:">
+          <el-input v-model="form.description" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确认</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch } from 'vue'
+import {
+  obtainDeviceList,
+  addDevice,
+  editDevice,
+  deleteDevice,
+  joinDevice,
+} from '@/api/device'
+import { obtainChannelList } from '@/api/channel'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const props = defineProps({
+  parkId: {
+    type: Number,
+    default: 0,
+  },
+})
+
+const loading = ref(false)
+const list = ref([])
+const total = ref(0)
+const channelList = ref([])
+const query = reactive({
+  parking_lot_id: 0,
+  page: 1,
+  page_size: 10,
+})
+
+const fetchData = async () => {
+  if (!props.parkId || props.parkId === 0) {
+    list.value = []
+    total.value = 0
+    return
+  }
+  loading.value = true
+  query.parking_lot_id = props.parkId
+  try {
+    const res = await obtainDeviceList({ ...query })
+    if (res.code === 0) {
+      list.value = res.data.list || []
+      total.value = res.data.total || 0
+    }
+  } finally {
+    loading.value = false
+  }
+}
+
+const fetchChannelList = async () => {
+  if (!props.parkId || props.parkId === 0) return
+  const res = await obtainChannelList({ parking_lot_id: props.parkId, page: 1, page_size: 999 })
+  if (res.code === 0) {
+    channelList.value = res.data.list || []
+  }
+}
+
+const onSizeChange = (val) => {
+  query.page_size = val
+  query.page = 1
+  fetchData()
+}
+
+// Dialog
+const dialogVisible = ref(false)
+const isEdit = ref(false)
+const form = reactive({
+  id: 0,
+  device_code: '',
+  device_name: '',
+  baud_rate: 0,
+  ip_address: '',
+  port: 0,
+  com_port: '',
+  description: '',
+  connect_type: 'tcp',
+  is_active: false,
+  channel_id: null,
+})
+
+const openDialog = (mode, row) => {
+  isEdit.value = mode === 'edit'
+  fetchChannelList()
+  if (mode === 'edit' && row) {
+    form.id = row.ID
+    form.device_code = row.device_code
+    form.device_name = row.device_name
+    form.baud_rate = row.baud_rate
+    form.ip_address = row.ip_address
+    form.port = row.port
+    form.com_port = row.com_port
+    form.description = row.description
+    form.connect_type = row.connect_type
+    form.is_active = row.is_active
+    form.channel_id = row.channel_id || null
+  } else {
+    form.id = 0
+    form.device_code = ''
+    form.device_name = ''
+    form.baud_rate = 0
+    form.ip_address = ''
+    form.port = 0
+    form.com_port = ''
+    form.description = ''
+    form.connect_type = 'tcp'
+    form.is_active = false
+    form.channel_id = null
+  }
+  dialogVisible.value = true
+}
+
+const submitForm = async () => {
+  if (!form.device_code || !form.device_name) {
+    ElMessage.error('请填写完整信息')
+    return
+  }
+  form.baud_rate = Number(form.baud_rate)
+  form.port = Number(form.port)
+  if (isEdit.value) {
+    const res = await editDevice(form)
+    if (res.code === 0) {
+      ElMessage.success('编辑成功')
+      dialogVisible.value = false
+      await fetchData()
+    } else {
+      ElMessage.error(res.data?.msg || '编辑失败')
+    }
+  } else {
+    const res = await addDevice(form)
+    if (res.code === 0) {
+      ElMessage.success('新增成功')
+      dialogVisible.value = false
+      await fetchData()
+    } else {
+      ElMessage.error(res.data?.msg || '新增失败')
+    }
+  }
+}
+
+const handleConnect = async (row) => {
+  const res = await joinDevice(row.ID)
+  if (res.code === 0) {
+    ElMessage.success('连接成功')
+  } else {
+    ElMessage.error(res.data?.msg || '连接失败')
+  }
+}
+
+const handleDelete = (id) => {
+  ElMessageBox.confirm('确定删除该设备吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(async () => {
+      const res = await deleteDevice(id)
+      if (res.code === 0) {
+        ElMessage.success('删除成功')
+        await fetchData()
+      }
+    })
+    .catch(() => {
+      ElMessage.info('已取消删除')
+    })
+}
+
+watch(
+  () => props.parkId,
+  (val) => {
+    query.page = 1
+    fetchData()
+  },
+  { immediate: true }
+)
+</script>

+ 219 - 0
frontend/src/view/parking/parkingInfo/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <el-row>
+    <el-col :span="5">
+      <el-card shadow="never" class="park-list-card">
+        <template #header>
+          <div class="card-header">
+            <span style="font-weight: 550">停车场列表</span>
+            <el-button text type="success" icon="Plus" @click="openCreateDialog" />
+            <el-button text type="primary" icon="Refresh" @click="fetchParkList" />
+          </div>
+        </template>
+        <el-menu
+          :default-active="activeIndex.toString()"
+          background-color="#f5f5f5"
+          @select="handleMenuSelect"
+        >
+          <el-menu-item
+            v-for="(item, index) in parkStore.parkList"
+            :key="item.ID"
+            :index="index.toString()"
+          >
+            <div class="menu-item-content">
+              <span>{{ item.lot_name }}</span>
+              <div class="menu-item-actions">
+                <el-icon @click.stop="openEditDialog(item)"><Edit /></el-icon>
+                <el-icon @click.stop="handleDelete(item.ID)"><Delete /></el-icon>
+              </div>
+            </div>
+          </el-menu-item>
+        </el-menu>
+      </el-card>
+    </el-col>
+
+    <el-col :span="18" :offset="1">
+      <el-tabs v-model="activeTab">
+        <el-tab-pane label="车场详情" name="detail">
+          <lot-detail
+            :park-id="parkStore.parkId"
+            @edit="openEditDialog"
+            @delete="handleDelete"
+          />
+        </el-tab-pane>
+        <el-tab-pane label="岗亭管理" name="booth">
+          <booth :park-id="parkStore.parkId" />
+        </el-tab-pane>
+        <el-tab-pane label="通道管理" name="channel">
+          <channel :park-id="parkStore.parkId" />
+        </el-tab-pane>
+        <el-tab-pane label="设备管理" name="device">
+          <device :park-id="parkStore.parkId" />
+        </el-tab-pane>
+      </el-tabs>
+    </el-col>
+
+    <!-- 新增/编辑停车场 Dialog -->
+    <el-dialog
+      v-model="dialogVisible"
+      :title="isEdit ? '编辑停车场' : '新增停车场'"
+      width="500"
+    >
+      <el-form label-width="120">
+        <el-form-item label="区域编码:">
+          <el-input v-model="form.lot_code" />
+        </el-form-item>
+        <el-form-item label="区域名称:">
+          <el-input v-model="form.lot_name" />
+        </el-form-item>
+        <el-form-item label="区域容量:">
+          <el-input v-model="form.capacity" type="number" />
+        </el-form-item>
+        <el-form-item label="备注:">
+          <el-input v-model="form.description" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm">确认</el-button>
+      </template>
+    </el-dialog>
+  </el-row>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue'
+import { useParkAreaStore } from '@/pinia/parkArea'
+import {
+  obtainPullOverList,
+  addPullOver,
+  editPullOver,
+  removePullOver,
+} from '@/api/pullOver'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import lotDetail from './lotDetail.vue'
+import booth from './booth.vue'
+import channel from './channel.vue'
+import device from './device.vue'
+
+const parkStore = useParkAreaStore()
+const activeTab = ref('detail')
+
+const activeIndex = computed(() => {
+  const idx = parkStore.parkList.findIndex((item) => item.ID === parkStore.parkId)
+  return idx >= 0 ? idx.toString() : '0'
+})
+
+// 获取停车场列表
+const fetchParkList = async () => {
+  const res = await obtainPullOverList()
+  if (res.code === 0) {
+    const list = res.data.list || []
+    parkStore.changeList(list)
+  }
+}
+
+// 菜单选择
+const handleMenuSelect = (index) => {
+  const item = parkStore.parkList[parseInt(index)]
+  if (item) {
+    parkStore.changeId(item.ID)
+  }
+}
+
+// 新增/编辑 dialog
+const dialogVisible = ref(false)
+const isEdit = ref(false)
+const form = reactive({
+  id: 0,
+  lot_code: '',
+  lot_name: '',
+  capacity: 0,
+  description: '',
+})
+
+const openCreateDialog = () => {
+  isEdit.value = false
+  form.id = 0
+  form.lot_code = ''
+  form.lot_name = ''
+  form.capacity = 0
+  form.description = ''
+  dialogVisible.value = true
+}
+
+const openEditDialog = (item) => {
+  isEdit.value = true
+  form.id = item.ID
+  form.lot_code = item.lot_code
+  form.lot_name = item.lot_name
+  form.capacity = item.capacity
+  form.description = item.description
+  dialogVisible.value = true
+}
+
+const submitForm = async () => {
+  form.capacity = Number(form.capacity)
+  if (isEdit.value) {
+    const res = await editPullOver(form)
+    if (res.code === 0) {
+      ElMessage.success('编辑成功')
+      dialogVisible.value = false
+      await fetchParkList()
+    }
+  } else {
+    const res = await addPullOver(form)
+    if (res.code === 0) {
+      ElMessage.success('新增成功')
+      dialogVisible.value = false
+      await fetchParkList()
+    }
+  }
+}
+
+const handleDelete = (id) => {
+  ElMessageBox.confirm('确定删除该停车场吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(async () => {
+      const res = await removePullOver(id)
+      if (res.code === 0) {
+        ElMessage.success('删除成功')
+        parkStore.changeId(0)
+        await fetchParkList()
+      }
+    })
+    .catch(() => {
+      ElMessage.info('已取消删除')
+    })
+}
+
+onMounted(() => {
+  fetchParkList()
+})
+</script>
+
+<style scoped lang="scss">
+.park-list-card {
+  min-height: 600px;
+
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+}
+
+.menu-item-content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+
+  .menu-item-actions {
+    display: flex;
+    gap: 4px;
+  }
+}
+</style>

+ 83 - 0
frontend/src/view/parking/parkingInfo/lotDetail.vue

@@ -0,0 +1,83 @@
+<template>
+  <div v-loading="loading">
+    <el-descriptions
+      v-if="detail.id"
+      :column="2"
+      border
+    >
+      <el-descriptions-item label="ID" :span="2">{{ detail.id }}</el-descriptions-item>
+      <el-descriptions-item label="区域编码">{{ detail.lot_code }}</el-descriptions-item>
+      <el-descriptions-item label="区域名称">{{ detail.lot_name }}</el-descriptions-item>
+      <el-descriptions-item label="容量">{{ detail.capacity }}</el-descriptions-item>
+      <el-descriptions-item label="备注">{{ detail.description || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="创建时间" :span="2">{{ detail.CreatedAt || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="更新时间" :span="2">{{ detail.UpdatedAt || '-' }}</el-descriptions-item>
+    </el-descriptions>
+
+    <div v-if="!detail.id && !loading" style="text-align: center; color: #999; margin-top: 40px;">
+      请从左侧选择一个停车场
+    </div>
+
+    <div v-if="detail.id" class="action-bar">
+      <el-button type="primary" icon="Edit" @click="handleEdit">编辑</el-button>
+      <el-button type="danger" icon="Delete" @click="handleDelete">删除</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import { obtainPullOverId } from '@/api/pullOver'
+
+const props = defineProps({
+  parkId: {
+    type: Number,
+    default: 0,
+  },
+})
+
+const emit = defineEmits(['edit', 'delete'])
+
+const loading = ref(false)
+const detail = ref({})
+
+const fetchDetail = async (id) => {
+  if (!id || id === 0) {
+    detail.value = {}
+    return
+  }
+  loading.value = true
+  try {
+    const res = await obtainPullOverId(id)
+    if (res.code === 0) {
+      detail.value = res.data || {}
+    }
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleEdit = () => {
+  emit('edit', detail.value)
+}
+
+const handleDelete = () => {
+  emit('delete', detail.value.id || detail.value.ID)
+}
+
+watch(
+  () => props.parkId,
+  (val) => {
+    fetchDetail(val)
+  },
+  { immediate: true }
+)
+</script>
+
+<style scoped lang="scss">
+.action-bar {
+  margin-top: 20px;
+  display: flex;
+  gap: 10px;
+}
+</style>

+ 23 - 23
wails/go.mod

@@ -1,16 +1,15 @@
 module wails-app
 
-go 1.24.0
-
-toolchain go1.24.2
+go 1.25.0
 
 require (
-	github.com/wailsapp/wails/v2 v2.9.2
+	github.com/wailsapp/wails/v2 v2.13.0
 	server v0.0.0
 )
 
 require (
 	filippo.io/edwards25519 v1.1.0 // indirect
+	git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
 	github.com/KyleBanks/depth v1.2.1 // indirect
 	github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
 	github.com/aws/aws-sdk-go v1.55.8 // indirect
@@ -26,7 +25,7 @@ require (
 	github.com/cloudwego/base64x v0.1.5 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
-	github.com/fsnotify/fsnotify v1.8.0 // indirect
+	github.com/fsnotify/fsnotify v1.9.0 // indirect
 	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/gin-contrib/sse v1.0.0 // indirect
@@ -52,6 +51,7 @@ require (
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/uuid v1.6.0 // indirect
+	github.com/gorilla/websocket v1.5.3 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
@@ -66,13 +66,13 @@ require (
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.18.0 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.9 // indirect
-	github.com/labstack/echo/v4 v4.10.2 // indirect
-	github.com/labstack/gommon v0.4.0 // indirect
-	github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
-	github.com/leaanthony/gosod v1.0.3 // indirect
+	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
+	github.com/labstack/echo/v4 v4.13.3 // indirect
+	github.com/labstack/gommon v0.4.2 // indirect
+	github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
+	github.com/leaanthony/gosod v1.0.4 // indirect
 	github.com/leaanthony/slicer v1.6.0 // indirect
-	github.com/leaanthony/u v1.1.0 // indirect
+	github.com/leaanthony/u v1.1.1 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
 	github.com/magiconair/properties v1.8.9 // indirect
@@ -95,11 +95,11 @@ require (
 	github.com/qiniu/qmgo v1.1.9 // indirect
 	github.com/redis/go-redis/v9 v9.7.0 // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
-	github.com/rivo/uniseg v0.4.4 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/robfig/cron/v3 v3.0.1 // indirect
 	github.com/sagikazarmark/locafero v0.7.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
-	github.com/samber/lo v1.38.1 // indirect
+	github.com/samber/lo v1.49.1 // indirect
 	github.com/shirou/gopsutil/v3 v3.24.5 // indirect
 	github.com/shoenig/go-m1cpu v0.1.6 // indirect
 	github.com/songzhibin97/gkit v1.2.13 // indirect
@@ -116,13 +116,13 @@ require (
 	github.com/tencentyun/cos-go-sdk-v5 v0.7.60 // indirect
 	github.com/tklauser/go-sysconf v0.3.14 // indirect
 	github.com/tklauser/numcpus v0.9.0 // indirect
-	github.com/tkrajina/go-reflector v0.5.6 // indirect
+	github.com/tkrajina/go-reflector v0.5.8 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
 	github.com/unrolled/secure v1.17.0 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/valyala/fasttemplate v1.2.2 // indirect
-	github.com/wailsapp/go-webview2 v1.0.16 // indirect
+	github.com/wailsapp/go-webview2 v1.0.22 // indirect
 	github.com/wailsapp/mimetype v1.4.1 // indirect
 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 	github.com/xdg-go/scram v1.1.2 // indirect
@@ -134,15 +134,15 @@ require (
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.27.0 // indirect
 	golang.org/x/arch v0.13.0 // indirect
-	golang.org/x/crypto v0.37.0 // indirect
+	golang.org/x/crypto v0.51.0 // indirect
 	golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
-	golang.org/x/image v0.23.0 // indirect
-	golang.org/x/net v0.35.0 // indirect
-	golang.org/x/sync v0.13.0 // indirect
-	golang.org/x/sys v0.32.0 // indirect
-	golang.org/x/text v0.24.0 // indirect
+	golang.org/x/image v0.40.0 // indirect
+	golang.org/x/net v0.54.0 // indirect
+	golang.org/x/sync v0.20.0 // indirect
+	golang.org/x/sys v0.44.0 // indirect
+	golang.org/x/text v0.37.0 // indirect
 	golang.org/x/time v0.9.0 // indirect
-	golang.org/x/tools v0.29.0 // indirect
+	golang.org/x/tools v0.44.0 // indirect
 	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -157,4 +157,4 @@ require (
 	modernc.org/sqlite v1.34.5 // indirect
 )
 
-replace server => ../server
+replace server => ./server

+ 48 - 50
wails/go.sum

@@ -1,5 +1,7 @@
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
+git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
@@ -69,8 +71,8 @@ github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20260327044607-61a3f8297cd
 github.com/flipped-aurora/gin-vue-admin/server v0.0.0-20260327044607-61a3f8297cd3/go.mod h1:4vCRqzN/5jwlghElibN0Sa+6V6DvAVoIiBwn3igr+MU=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
-github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
 github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
 github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
@@ -117,7 +119,6 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
 github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
 github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
-github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
@@ -150,6 +151,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -190,8 +193,8 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
-github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
+github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
+github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -199,21 +202,20 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
-github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
-github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
-github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
+github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
+github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
+github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
 github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
 github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
-github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=
-github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
-github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
-github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
-github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
+github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
+github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
+github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
+github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
 github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
 github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
-github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
-github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
+github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
+github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
@@ -225,12 +227,11 @@ github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a
 github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
-github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
 github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
-github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
+github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@@ -279,8 +280,8 @@ github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93Ge
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
-github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
@@ -289,8 +290,8 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
 github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
 github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
 github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
-github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
-github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
+github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
+github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
 github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
 github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
@@ -322,8 +323,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
@@ -342,8 +344,8 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ
 github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
 github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
 github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
-github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
-github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
+github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
+github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -352,15 +354,14 @@ github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbW
 github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
 github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/wailsapp/go-webview2 v1.0.16 h1:wffnvnkkLvhRex/aOrA3R7FP7rkvOqL/bir1br7BekU=
-github.com/wailsapp/go-webview2 v1.0.16/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
+github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
+github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
 github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
 github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
-github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k=
-github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs=
+github.com/wailsapp/wails/v2 v2.13.0 h1:S7OgXWpj72V91unF8iDWJKbcS9ZpwCT3R0QVru4v2Mg=
+github.com/wailsapp/wails/v2 v2.13.0/go.mod h1:nVr/wSIEZ7xxKPkzK65mjpKpaOPQI2k4pvLwGR/i4kc=
 github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -400,19 +401,20 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
 golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
+golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
-golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
 golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
+golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
+golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
-golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
+golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -432,8 +434,8 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
 golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
-golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
-golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
+golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -442,8 +444,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -453,9 +455,6 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -473,8 +472,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
+golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -507,8 +506,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
-golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
+golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
 golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
 golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -518,8 +517,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
-golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
-golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
+golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
+golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
@@ -535,7 +534,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=

+ 78 - 0
main.go

@@ -0,0 +1,78 @@
+package main
+
+import (
+	"context"
+	"embed"
+	"io/fs"
+	"log"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+
+	"server"
+
+	"github.com/wailsapp/wails/v2"
+	"github.com/wailsapp/wails/v2/pkg/options"
+	"github.com/wailsapp/wails/v2/pkg/options/assetserver"
+	"github.com/wailsapp/wails/v2/pkg/options/windows"
+)
+
+//go:embed all:frontend/dist
+var assets embed.FS
+
+func main() {
+	// Initialize the Gin backend (non-blocking goroutine, listens on :8888)
+	server.InitBackend()
+
+	// Reverse proxy for /api/* requests to Gin backend (strips /api prefix)
+	backendURL, _ := url.Parse("http://127.0.0.1:8888")
+	proxy := httputil.NewSingleHostReverseProxy(backendURL)
+
+	// Embedded frontend static files
+	distFS, err := fs.Sub(assets, "frontend/dist")
+	if err != nil {
+		log.Fatal("failed to get embedded frontend:", err)
+	}
+	fileServer := http.FileServer(http.FS(distFS))
+
+	// Create a handler that proxies /api to Gin, serves everything else from embed
+	mux := http.NewServeMux()
+	mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
+		// Strip /api prefix before forwarding to Gin
+		r.URL.Path = r.URL.Path[4:] // remove "/api"
+		if r.URL.Path == "" {
+			r.URL.Path = "/"
+		}
+		r.RequestURI = r.URL.RequestURI()
+		proxy.ServeHTTP(w, r)
+	})
+	mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
+		r.URL.Path = "/"
+		r.RequestURI = "/"
+		proxy.ServeHTTP(w, r)
+	})
+	mux.Handle("/", fileServer)
+
+	err = wails.Run(&options.App{
+		Title:     "智慧停车管理系统",
+		Width:     1400,
+		Height:    900,
+		MinWidth:  1024,
+		MinHeight: 768,
+		AssetServer: &assetserver.Options{
+			Assets:  assets,
+			Handler: mux,
+		},
+		OnStartup: func(ctx context.Context) {
+			log.Println("Wails app started, backend running on :8888")
+		},
+		Windows: &windows.Options{
+			WebviewIsTransparent: false,
+			WindowIsTranslucent:  false,
+		},
+	})
+
+	if err != nil {
+		log.Fatal(err)
+	}
+}

+ 13 - 0
server/core/viper.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/gin-gonic/gin"
 	"os"
+	"path/filepath"
 	"server/core/internal"
 
 	"github.com/fsnotify/fsnotify"
@@ -52,6 +53,18 @@ func Viper(path ...string) *viper.Viper {
 	v.SetConfigFile(config)
 	v.SetConfigType("yaml")
 	err := v.ReadInConfig()
+	// If config.yaml not found in CWD, try next to the executable
+	if err != nil {
+		if exePath, exeErr := os.Executable(); exeErr == nil {
+			exeDir := filepath.Dir(exePath)
+			fallbackConfig := filepath.Join(exeDir, config)
+			if _, statErr := os.Stat(fallbackConfig); statErr == nil {
+				fmt.Printf("使用可执行文件目录的config: %s\n", fallbackConfig)
+				v.SetConfigFile(fallbackConfig)
+				err = v.ReadInConfig()
+			}
+		}
+	}
 	if err != nil {
 		panic(fmt.Errorf("Fatal error config file: %s \n", err))
 	}

+ 1 - 21
server/main.go

@@ -1,4 +1,4 @@
-package main
+package server
 
 import (
 	_ "go.uber.org/automaxprocs"
@@ -9,19 +9,6 @@ import (
 	"server/initialize"
 )
 
-//go:generate go env -w GO111MODULE=on
-//go:generate go env -w GOPROXY=https://goproxy.cn,direct
-//go:generate go mod tidy
-//go:generate go mod download
-
-// @title                       Gin-Vue-Admin Swagger API接口文档
-// @version                     v2.6.3
-// @description                 使用gin+vue进行极速开发的全栈开发基础平台
-// @securityDefinitions.apikey  ApiKeyAuth
-// @in                          header
-// @name                        x-token
-// @BasePath                    /
-
 // InitBackend initializes the Go backend (Viper, Zap, GORM, Gin) and starts the
 // HTTP server in a background goroutine. It does not block. Call this before
 // running the Wails application.
@@ -39,10 +26,3 @@ func InitBackend() {
 	initialize.InitUHFDevices()
 	go core.RunWindowsServer()
 }
-
-// main is retained for standalone (non-Wails) usage.
-func main() {
-	InitBackend()
-	// Block forever when running standalone.
-	select {}
-}

+ 1 - 1
wails/wails.json

@@ -1,7 +1,7 @@
 {
   "name": "smart-parking",
   "outputfilename": "smart-parking",
-  "frontend:dir": "../frontend",
+  "frontend:dir": "frontend",
   "frontend:install": "npm install",
   "frontend:build": "npm run build",
   "frontend:dev:watcher": "npm run dev",

+ 0 - 45
wails/main.go

@@ -1,45 +0,0 @@
-package main
-
-import (
-	"context"
-	"embed"
-	"log"
-
-	"server"
-
-	"github.com/wailsapp/wails/v2"
-	"github.com/wailsapp/wails/v2/pkg/options"
-	"github.com/wailsapp/wails/v2/pkg/options/assetserver"
-	"github.com/wailsapp/wails/v2/pkg/options/windows"
-)
-
-//go:embed all:../frontend/dist
-var assets embed.FS
-
-func main() {
-	// Initialize the Gin backend (non-blocking goroutine)
-	server.InitBackend()
-
-	// Create Wails application
-	err := wails.Run(&options.App{
-		Title:     "智慧停车管理系统",
-		Width:     1400,
-		Height:    900,
-		MinWidth:  1024,
-		MinHeight: 768,
-		AssetServer: &assetserver.Options{
-			Assets: assets,
-		},
-		OnStartup: func(ctx context.Context) {
-			log.Println("Wails app started, backend running on :8888")
-		},
-		Windows: &windows.Options{
-			WebviewIsTransparent: false,
-			WindowIsTranslucent:  false,
-		},
-	})
-
-	if err != nil {
-		log.Fatal(err)
-	}
-}