| AGENTS.md | ●●●●● patch | view | raw | blame | history | |
| applications/drone-command/src/views/aICrudTemplate/FormDiaLog.vue | ●●●●● patch | view | raw | blame | history | |
| applications/drone-command/src/views/aICrudTemplate/aICrudTemplate.vue | ●●●●● patch | view | raw | blame | history | |
| applications/drone-command/src/views/aICrudTemplate/aICrudTemplateApi.js | ●●●●● patch | view | raw | blame | history |
AGENTS.md
@@ -1,42 +1,38 @@ # 仓库指南 # Repository Guidelines ## 项目结构与模块组织 这是一个 pnpm monorepo,包含两个工作区根目录: - `applications/`:可运行的应用 - `applications/drone-web-manage`:管理端(Vite + Vue 3) - `applications/command-center-dashboard`:大屏指挥调度 - `applications/drone-app-web-view`:App WebView - `packages/`:共享库(如 `packages/apis`、`packages/components`、`packages/constants`、`packages/hooks`、`packages/utils`) 静态资源一般位于各应用的 `public/` 与 `src/assets/`(例如 `applications/drone-web-manage/public/img`)。 - `applications/`:可运行的 Web 应用(如 `drone-command/`、`mobile-web-view/`、`task-work-order/`)。 - `packages/`:共享库(API、组件、常量、hooks、工具等)。 - `uniapps/`:UniApp 相关应用(如 `work-app/`、`work-wx/`)。 - 资源文件通常放在各应用内,具体入口以各应用的 `README.md` 为准。 ## 构建、测试与开发命令 默认在仓库根目录执行: - `pnpm install`:安装所有工作区依赖。 - `pnpm dev`:启动所有应用的开发模式。 - `pnpm dev:manage`:启动 `applications/drone-web-manage`。 - `pnpm dev:largeScreen`:启动 `applications/command-center-dashboard`。 - `pnpm dev:webView`:启动 `applications/drone-app-web-view`。 - `pnpm build:test:manage` / `pnpm build:prod:manage`:构建管理端测试/生产包。 - `pnpm build:test:largeScreen` / `pnpm build:prod:largeScreen`:构建大屏应用。 - `pnpm build:test:webView` / `pnpm build:prod:webView`:构建 WebView 应用。 各应用内可用脚本使用 `pnpm -C <app> <script>` 调用,如 `dev`、`serve`、`build:*`。 在仓库根目录使用 `pnpm`: - `pnpm install`:安装工作区依赖。 - `pnpm dev`:并行启动所有工作区的开发服务。 - `pnpm dev:drone-command`:启动单个应用。 - `pnpm dev:mobile-web-view`:启动单个应用。 - `pnpm dev:task-work-order`:启动单个应用。 - `pnpm dev:work-app`:启动 UniApp H5 开发服务。 - `pnpm dev:work-wx`:启动 UniApp H5 开发服务。 - `pnpm build:test:<app>` / `pnpm build:prod:<app>`:构建指定应用,例如 `pnpm build:prod:mobile-web-view`。 ## 代码风格与命名约定 - 格式化遵循 `.prettierrc.json`(使用制表符,`printWidth: 120`,单引号,无分号,CRLF)。 - SCSS 覆盖配置使用 4 空格制表与双引号。 - Vue 组件使用 `PascalCase` 命名(如 `UserProfile.vue`),页面目录同样使用 `PascalCase`。 - 共享逻辑优先放入 `packages/*`,避免在应用内复制粘贴。 ## 编码风格与命名规范 - 使用 Prettier 统一格式化(`.prettierrc.json`)。 - 使用 Tab 缩进(`tabWidth: 2`, `useTabs: true`),`semi: false`,`singleQuote: true`。 - SCSS 使用 `tabWidth: 4` 且双引号。 - 文件与目录保持 kebab-case,遵循现有应用与包的命名风格。 ## 测试规范 - 当前未配置测试框架,`pnpm test` 会直接报错退出。 - 如需新增测试,请在此文件注明测试框架与目录结构(例如 `src/**/__tests__`)。 ## 测试指南 - 当前未配置测试框架(`pnpm test` 会直接退出)。 - 若新增测试,请在对应应用 `README.md` 说明测试框架,并补充根目录脚本。 ## 提交与合并请求规范 - 提交信息遵循 `type: subject`(例如 `feat: add device filter`)。 - 常见类型:`feat`、`fix`、`docs`、`style`、`refactor`、`perf`、`test`、`chore`、`ci`、`revert`。 - PR 需包含清晰的变更说明、关联的需求/缺陷链接,并为 UI 变更提供截图或 GIF。 - 提交历史使用简短的约定式主题(如 `feat: ...`)。 - 建议使用 `feat:`、`fix:`、`chore:`、`refactor:` 等前缀并保持简洁描述。 - PR 需要包含:目的、影响的应用/包、UI 改动截图。 - 可关联相关 issue 或工单。 ## 环境与配置说明 - Node.js 20以上 - pnpm 9以上 - 统一使用 `pnpm`,锁文件为 `pnpm-lock.yaml`。 ## 环境与配置 - Node.js >= 20,pnpm >= 9。 - 优先使用根目录工作区脚本,除非必要不要在子目录直接运行脚本。 applications/drone-command/src/views/aICrudTemplate/FormDiaLog.vue
New file @@ -0,0 +1,216 @@ <template> <el-dialog v-model="visible" :title="dialogTitle" @closed="handleClosed" destroy-on-close> <el-form ref="formRef" :model="formData" :rules="rules" :disabled="dialogReadonly" label-width="100px"> <el-row> <el-col :span="12"> <el-form-item label="设备名称" prop="deviceName"> <el-input v-model="formData.deviceName" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="设备类型" prop="deviceType"> <el-select v-model="formData.deviceType" placeholder="请选择" clearable> <el-option v-for="item in dictObj.deviceType" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="设备属性" prop="deviceAtt"> <el-select v-model="formData.deviceAtt" placeholder="请选择" clearable> <el-option v-for="item in dictObj.deviceAtt" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="型号" prop="deviceModel"> <el-input v-model="formData.deviceModel" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="规格" prop="deviceSpecification"> <el-input v-model="formData.deviceSpecification" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="生产厂商" prop="manufacturer"> <el-input v-model="formData.manufacturer" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="来源" prop="source"> <el-input v-model="formData.source" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用途" prop="purpose"> <el-input v-model="formData.purpose" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="所属部门" prop="belongDept"> <el-tree-select v-model="formData.belongDept" node-key="id" :data="deptTree" :props="treeProps" check-strictly @change="getUserList" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="负责人" prop="charger"> <el-select v-model="formData.charger" placeholder="请选择" :disabled="!formData.belongDept" clearable> <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="联系电话" prop="contactPhone"> <el-input v-model="formData.contactPhone" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <el-button @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button> <el-button v-if="!dialogReadonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit" > 确定 </el-button> </template> </el-dialog> </template> <script setup> import { computed, ref } from 'vue' import { ElMessage } from 'element-plus' import { fwDeviceDetailApi, fwDeviceSubmitApi } from '@/views/aICrudTemplate/aICrudTemplateApi' import { getUserListApi } from '@/api/system/user' import { fieldRules } from '@ztzf/utils' const initForm = () => ({ belongDept: '', // 所属部门 charger: '', // 负责人 contactPhone: '', // 联系电话 deviceAtt: '', // 设备属性 deviceType: '', // 设备类型 deviceModel: '', // 型号 deviceSpecification: '', // 规格 deviceName: '', // 设备名称 manufacturer: '', // 生产厂商 purpose: '', // 用途 source: '', // 来源 }) const treeProps = { label: 'name', children: 'children', } const dictObj = inject('dictObj') const deptTree = inject('deptTree') const emit = defineEmits(['success']) const formRef = ref(null) // 表单实例 const formData = ref(initForm()) // 表单数据 const visible = ref(false) // 弹框显隐 const dialogMode = ref('add') // 弹框模式 const submitting = ref(false) // 提交中 const userList = ref([]) // 用户列表 const dialogReadonly = computed(() => dialogMode.value === 'view') const dialogTitle = computed(() => { if (dialogMode.value === 'edit') { return '编辑' } else if (dialogMode.value === 'view') { return '查看' } else { return '新增' } }) const rules = { deviceName: fieldRules(true, 50), deviceType: fieldRules(true, 0), deviceAtt: fieldRules(true, 0), deviceModel: fieldRules(true, 50), deviceSpecification: fieldRules(true, 50), manufacturer: fieldRules(true, 50), source: fieldRules(true, 50), purpose: fieldRules(true, 50), belongDept: fieldRules(true, 50), contactPhone: fieldRules(true, 50), charger: fieldRules(true, 0), } // 获取用户列表 function getUserList(val) { formData.value.charger = '' if (val) { getUserListApi(val).then(res => { userList.value = res.data.data.records }) } } // 关闭弹框 function handleCancel() { visible.value = false } // 提交新增/编辑 async function handleSubmit() { const isValid = await formRef.value?.validate().catch(() => false) if (!isValid) return submitting.value = true try { await fwDeviceSubmitApi(formData.value) ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功') visible.value = false emit('success') } finally { submitting.value = false } } // 加载详情 async function loadDetail() { if (!formData.value.id) return const res = await fwDeviceDetailApi({ id: formData.value.id }) formData.value = res?.data?.data ?? {} } // 关闭后重置 function handleClosed() { formData.value = initForm() } // 打开弹框 async function open({ mode, row } = {}) { dialogMode.value = mode || 'add' formData.value = row visible.value = true if (dialogMode.value === 'add') { formData.value = initForm() } else { await loadDetail() } } defineExpose({ open }) </script> applications/drone-command/src/views/aICrudTemplate/aICrudTemplate.vue
New file @@ -0,0 +1,224 @@ <template> <basic-container> <el-form ref="queryParamsRef" :model="searchParams"> <el-row :gutter="16"> <el-col :span="4"> <el-form-item label="名称" prop="deviceName"> <el-input v-model="searchParams.deviceName" placeholder="请输入" clearable @clear="handleSearch" /> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="类型" prop="deviceType"> <el-select v-model="searchParams.deviceType" placeholder="请选择" clearable @change="handleSearch"> <el-option v-for="item in dictObj.deviceType" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="部门" prop="belongDept"> <el-tree-select v-model="searchParams.belongDept" :data="deptTree" :props="treeProps" node-key="id" check-strictly clearable @change="handleSearch" /> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="时间"> <el-date-picker popper-class="ztzf-date-picker-popper" class="ztzf-date-picker" v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" @change="handleSearch" /> </el-form-item> </el-col> <el-col :span="4"> <el-form-item> <el-button @click="resetForm">重置</el-button> <el-button type="primary" @click="handleSearch">查询</el-button> </el-form-item> </el-col> </el-row> </el-form> <div> <el-button type="primary" @click="handleAdd">新增</el-button> <el-button type="primary" @click="exportFile" :loading="exportLoading">导出</el-button> <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button> </div> <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" /> <el-table-column type="index" width="60" label="序号" /> <el-table-column prop="deviceName" label="名称" /> <el-table-column prop="deviceType" label="类型"> <template v-slot="{ row }"> {{ getDictLabel(row.deviceType, dictObj.deviceType) }} </template> </el-table-column> <el-table-column prop="deviceModel" label="设备型号" /> <el-table-column prop="deviceSpecification" label="规格" /> <el-table-column label="操作"> <template v-slot="{ row }"> <el-link @click="handleView(row)" type="primary">查看</el-link> <el-link @click="handleEdit(row)" type="warning">编辑</el-link> <el-link @click="handleDelete(row)" type="danger">删除</el-link> </template> </el-table-column> </el-table> <div> <el-pagination v-model:current-page="searchParams.current" v-model:page-size="searchParams.size" :total="total" @change="getList" /> </div> <FormDiaLog ref="dialogRef" @success="getList" /> </basic-container> </template> <script setup> import { onMounted, ref } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { exportFwDeviceApi, fwDevicePageApi, fwDeviceRemoveApi } from '@/views/aICrudTemplate/aICrudTemplateApi' import FormDiaLog from './FormDiaLog.vue' import { getDictionaryByCode } from '@/api/system/dictbiz' import { getDeptTree } from '@/api/system/dept' import { blobDownload, dateRangeFormat, getDictLabel } from '@ztzf/utils' const initSearchParams = () => ({ deviceName: '', // 设备名称 deviceType: '', // 设备类型 belongDept: '', // 所属部门 current: 1, // 当前页 size: 10, // 每页大小 }) const searchParams = ref(initSearchParams()) // 查询参数 const total = ref(0) // 总条数 const loading = ref(false) // 列表加载中 const list = ref([]) // 列表数据 const selectedIds = ref([]) // 勾选的设备ID列表 const queryParamsRef = ref(null) // 查询表单实例 const dialogRef = ref(null) // 弹框实例 const dictObj = ref({ deviceType: [], //设备类型 deviceAtt: [], //设备属性 }) const dateRange = ref([]) //时间范围 const deptTree = ref([]) //部门树 const treeProps = { label: 'name', children: 'children', } const exportLoading = ref(false) //导出加载 provide('dictObj', dictObj) provide('deptTree', deptTree) // 获取列表 async function getList() { const range = dateRangeFormat(dateRange.value) loading.value = true try { const params = { ...searchParams.value, startTime: range[0], endTime: range[1] } const res = await fwDevicePageApi(params) list.value = res?.data?.data?.records ?? [] total.value = res?.data?.data?.total ?? 0 } finally { loading.value = false } } // 查询 function handleSearch() { searchParams.value.current = 1 getList() } // 重置查询 function resetForm() { dateRange.value = [] queryParamsRef.value?.resetFields() searchParams.value.current = 1 getList() } // 查看 function handleView(row) { dialogRef.value?.open({ mode: 'view', row: { ...row } }) } // 编辑 function handleEdit(row) { dialogRef.value?.open({ mode: 'edit', row: { ...row } }) } // 删除 async function handleDelete(row) { const tips = row ? '该条' : '选中的项' await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' }) const ids = row ? row.id : selectedIds.value.join(',') await fwDeviceRemoveApi({ ids }) ElMessage.success('删除成功') selectedIds.value = [] getList() } // 勾选值设置 function handleSelectionChange(rows) { selectedIds.value = rows.map(item => item.id) } // 导出 function exportFile() { exportLoading.value = true exportFwDeviceApi() .then(res => { blobDownload(res) }) .finally(() => { exportLoading.value = false }) } // 获取字典 function getDictList() { getDictionaryByCode('deviceType,deviceAtt').then(res => { dictObj.value = res.data.data }) } // 获取部门树 function getDeptTreeFun() { getDeptTree().then(res => { deptTree.value = res.data.data }) } // 新增 function handleAdd() { dialogRef.value?.open({ mode: 'add' }) } onMounted(() => { getList() getDictList() getDeptTreeFun() }) </script> <style scoped lang="scss"></style> applications/drone-command/src/views/aICrudTemplate/aICrudTemplateApi.js
New file @@ -0,0 +1,46 @@ import request from '@/axios' // 查list export const fwDevicePageApi = params => { return request({ url: `/drone-fw/device/fwDevice/page`, method: 'get', params, }) } // 增加或更新 export const fwDeviceSubmitApi = data => { return request({ url: `/drone-fw/device/fwDevice/submit`, method: 'post', data, }) } //删除 export const fwDeviceRemoveApi = params => { return request({ url: `/drone-fw/device/fwDevice/remove`, method: 'post', params, }) } //详情 export const fwDeviceDetailApi = params => { return request({ url: `/drone-fw/device/fwDevice/detail`, method: 'get', params, }) } //导出 export const exportFwDeviceApi = () => { return request({ url: `/drone-fw/record/fwDroneAlarmRecord/export-fwDroneAlarmRecord`, method: 'get', responseType: 'blob', }) }