Browse Source

feat: 二期内容开发

sunxiao 1 week ago
parent
commit
349a19b3f3

+ 1 - 1
.env.development

@@ -5,7 +5,7 @@ VITE_APP_TITLE = 智能国标化验室数据平台
 VITE_APP_ENV = 'development'
 
 # 管理系统/开发环境
-VITE_APP_BASE_API = http://192.168.40.21:8080
+VITE_APP_BASE_API = http://192.168.40.21:8889
 
 # VITE_APP_BASE_TEST = http://10.0.0.28:8080/
 # VITE_APP_BASE_PROD = http://192.168.9.54:8080/

+ 1 - 1
.env.production

@@ -5,7 +5,7 @@ VITE_APP_TITLE = 智能国标化验室数据平台
 VITE_APP_ENV = 'production'
 
 # 管理系统/生产环境
-VITE_APP_BASE_API = 'http://192.168.40.18:8080'
+VITE_APP_BASE_API = 'http://192.168.40.18:8889'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli
 VITE_BUILD_COMPRESS = gzip

+ 100 - 0
src/api/client/manage.js

@@ -0,0 +1,100 @@
+import request from '@/utils/request'
+
+// 机构管理 - list
+export function getOrganizationList(params) {
+  return request({
+    url: '/business/organization/list',
+    method: 'get',
+    params
+  })
+}
+
+// 机构管理 - add
+export function postOrganization(data) {
+  return request({
+    url: '/business/organization',
+    method: 'post',
+    data
+  })
+}
+
+// 机构管理 - del
+export function delOrganization(id) {
+  return request({
+    url: `/business/organization/${id}`,
+    method: 'delete',
+  })
+}
+
+// 机构管理 - edit
+export function putOrganization(data) {
+  return request({
+    url: '/business/organization',
+    method: 'put',
+    data
+  })
+}
+
+// 机构管理 - 省市区
+export function getRegion(params) {
+  return request({
+    url: '/business/region/list',
+    method: 'get',
+    params
+  })
+}
+
+
+// 机构管理 - list - 全部水厂
+export function getAllWaterFactoryList(params) {
+  return request({
+    url: '/business/organization/all',
+    method: 'get',
+    params: { type: 1 }
+  })
+}
+
+// 设备管理 - list - 全部水厂
+export function getAllDeviceList() {
+  return request({
+    url: '/business/device/all',
+    method: 'get'
+  })
+}
+
+
+
+// 设备管理 - list
+export function getDeviceList(params) {
+  return request({
+    url: '/business/device/list',
+    method: 'get',
+    params
+  })
+}
+
+// 设备管理 - add
+export function postDevice(data) {
+  return request({
+    url: '/business/device',
+    method: 'post',
+    data
+  })
+}
+
+// 设备管理 - edit
+export function putDevice(data) {
+  return request({
+    url: '/business/device',
+    method: 'put',
+    data
+  })
+}
+
+// 设备管理 - del
+export function delDevice(id) {
+  return request({
+    url: `/business/device/${id}`,
+    method: 'delete',
+  })
+}

+ 195 - 0
src/api/configuration/index.js

@@ -0,0 +1,195 @@
+import request from '@/utils/request'
+
+// 取样点位 - list
+export function getPositionList(params) {
+  return request({
+    url: '/business/position/list',
+    method: 'get',
+    params
+  })
+}
+
+// 取样点位 - add
+export function postPosition(data) {
+  return request({
+    url: '/business/position',
+    method: 'post',
+    data
+  })
+}
+
+// 取样点位 - del
+export function delPosition(id) {
+  return request({
+    url: `/business/position/${id}`,
+    method: 'delete',
+  })
+}
+
+// 取样点位 - edit
+export function putPosition(data) {
+  return request({
+    url: '/business/position',
+    method: 'put',
+    data
+  })
+}
+
+// -------------------------------------------------------
+
+// 化验项目 - list
+export function getAssayList(params) {
+  return request({
+    url: '/business/item/list',
+    method: 'get',
+    params
+  })
+}
+
+// 化验项目 - add
+export function postAssay(data) {
+  return request({
+    url: '/business/item',
+    method: 'post',
+    data
+  })
+}
+
+// 化验项目 - del
+export function delAssay(id) {
+  return request({
+    url: `/business/item/${id}`,
+    method: 'delete',
+  })
+}
+
+// 化验项目 - edit
+export function putAssay(data) {
+  return request({
+    url: '/business/item',
+    method: 'put',
+    data
+  })
+}
+
+
+// -------------------------------------------------------
+
+// 药剂管理 - list
+export function getMedicineList(params) {
+  return request({
+    url: '/business/medicine/list',
+    method: 'get',
+    params
+  })
+}
+
+// 药剂管理 - add
+export function postMedicine(data) {
+  return request({
+    url: '/business/medicine',
+    method: 'post',
+    data
+  })
+}
+
+// 药剂管理 - edit
+export function putMedicine(data) {
+  return request({
+    url: '/business/medicine',
+    method: 'put',
+    data
+  })
+}
+
+// 药剂管理 - del
+export function delMedicine(id) {
+  return request({
+    url: `/business/medicine/${id}`,
+    method: 'delete'
+  })
+}
+
+// -------------------------------------------------------
+
+// 化验流程 - list
+export function getWorkFlowList(params) {
+  return request({
+    url: '/business/workflow/list',
+    method: 'get',
+    params
+  })
+}
+
+// 化验流程 - add
+export function postWorkFlow(data) {
+  return request({
+    url: '/business/workflow',
+    method: 'post',
+    data
+  })
+}
+
+// 化验流程 - edit
+export function putWorkFlow(data) {
+  return request({
+    url: '/business/workflow',
+    method: 'put',
+    data
+  })
+}
+
+// 化验流程 - edit
+export function delWorkFlow(id) {
+  return request({
+    url: `/business/workflow/${id}`,
+    method: 'delete'
+  })
+}
+
+
+// 机构管理 - list - 全部水厂
+export function getAllWaterFactoryList(params) {
+  return request({
+    url: '/business/organization/all',
+    method: 'get',
+    params: { type: 1 }
+  })
+}
+
+// -------------------------------------------------------
+
+// 质控样 - list
+export function getSampleList(params) {
+  return request({
+    url: '/business/value/list',
+    method: 'get',
+    params
+  })
+}
+
+// 质控样 - add
+export function postSample(data) {
+  return request({
+    url: '/business/value',
+    method: 'post',
+    data
+  })
+}
+
+// 质控样 - edit
+export function putSample(data) {
+  return request({
+    url: '/business/value',
+    method: 'put',
+    data
+  })
+}
+
+// 质控样 - del
+export function delSample(id) {
+  return request({
+    url: `/business/value/${id}`,
+    method: 'delete'
+  })
+}

+ 1 - 1
src/router/index.js

@@ -161,7 +161,7 @@ export const dynamicRoutes = [
 ]
 
 const router = createRouter({
-  history: createWebHashHistory(("/sjzc/")),
+  history: createWebHashHistory(),
   routes: constantRoutes,
   scrollBehavior(to, from, savedPosition) {
     if (savedPosition) {

+ 297 - 0
src/views/backup/device/index.vue

@@ -0,0 +1,297 @@
+<script setup>
+import { CirclePlus, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const total = ref(100);
+const queryParams = ref({});
+const formInline = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+const dialogVisible1 = ref(false);
+const dialogVisible2 = ref(false);
+
+const rules = {
+  name: [
+    { required: true, message: 'Please input Activity name', trigger: 'blur' },
+  ],
+  type: [
+    { type: 'array', required: true, message: 'Please select at least one activity type', trigger: 'change', }
+  ]
+}
+
+const tableData = [
+  {
+    id: 1,
+    date: '2016-05-02',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+  {
+    id: 2,
+    date: '2016-05-04',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+  {
+    id: 3,
+    date: '2016-05-01',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+    children: [
+      {
+        id: 31,
+        date: '2016-05-01',
+        name: 'wangxiaohu',
+        address: 'No. 189, Grove St, Los Angeles',
+      },
+      {
+        id: 32,
+        date: '2016-05-01',
+        name: 'wangxiaohu',
+        address: 'No. 189, Grove St, Los Angeles',
+      },
+    ],
+  },
+  {
+    id: 4,
+    date: '2016-05-03',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+]
+
+const loading = ref(false);
+
+const initPageData = async () => {
+
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  datePieckerValue.value = [dayjs(new Date()).format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')];
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.deviceNo = firstDeviceNo;
+  initPageData();
+}
+
+// 切换table - 状态
+const onChangeTag= (row) => {
+  ElMessageBox.confirm(
+    '是要停用机构吗?',
+    '状态变更提醒',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  ).then(() => {
+    ElMessage({
+      type: 'success',
+      message: 'Delete completed',
+    })
+  }).catch(() => {
+    ElMessage({
+      type: 'info',
+      message: 'Delete canceled',
+    })
+  })
+  row.checked = !row.checked;
+}
+
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate((valid, fields) => {
+    if (valid) {
+      console.log('submit!')
+      formRef.value.resetFields();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+</script>
+
+<template>
+  <div>
+
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="设备名称">
+            <el-input v-model="queryParams.deviceNo" placeholder="请输入设备名称" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="设备类型">
+            <el-input v-model="queryParams.deviceNo" placeholder="请输入设备类型" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="化验状态">
+            <el-select v-model="queryParams.deviceNo" placeholder="请选择化验状态" filterable clearable>
+              <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增设备</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="tableLoading" row-key="id">
+        <el-table-column prop="date" label="设备名称" width="220" fixed/>
+        <el-table-column prop="name" label="设备型号" />
+        <el-table-column prop="address" label="设备类型"/>
+        <el-table-column prop="address" label="部件详情" width="500">
+          <template #default="{ row }">
+            <ul
+              class="flex items-center space-x-[10px] text-[#409EFF] text-[12px] font-bold cursor-pointer"
+              @click="dialogVisible2 = true"
+            >
+              <li v-for="item in 3" :key="item">
+                试管1(20*180 m’m)
+              </li>
+            </ul>
+          </template>
+        </el-table-column>
+        <el-table-column prop="assayStatus" label="设备状态" width="180">
+          <template #default="{ row }">
+            <el-check-tag :checked="row.checked" type="success" @change="onChangeTag(row)">
+              正常
+            </el-check-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="worksName" label="创建人"/>
+        <el-table-column prop="worksName" label="投产时间"/>
+        <el-table-column prop="worksName" label="创建时间"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="80">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleTableBtnClick(row)">编辑</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- v-show="total > 0" -->
+      <pagination
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="initPageData"
+      />
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" title="新增设备" width="600">
+      <el-form :model="formInline" :rules="rules" ref="formRef" label-width="80px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="24">
+            <el-form-item label="设备名称" prop="name">
+              <el-input v-model="formInline.user" placeholder="请输入设备名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设备型号">
+            <el-select v-model="queryParams.deviceNo" placeholder="请选择化验状态" filterable clearable>
+              <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+            </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设备类型">
+            <el-select v-model="queryParams.deviceNo" placeholder="请选择化验状态" filterable clearable>
+              <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+            </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设备状态">
+            <el-select v-model="queryParams.deviceNo" placeholder="请选择化验状态" filterable clearable>
+              <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+            </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="投产日期">
+              <el-date-picker
+                v-model="formInline.value1"
+                type="date"
+                placeholder="Pick a day"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="dialogVisible2" title="配置部件" width="600">
+      <div class="flex mb-[20px] bg-[#f3f5f9] py-[10px] px-[10px] space-x-[20px] font-bold">
+        <p class="space-x-[10px]">
+          <span>型号名称:</span>
+          <span class="text-[red]">高COD</span>
+        </p>
+        <p class="space-x-[10px]">
+          <span>设备类型:</span>
+          <span class="text-[red]">高COD</span>
+        </p>
+      </div>
+      
+      <ul class="flex justify-between items-center mb-[20px] space-x-[20px]">
+        <li class="w-[100%]">
+          <el-select v-model="queryParams.deviceNo" placeholder="请选择上级机构" filterable clearable>
+            <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+          </el-select>
+        </li>
+        <li>
+          <el-button @click="handleDialogConfirm" type="primary">添 加</el-button>
+        </li>
+      </ul>
+
+      <el-table :data="tableData" style="width: 100%" table-layout="auto" border>
+        <el-table-column prop="date" label="部件名称" width="180" />
+        <el-table-column prop="name" label="部件类型" width="300" />
+        <el-table-column prop="address" label="操作">
+          <el-button link type="primary">删除</el-button>
+        </el-table-column>
+      </el-table>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible1 = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">保 存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+// <el-select v-model="queryParams.deviceNo" placeholder="请选择上级机构" filterable clearable>
+//                 <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+//               </el-select>
+</style>

+ 266 - 0
src/views/backup/part/index.vue

@@ -0,0 +1,266 @@
+<script setup>
+import { CirclePlus, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const total = ref(100);
+const queryParams = ref({});
+const formInline = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+const dialogVisible1 = ref(false);
+const dialogVisible2 = ref(false);
+
+const rules = {
+  name: [
+    { required: true, message: 'Please input Activity name', trigger: 'blur' },
+  ],
+  type: [
+    { type: 'array', required: true, message: 'Please select at least one activity type', trigger: 'change', }
+  ]
+}
+
+const tableData = [
+  {
+    id: 1,
+    date: '2016-05-02',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+  {
+    id: 2,
+    date: '2016-05-04',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+  {
+    id: 3,
+    date: '2016-05-01',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+    children: [
+      {
+        id: 31,
+        date: '2016-05-01',
+        name: 'wangxiaohu',
+        address: 'No. 189, Grove St, Los Angeles',
+      },
+      {
+        id: 32,
+        date: '2016-05-01',
+        name: 'wangxiaohu',
+        address: 'No. 189, Grove St, Los Angeles',
+      },
+    ],
+  },
+  {
+    id: 4,
+    date: '2016-05-03',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+]
+
+const loading = ref(false);
+
+const initPageData = async () => {
+
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  datePieckerValue.value = [dayjs(new Date()).format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')];
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.deviceNo = firstDeviceNo;
+  initPageData();
+}
+
+// 切换table - 状态
+const onChangeTag= (row) => {
+  ElMessageBox.confirm(
+    '是要停用机构吗?',
+    '状态变更提醒',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  ).then(() => {
+    ElMessage({
+      type: 'success',
+      message: 'Delete completed',
+    })
+  }).catch(() => {
+    ElMessage({
+      type: 'info',
+      message: 'Delete canceled',
+    })
+  })
+  row.checked = !row.checked;
+}
+
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate((valid, fields) => {
+    if (valid) {
+      console.log('submit!')
+      formRef.value.resetFields();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="型号名称">
+            <el-input v-model="queryParams.deviceNo" placeholder="请输入设备名称" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="正式投产日期">
+            <el-date-picker
+              v-model="queryParams.value1"
+              type="date"
+              style="width: 100%;"
+              placeholder="Pick a day"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="6">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增型号</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="tableLoading" row-key="id">
+        <el-table-column prop="date" label="部件编号" width="220" fixed/>
+        <el-table-column prop="name" label="部件名称" />
+        <el-table-column prop="address" label="部件型号"/>
+        <el-table-column prop="address" label="正式投产日期"/>
+        <el-table-column prop="worksName" label="创建人"/>
+        <el-table-column prop="worksName" label="创建时间"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="100">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleTableBtnClick(row)">编辑</el-button>
+            <el-button link type="primary" size="small" @click="handleTableBtnClick(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- v-show="total > 0" -->
+      <pagination
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="initPageData"
+      />
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" title="新增型号" width="600">
+      <el-form :model="formInline" :rules="rules" ref="formRef" label-width="100px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="24">
+            <el-form-item label="型号名称" prop="name">
+              <el-input v-model="formInline.user" placeholder="请输入设备名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="正式投产时间">
+              <el-date-picker
+                v-model="formInline.value1"
+                type="date"
+                placeholder="Pick a day"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设备说明">
+              <el-input
+                v-model="textarea"
+                resize='none'
+                :rows="2"
+                type="textarea"
+                placeholder="Please input"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="dialogVisible2" title="配置部件" width="600">
+      <div class="flex mb-[20px] bg-[#f3f5f9] py-[10px] px-[10px] space-x-[20px] font-bold">
+        <p class="space-x-[10px]">
+          <span>型号名称:</span>
+          <span class="text-[red]">高COD</span>
+        </p>
+        <p class="space-x-[10px]">
+          <span>设备类型:</span>
+          <span class="text-[red]">高COD</span>
+        </p>
+      </div>
+      
+      <ul class="flex justify-between items-center mb-[20px] space-x-[20px]">
+        <li class="w-[100%]">
+          <el-select v-model="queryParams.deviceNo" placeholder="请选择上级机构" filterable clearable>
+            <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+          </el-select>
+        </li>
+        <li>
+          <el-button @click="handleDialogConfirm" type="primary">添 加</el-button>
+        </li>
+      </ul>
+
+      <el-table :data="tableData" style="width: 100%" table-layout="auto" border>
+        <el-table-column prop="date" label="部件名称" width="180" />
+        <el-table-column prop="name" label="部件类型" width="300" />
+        <el-table-column prop="address" label="操作">
+          <el-button link type="primary">删除</el-button>
+        </el-table-column>
+      </el-table>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible1 = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">保 存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+// <el-select v-model="queryParams.deviceNo" placeholder="请选择上级机构" filterable clearable>
+//                 <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+//               </el-select>
+</style>

+ 299 - 0
src/views/client/device/index.vue

@@ -0,0 +1,299 @@
+<script setup>
+import { CirclePlus} from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getDeviceList, getAllWaterFactoryList, postDevice, putDevice, delDevice } from '@/api/client/manage'
+
+const { proxy } = getCurrentInstance();
+
+const queryParams = ref({ 
+  pageNum: 1,
+  pageSize: 10,
+ });
+
+const total = ref(0);
+const loading = ref(false);
+const tableData = ref([]);
+
+const formData = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+
+const rules = {
+  deviceWorks: [
+    { required: true, message: '请选择水厂', trigger: 'change' }
+  ],
+  deviceSn: [
+    { required: true, message: '请输入设备SN', trigger: 'blur' },
+  ],
+  deviceName: [
+    { required: true, message: '请输入设备名称', trigger: 'blur' },
+  ],
+  deviceModel: [
+    { required: true, message: '请输入设备型号', trigger: 'blur' },
+  ],
+  type: [
+    { required: true, message: '请选择设备型号', trigger: 'blur' },
+  ],
+  deviceStatus: [
+    { required: true, message: '请选择设备状态', trigger: 'blur' },
+  ]
+}
+
+// 设备用途 select 数据
+const deviceTypeOptions = [
+  { value: 1, label: '化验室' },
+  { value: 2, label: '连续检测' },
+]
+
+// 设备状态 select 数据
+const deviceStatusOptions = [
+  { value: 0, label: '正常' },
+  { value: 1, label: '停用' },
+]
+
+const waterFactoryOptions = ref([]);
+
+const initPageData = async () => {
+  
+  loading.value = true;
+
+  const { total: tableTotal, rows } = await getDeviceList(queryParams.value);
+
+  const typeEmun = {
+    1: '化验室',
+    2: '连续检测'
+  }
+  tableData.value = rows.map(item => ({
+    ...item,
+    typeName: typeEmun[item.type]
+  }));
+  total.value = tableTotal;
+  loading.value = false;
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+
+// 编辑设置
+const handleEditDevice = (row) => {
+  formData.value = { ...row };
+  dialogVisible.value = true;
+}
+
+// 删除设备
+const handleDelete = async ({ deviceId }) => {
+  await delDevice(deviceId);
+  proxy.$modal.msgSuccess("删除成功");
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+  }
+  initPageData();
+}
+
+// 切换table - 状态
+const onChangeTag= (row) => {
+  ElMessageBox.confirm(
+    `请确认,是否要${row.deviceStatus == 0 ? '停用' : '启用'}该设备?`,
+    '状态变更提醒',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  ).then(() => {
+    putDevice({ deviceId: row.deviceId, deviceStatus: row.deviceStatus == 0 ? 1 : 0 });
+    initPageData();
+    proxy.$modal.msgSuccess("状态变更成功");
+  }).catch(() => {})
+}
+
+// 重置dialog表单
+const handleDialogReset = () => {
+  formData.value = {};
+  formRef.value.resetFields();
+}
+
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return;
+  await formRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      !formData.value.deviceId ? await postDevice(formData.value) : await putDevice(formData.value);
+      initPageData();
+      formRef.value.resetFields();
+      dialogVisible.value = false;
+      proxy.$modal.msgSuccess("操作成功");
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+onMounted(() => {
+  getAllWaterFactoryList().then(({ data }) => {
+    waterFactoryOptions.value = data;
+  })
+  initPageData();
+})
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="设备名称">
+            <el-input placeholder="请输入设备名称" v-model.trim="queryParams.deviceName"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="设备SN">
+            <el-input placeholder="请输入设备SN" v-model.trim="queryParams.deviceSn"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="设备用途">
+            <el-select v-model="queryParams.type" placeholder="请选择设备用途" clearable>
+              <el-option v-for="item in deviceTypeOptions" :key="item.label" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="所属水厂">
+            <el-select v-model="queryParams.deviceWorks" placeholder="请选择所属水厂" filterable clearable>
+              <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="设备状态">
+            <el-select v-model="queryParams.deviceStatus" placeholder="请选择设备状态" clearable>
+              <el-option label="全部" value="" />
+              <el-option v-for="item in deviceStatusOptions" :key="item.label" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="12">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增设备</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="tableLoading" row-key="id" border>
+        <el-table-column prop="deviceName" label="设备名称" width="200" fixed/>
+        <el-table-column prop="deviceSn" label="设备SN" width="180" />
+        <el-table-column prop="deviceModel" label="设备型号" width="180"/>
+        <el-table-column prop="organization.name" label="所属水厂" width="200"/>
+        <el-table-column prop="typeName" label="设备用途" width="180"/>
+        <el-table-column prop="deviceStatus" label="使用状态" width="180">
+          <template #default="{ row }">
+            <el-check-tag :checked="row.deviceStatus == 0" type="success" @change="onChangeTag(row)">
+              {{ row.deviceStatus == 0 ? '正常' : '停用' }}
+            </el-check-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createUser.nickName" label="创建人" width="180"/>
+        <el-table-column prop="createTime" label="创建时间" width="180"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="120">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleEditDevice(row)">编辑</el-button>
+            <el-popconfirm title="请确认是否删除?" @confirm="handleDelete(row)" width="200">
+              <template #reference>
+                <el-button text type="primary" size="small">删除</el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize" @pagination="initPageData" />
+
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="formData.deviceId ? '编辑设备' : '新增设备'" width="900" @closed="handleDialogReset">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="120px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="11">
+            <el-form-item label="选择水厂" prop="deviceWorks">
+              <el-select v-model="formData.deviceWorks" placeholder="请选择水厂" filterable clearable>
+                <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="设备SN" prop="deviceSn">
+              <el-input v-model.trim="formData.deviceSn" placeholder="请输入设备SN" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="设备名称" prop="deviceName">
+              <el-input v-model.trim="formData.deviceName" placeholder="请输入设备名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="设备型号" prop="deviceModel">
+              <el-input v-model.trim="formData.deviceModel" placeholder="请输入设备型号" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="设备用途" prop="type">
+              <el-select v-model="formData.type" placeholder="请选择设备用途" clearable>
+                <el-option v-for="item in deviceTypeOptions" :key="item.label" :label="item.label" :value="item.value?.toString()" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="设备状态" prop="deviceStatus">
+              <el-select v-model="formData.deviceStatus" placeholder="请选择设备状态" clearable>
+                <el-option v-for="item in deviceStatusOptions" :key="item.label" :label="item.label" :value="item.value?.toString()" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="维护人员" prop="deviceMaintainer">
+              <el-input v-model.trim="formData.deviceMaintainer" placeholder="请输入维护人员" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="维护人员电话" prop="deviceTel">
+              <el-input v-model.trim="formData.deviceTel" placeholder="请输入维护人员电话" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 12 - 0
src/views/client/manage/index copy.vue

@@ -0,0 +1,12 @@
+<script setup lang="ts">
+
+</script>
+
+<template>
+
+
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 357 - 0
src/views/client/manage/index.vue

@@ -0,0 +1,357 @@
+<script setup>
+import { CirclePlus } from '@element-plus/icons-vue'
+import { ElMessageBox } from 'element-plus'
+import { getOrganizationList, postOrganization, putOrganization, delOrganization, getRegion } from '@/api/client/manage'
+
+const { proxy } = getCurrentInstance();
+
+const loading = ref(false);
+const tableLoading = ref(false);
+const tableData = ref([]);
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 1000,
+  name: '',
+  provinceCode: '',
+  type: 0,
+  status: '',
+  regionList: []
+});
+
+const typeEmun = {
+  0: '集团',
+  1: '水厂'
+}
+
+const formData = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+
+const rules = {
+  name: [
+    { required: true, message: '请输入结构名称', trigger: 'blur' },
+  ],
+  regionList: [
+    { type: 'array', required: true, message: '请选择省市区', trigger: 'change' }
+  ]
+}
+
+const regionOptions = ref([]);
+const typeOptions = [
+  { value: 0, label: '集团'},
+  { value: 1, label: '水厂'},
+]
+const statusOptions = [
+  { value: '', label: '全部'},
+  { value: 0, label: '正常'},
+  { value: 1, label: '停用'},
+]
+
+const cascaderProps = {
+  lazy: true,
+  lazyLoad(node, resolve) {
+    const { level } = node
+    getRegion({ regionLevel: level + 1, parentId: node.data.regionId, pageSize: 100 }).then(({ rows }) => {
+
+      console.log( "rows", rows );
+
+      const nodes = rows.map(item => {
+        return {
+          value: item.regionCod,
+          label: item.regionName,
+          regionId: item.regionId,
+          leaf: level >= 2,
+        }
+      })
+      resolve(nodes)
+    })
+  },
+}
+
+// 新增机构
+const handleAddOrganization = () => {
+  formData.value = { ...formData.value, type: 0, title: "新增机构", isAdd: true }
+  dialogVisible.value = true;
+}
+
+// 添加水厂
+const handleAddWaterFactory = (row) => {
+  formData.value = { ...formData.value, parentId: row.id, parentName: row.name, type: 1, title: "添加水厂", isAdd: true };
+  dialogVisible.value = true;
+}
+
+// 编辑水厂
+const handleEditOrganizationOrFactory = (row) => {
+  const { parentId, name: parentName, type, provinceCode, cityCode, countryCode }  = row;
+  formData.value = {
+    ...row,
+    parentId: type == 0 ? 0 : parentId,
+    parentName,
+    type,
+    title: type == 0 ? "编辑机构" : "编辑水厂",
+    regionList: [provinceCode, cityCode, countryCode]
+  };
+  dialogVisible.value = true;
+}
+
+// 删除机构or水厂
+const handleDelete = async ({ id }) => {
+  await delOrganization(id);
+  proxy.$modal.msgSuccess("删除成功");
+  initPageData();
+}
+
+const initPageData = async () => {
+  tableLoading.value = true;
+
+  const { rows } = await getOrganizationList(queryParams.value);
+
+  tableData.value = rows.map(item => ({
+    ...item,
+    typeText: typeEmun[item.type],
+    children: item.children ? item.children.map(child => ({
+      ...child,
+      typeText: typeEmun[child.type],
+      parentId: item.id
+    })) : [],
+  }));
+  tableLoading.value = false;
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10000,
+    name: '',
+    provinceCode: '',
+    type: 0,
+    status: '',
+    regionList: []
+  }
+  initPageData();
+}
+
+// 重置dialog表单
+const handleDialogReset = () => {
+  formRef.value.resetFields();
+  formData.value = {};
+}
+
+// 切换table - 状态
+const onChangeTag= (row) => {
+  const content = `请确认,是否要${row.status == 0 ? '停用' : '启用'}该${row.type == 0 ? '机构' : '水厂'}?`
+  ElMessageBox.confirm(
+    content,
+    '状态变更提醒',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  ).then(async () => {
+    const params = {
+      id: row.id,
+      status: row.status == 0 ? 1 : 0
+    }
+    await putOrganization(params);
+    initPageData();
+    proxy.$modal.msgSuccess("状态变更成功");
+  }).catch(() => {})
+}
+
+// 新增机构 - 保存
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      const [provinceCode, cityCode, countryCode] = formData.value.regionList || [];
+      const params = {
+        ...formData.value,
+        provinceCode,
+        cityCode,
+        countryCode
+      }
+      params.isAdd ? await postOrganization(params) : await putOrganization(params);
+      initPageData();
+      formRef.value.resetFields();
+      dialogVisible.value = false;
+      proxy.$modal.msgSuccess("操作成功");
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+onMounted(() => {
+  getRegion({ regionLevel: 1 }).then(({ rows }) => {
+    regionOptions.value = rows.map(item => ({ value: item.regionCod, label: item.regionName}));
+  })
+  initPageData();
+})
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="机构名称">
+            <el-input placeholder="请输入机构名称" v-model.trim="queryParams.name"></el-input>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="所属省份">
+            <el-select v-model="queryParams.provinceCode" placeholder="请选择所属省份" filterable clearable>
+              <el-option v-for="item in regionOptions" :key="item.value" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="机构类型">
+            <el-select v-model="queryParams.type" placeholder="请选择机构类型" filterable clearable>
+              <el-option v-for="item in typeOptions" :key="item.label" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="状态">
+            <el-select v-model="queryParams.status" placeholder="请选择状态" filterable clearable>
+              <el-option v-for="item in statusOptions" :key="item.label" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="18">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="handleAddOrganization" :loading="loading" :icon="CirclePlus">新增机构</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="tableLoading" row-key="id" border>
+        <el-table-column prop="name" label="机构名称" width="280" fixed/>
+        <el-table-column prop="code" label="机构编号" width="180" />
+        <el-table-column prop="typeText" label="机构类型" width="180"/>
+        <el-table-column prop="provinceName" label="省份" width="180"/>
+        <el-table-column prop="lxjcCounts" label="连续检测设备总数" width="180"/>
+        <el-table-column prop="robotCounts" label="实验室设备总数" width="180"/>
+        <el-table-column prop="concat" label="联系人" width="180"/>
+        <el-table-column prop="phone" label="联系电话" width="180"/>
+        <el-table-column prop="status" label="状态" width="180">
+          <template #default="{ row }">
+            <el-check-tag :checked="row.status == 0" type="success" @change="onChangeTag(row)">
+              {{ row.status == 0 ? '正常' : '停用' }}
+            </el-check-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createUser.nickName" label="创建人" width="180"/>
+        <el-table-column prop="createTime" label="创建时间" width="180"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="220">
+          <template #default="{ row }">
+            <el-button text type="primary" size="small" @click="handleAddWaterFactory(row)" :disabled="row.type == 1">添加水厂</el-button>
+            <el-button text type="primary" size="small" @click="handleEditOrganizationOrFactory(row)">编辑</el-button>
+            <el-popconfirm title="请确认是否删除?" @confirm="handleDelete(row)" width="200">
+              <template #reference>
+                <el-button text type="primary" size="small">删除</el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="formData.title" width="900" @closed="handleDialogReset">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="80px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="11">
+            <el-form-item label="名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="排序" prop="sort">
+              <el-input-number v-model="formData.sort" :min="1" :step="1"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="编号" prop="code">
+              <el-input v-model="formData.code" placeholder="请输入编号" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11" v-if="formData.type == 1" prop="parentId">
+            <el-form-item label="上级机构" prop="parentName">
+              <span>{{ formData.parentName }}</span>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11" prop="regionList">
+            <el-form-item label="省市区" prop="regionList">
+              <el-cascader
+                v-model="formData.regionList"
+                :props="cascaderProps"
+                placeholder="请选择省市区"
+                class="w-full"
+              /> 
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="详细地址" prop="address">
+              <el-input v-model="formData.address" placeholder="请输入机构类型" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="经度" prop="longitude">
+              <el-input v-model="formData.longitude" placeholder="请输入经度" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="纬度" prop="latitude">
+              <el-input v-model="formData.latitude" placeholder="请输入纬度" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="formData.status" placeholder="请选择机构状态" clearable>
+                <el-option label="正常" :value="0" />
+                <el-option label="停用" :value="1" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="联系人" prop="concat">
+              <el-input v-model="formData.concat" placeholder="请输入联系人" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="联系电话" prop="phone">
+              <el-input v-model="formData.phone" placeholder="请输入联系电话" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
+  </div>
+</template>

+ 178 - 0
src/views/configuration/agentia/index.vue

@@ -0,0 +1,178 @@
+<script setup>
+import { CirclePlus } from '@element-plus/icons-vue'
+import { getMedicineList, putMedicine, postMedicine, delMedicine } from '@/api/configuration'
+
+const { proxy } = getCurrentInstance();
+
+const queryParams = ref({ 
+  pageNum: 1,
+  pageSize: 10,
+ });
+
+const total = ref(0);
+const loading = ref(false);
+const tableData = ref([]);
+
+const formData = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+
+const rules = {
+  code: { required: true, message: '请输入药剂编号', trigger: 'blur' },
+  name: { required: true, message: '请输入药剂名称', trigger: 'blur' },
+}
+
+const initPageData = async () => {
+  loading.value = true;
+
+  const { total: tableTotal, rows } = await getMedicineList(queryParams.value);
+
+  tableData.value = rows.map(item => ({
+    ...item
+  }));
+  
+  total.value = tableTotal;
+  loading.value = false;
+}
+
+// Table - 删除
+const handleDelete = async ({ id }) => {
+  await delMedicine(id);
+  proxy.$modal.msgSuccess("删除成功");
+  initPageData();
+}
+
+// Table - 编辑
+const handleEdit = (row) => {
+  formData.value = { ...row };
+  dialogVisible.value = true;
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10
+  }
+  initPageData();
+}
+
+// Dialog - 新增 or 修改
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      !formData.value.id ? await postMedicine(formData.value) : await putMedicine(formData.value);
+      formRef.value.resetFields();
+      dialogVisible.value = false;
+      proxy.$modal.msgSuccess("操作成功");
+      initPageData();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+// Dialog - 重置表单
+const handleDialogReset = () => {
+  formRef.value.resetFields();
+  formData.value = {};
+}
+
+onMounted(() => {
+  initPageData();
+})
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="药剂编号">
+            <el-input v-model="queryParams.code" placeholder="请输入药剂编号" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="药剂名称">
+            <el-input v-model="queryParams.name" placeholder="请输入药剂名称" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="6">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增药剂</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
+        <el-table-column prop="code" label="药剂编号" fixed />
+        <el-table-column prop="name" label="药剂名称" />
+        <el-table-column prop="createUser.userName" label="创建人" />
+        <el-table-column prop="createTime" label="创建时间" />
+        <el-table-column prop="address" label="操作" fixed="right" width="100">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
+            <el-popconfirm title="请确认是否删除?" @confirm="handleDelete(row)" width="200">
+              <template #reference>
+                <el-button link type="primary" size="small">删除</el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        :total="total"
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="initPageData"
+      />
+    </el-card>
+    
+    <el-dialog v-model="dialogVisible" :title="formData.id? '编辑药剂':'新增药剂'" width="600" @closed="handleDialogReset">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="120px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="24">
+            <el-form-item label="药剂编号" prop="code">
+              <el-input v-model="formData.code" placeholder="请输入药剂编号" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="药剂名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入药剂名称" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 285 - 0
src/views/configuration/assay/index.vue

@@ -0,0 +1,285 @@
+<script setup>
+import { CirclePlus } from '@element-plus/icons-vue'
+import { getAssayList, postAssay, delAssay, putAssay, getMedicineList } from '@/api/configuration'
+
+const { proxy } = getCurrentInstance();
+
+const queryParams = ref({ 
+  pageNum: 1,
+  pageSize: 10,
+ });
+
+const total = ref(0);
+const loading = ref(false);
+const tableData = ref([]);
+
+const formData = ref({});
+const formRef = ref(null);
+
+const tableRowData = ref({});
+const yjOptions = ref([]);
+const yjFormData = ref({});
+
+const dialogVisible = ref(false);
+const dialogFyVisible = ref(false);
+const dialogYjVisible = ref(false);
+
+const rules = {
+  code: { required: true, message: '请输入化验项目编号', trigger: 'blur' },
+  name: { required: true, message: '请输入化验项目名称', trigger: 'blur' }
+}
+
+// 废液 - 默认配置
+const fyMockData = [
+  { relativeId: 0, name: '酸性' },
+  { relativeId: 1, name: '碱性' },
+  { relativeId: 2, name: '中性' }
+]
+
+// 废液 - 开启编辑
+const handleFyRowData = (row) => {
+  const fyList = row.fyList.map(item => {
+    return {
+      ...item,
+      name: fyMockData.find(({ relativeId }) => item.relativeId == relativeId)?.name || ''
+    }
+  });
+  if ( fyList && !fyList.length ) {
+    fyMockData.forEach(({ relativeId, name }) => {
+      fyList.push({
+        itemId: row.id,
+        type: 0,
+        relativeId,
+        name,
+        amount: 0
+      })
+    })
+  }
+  tableRowData.value = { ...row, fyList };
+  dialogFyVisible.value = true;
+}
+
+// 废液 - 保存编辑
+const handleFyConfirm = async () => {
+  await putAssay(tableRowData.value);
+  dialogFyVisible.value = false;
+  proxy.$modal.msgSuccess("操作成功");
+  initPageData();
+}
+
+
+// 药剂 - 开启编辑
+const handleYjRowData = async (row) => {
+  const { rows: yjRows } = await getMedicineList({ pageNum:1, pageSize: 1000 });
+  yjOptions.value = yjRows;
+  tableRowData.value = { ...row };
+  dialogYjVisible.value = true;
+}
+
+// 药剂编辑行
+const handleYjSaveRow = () => {
+  const { relativeId, amount } = yjFormData.value;
+  if (!relativeId) {
+    return proxy.$modal.msgError("请先选择药剂");
+  }
+  if (!amount && amount != 0) {
+    return proxy.$modal.msgError("请输入药剂消耗量");
+  }
+
+  const { name } = yjOptions.value.find(item => item.id == relativeId);
+
+  tableRowData.value.yjList.push({ ...yjFormData.value, name, type: 1, itemId: tableRowData.value.id });
+}
+
+// 药剂删除行
+const handleYjDeleteRow = (index) => {
+  tableRowData.value.yjList.splice(index, 1);
+}
+
+// 药剂最终保存
+const handleDialogYjConfirm = async () => {
+  await putAssay(tableRowData.value);
+  proxy.$modal.msgSuccess("操作成功");
+  dialogYjVisible.value = false;
+}
+
+// 药剂弹窗关闭
+const changeYjClose = () => {
+  tableRowData.value = {};
+  yjFormData.value = {};
+}
+
+const initPageData = async () => {
+  loading.value = true;
+
+  const { rows } = await getAssayList(queryParams.value);
+
+  tableData.value = rows.map(item => ({
+    ...item,
+  }));
+  
+  loading.value = false;
+}
+
+// Table - 编辑row
+const handleEditRow = (row) => {
+  formData.value = { ...row };
+  dialogVisible.value = true;
+}
+
+// Table - 删除
+const handleDelete = async ({ id }) => {
+  await delAssay(id);
+  proxy.$modal.msgSuccess("删除成功");
+  initPageData();
+}
+
+// Dialog - 新增 or 修改
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      !formData.value.id ? await postAssay(formData.value) : await putAssay(formData.value);
+      formRef.value.resetFields();
+      dialogVisible.value = false;
+      proxy.$modal.msgSuccess("操作成功");
+      initPageData();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+// Dialog - 重置表单
+const handleDialogReset = () => {
+  formRef.value.resetFields();
+  formData.value = {};
+}
+
+onMounted(() => initPageData());
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增化验项目</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id" border>
+        <el-table-column prop="code" label="化验项目编号" fixed/>
+        <el-table-column prop="name" label="化验项目名称" />
+        <el-table-column prop="fyAmount" label="理论废液产生总量(L)" width="180"/>
+        <el-table-column prop="yjAmount" label="理论消耗药剂总量(L)" width="180"/>
+        <el-table-column prop="createUser.userName" label="创建人"/>
+        <el-table-column prop="createTime" label="创建时间"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="310">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleFyRowData(row)">配置废液产生量</el-button>
+            <el-button link type="primary" size="small" @click="handleYjRowData(row)">配置药剂用量</el-button>
+            <el-button link type="primary" size="small" @click="handleEditRow(row)">编辑</el-button>
+            <el-popconfirm title="请确认是否删除?" @confirm="handleDelete(row)" width="200">
+              <template #reference>
+                <el-button text type="primary" size="small">删除</el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        :total="total"
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="initPageData"
+      />
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="formData.id ? '编辑化验项目' : '新增化验项目'" width="600" @closed="handleDialogReset">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="120px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="24">
+            <el-form-item label="化验项目编号" prop="code">
+              <el-input v-model.trim="formData.code" placeholder="请输入化验项目编号" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="化验项目名称" prop="name">
+              <el-input v-model.trim="formData.name" placeholder="请输入化验项目名称" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
+    <el-dialog v-model="dialogFyVisible" title="配置废液产生量" width="600">
+      <div class="mb-[20px] bg-[#f3f5f9] py-[10px] px-[10px] space-x-[10px] font-bold text-[red]">
+        <span>化验项目:</span>
+        <span>{{ tableRowData.name }}</span>
+      </div>
+ 
+      <el-table :data="tableRowData.fyList" style="width: 100%" table-layout="auto" border>
+        <el-table-column prop="name" label="废液名称" width="180" />
+        <el-table-column prop="amount" label="产生量(L)" >
+          <template #default="{ row }">
+            <el-input-number v-model="row.amount" :min="0" :max="10" :value-on-clear="0" placeholder="请输入产生量" class="w-full"/>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogFyVisible = false" >取 消</el-button>
+          <el-button @click="handleFyConfirm" type="primary">保 存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="dialogYjVisible" title="配置药剂用量" width="600" @closed="changeYjClose">
+      <div class="mb-[20px] bg-[#f3f5f9] py-[10px] px-[10px] space-x-[10px] font-bold">
+        <span>化验项目:</span>
+        <span class="text-[red]">{{ tableRowData.name }}</span>
+      </div>
+      
+      <ul class="flex justify-between items-center mb-[20px] space-x-[20px]">
+        <li class="w-[50%]">
+          <el-select v-model="yjFormData.relativeId" placeholder="请选择药剂" filterable clearable>
+            <el-option v-for="item in yjOptions" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </li>
+        <li class="w-[50%]">
+          <el-input v-model="yjFormData.amount" placeholder="请输入药剂消耗量" clearable />
+        </li>
+        <li>
+          <el-button @click="handleYjSaveRow" type="primary">添 加</el-button>
+        </li>
+      </ul>
+
+      <el-table :data="tableRowData.yjList || []" style="width: 100%" table-layout="auto" border>
+        <el-table-column prop="name" label="废液名称" width="180" />
+        <el-table-column prop="amount" label="产生量(L)" width="300" />
+        <el-table-column prop="handle" label="操作">
+          <template #default="scoped">
+            <el-button link type="primary" @click="handleYjDeleteRow(scoped.$index)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogYjVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogYjConfirm" type="primary">保 存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+</style>

+ 491 - 0
src/views/configuration/design/index.vue

@@ -0,0 +1,491 @@
+<script setup>
+import { CirclePlus, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const queryParams = ref({});
+const value = ref([])
+const formInline = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+const rules = {
+  name: [
+    { required: true, message: 'Please input Activity name', trigger: 'blur' },
+  ],
+  type: [
+    { type: 'array', required: true, message: 'Please select at least one activity type', trigger: 'change', }
+  ]
+}
+const options = [
+  {
+    value: 'guide',
+    label: 'Guide',
+    children: [
+      {
+        value: 'disciplines',
+        label: 'Disciplines',
+        children: [
+          {
+            value: 'consistency',
+            label: 'Consistency',
+          },
+          {
+            value: 'feedback',
+            label: 'Feedback',
+          },
+          {
+            value: 'efficiency',
+            label: 'Efficiency',
+          },
+          {
+            value: 'controllability',
+            label: 'Controllability',
+          },
+        ],
+      },
+      {
+        value: 'navigation',
+        label: 'Navigation',
+        children: [
+          {
+            value: 'side nav',
+            label: 'Side Navigation',
+          },
+          {
+            value: 'top nav',
+            label: 'Top Navigation',
+          },
+        ],
+      },
+    ],
+  },
+  {
+    value: 'component',
+    label: 'Component',
+    children: [
+      {
+        value: 'basic',
+        label: 'Basic',
+        children: [
+          {
+            value: 'layout',
+            label: 'Layout',
+          },
+          {
+            value: 'color',
+            label: 'Color',
+          },
+          {
+            value: 'typography',
+            label: 'Typography',
+          },
+          {
+            value: 'icon',
+            label: 'Icon',
+          },
+          {
+            value: 'button',
+            label: 'Button',
+          },
+        ],
+      },
+      {
+        value: 'form',
+        label: 'Form',
+        children: [
+          {
+            value: 'radio',
+            label: 'Radio',
+          },
+          {
+            value: 'checkbox',
+            label: 'Checkbox',
+          },
+          {
+            value: 'input',
+            label: 'Input',
+          },
+          {
+            value: 'input-number',
+            label: 'InputNumber',
+          },
+          {
+            value: 'select',
+            label: 'Select',
+          },
+          {
+            value: 'cascader',
+            label: 'Cascader',
+          },
+          {
+            value: 'switch',
+            label: 'Switch',
+          },
+          {
+            value: 'slider',
+            label: 'Slider',
+          },
+          {
+            value: 'time-picker',
+            label: 'TimePicker',
+          },
+          {
+            value: 'date-picker',
+            label: 'DatePicker',
+          },
+          {
+            value: 'datetime-picker',
+            label: 'DateTimePicker',
+          },
+          {
+            value: 'upload',
+            label: 'Upload',
+          },
+          {
+            value: 'rate',
+            label: 'Rate',
+          },
+          {
+            value: 'form',
+            label: 'Form',
+          },
+        ],
+      },
+      {
+        value: 'data',
+        label: 'Data',
+        children: [
+          {
+            value: 'table',
+            label: 'Table',
+          },
+          {
+            value: 'tag',
+            label: 'Tag',
+          },
+          {
+            value: 'progress',
+            label: 'Progress',
+          },
+          {
+            value: 'tree',
+            label: 'Tree',
+          },
+          {
+            value: 'pagination',
+            label: 'Pagination',
+          },
+          {
+            value: 'badge',
+            label: 'Badge',
+          },
+        ],
+      },
+      {
+        value: 'notice',
+        label: 'Notice',
+        children: [
+          {
+            value: 'alert',
+            label: 'Alert',
+          },
+          {
+            value: 'loading',
+            label: 'Loading',
+          },
+          {
+            value: 'message',
+            label: 'Message',
+          },
+          {
+            value: 'message-box',
+            label: 'MessageBox',
+          },
+          {
+            value: 'notification',
+            label: 'Notification',
+          },
+        ],
+      },
+      {
+        value: 'navigation',
+        label: 'Navigation',
+        children: [
+          {
+            value: 'menu',
+            label: 'Menu',
+          },
+          {
+            value: 'tabs',
+            label: 'Tabs',
+          },
+          {
+            value: 'breadcrumb',
+            label: 'Breadcrumb',
+          },
+          {
+            value: 'dropdown',
+            label: 'Dropdown',
+          },
+          {
+            value: 'steps',
+            label: 'Steps',
+          },
+        ],
+      },
+      {
+        value: 'others',
+        label: 'Others',
+        children: [
+          {
+            value: 'dialog',
+            label: 'Dialog',
+          },
+          {
+            value: 'tooltip',
+            label: 'Tooltip',
+          },
+          {
+            value: 'popover',
+            label: 'Popover',
+          },
+          {
+            value: 'card',
+            label: 'Card',
+          },
+          {
+            value: 'carousel',
+            label: 'Carousel',
+          },
+          {
+            value: 'collapse',
+            label: 'Collapse',
+          },
+        ],
+      },
+    ],
+  },
+  {
+    value: 'resource',
+    label: 'Resource',
+    children: [
+      {
+        value: 'axure',
+        label: 'Axure Components',
+      },
+      {
+        value: 'sketch',
+        label: 'Sketch Templates',
+      },
+      {
+        value: 'docs',
+        label: 'Design Documentation',
+      },
+    ],
+  },
+]
+
+const tableData = [
+  {
+    id: 1,
+    date: '2016-05-02',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+  {
+    id: 2,
+    date: '2016-05-04',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+  {
+    id: 3,
+    date: '2016-05-01',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+    children: [
+      {
+        id: 31,
+        date: '2016-05-01',
+        name: 'wangxiaohu',
+        address: 'No. 189, Grove St, Los Angeles',
+      },
+      {
+        id: 32,
+        date: '2016-05-01',
+        name: 'wangxiaohu',
+        address: 'No. 189, Grove St, Los Angeles',
+      },
+    ],
+  },
+  {
+    id: 4,
+    date: '2016-05-03',
+    name: 'wangxiaohu',
+    address: 'No. 189, Grove St, Los Angeles',
+  },
+]
+
+const loading = ref(false);
+
+const initPageData = async () => {
+
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  datePieckerValue.value = [dayjs(new Date()).format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')];
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.deviceNo = firstDeviceNo;
+  initPageData();
+}
+
+// 切换table - 状态
+const onChangeTag= (row) => {
+  ElMessageBox.confirm(
+    '是要停用机构吗?',
+    '状态变更提醒',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  ).then(() => {
+    ElMessage({
+      type: 'success',
+      message: 'Delete completed',
+    })
+  }).catch(() => {
+    ElMessage({
+      type: 'info',
+      message: 'Delete canceled',
+    })
+  })
+  row.checked = !row.checked;
+}
+
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate((valid, fields) => {
+    if (valid) {
+      console.log('submit!')
+      formRef.value.resetFields();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="药剂编号">
+            <el-input v-model="queryParams.deviceNo" placeholder="请输入设备SN" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="药剂名称">
+            <el-input v-model="queryParams.deviceNo" placeholder="请输入设备SN" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="6">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增质控样</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="tableLoading" row-key="id">
+        <el-table-column prop="date" label="所属水厂" fixed/>
+        <el-table-column prop="name" label="取样点位"/>
+        <el-table-column prop="name" label="化验项目"/>
+        <el-table-column prop="name" label="设计出水上限值"/>
+        <el-table-column prop="name" label="设计进水上限值"/>
+        <el-table-column prop="name" label="设计进水下限值"/>
+        <el-table-column prop="lastAssayTime" label="创建人"/>
+        <el-table-column prop="beginAssayTime" label="创建时间"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="100">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleTableBtnClick(row)">编辑</el-button>
+            <el-button link type="primary" size="small" @click="handleTableBtnClick(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" title="新增设计值" width="600">
+      <el-form :model="formInline" :rules="rules" ref="formRef" label-width="130px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="24">
+            <el-form-item label="所属水厂" prop="name">
+              <el-select v-model="queryParams.deviceNo" placeholder="请选择上级机构" filterable clearable>
+                <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="取样点位">
+              <el-select v-model="queryParams.deviceNo" placeholder="请选择上级机构" filterable clearable>
+                <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="化验项目">
+              <el-select v-model="queryParams.deviceNo" placeholder="请选择上级机构" filterable clearable>
+                <el-option v-for="item in options" :key="item.deviceNo" :label="item.deviceName" :value="item.deviceNo" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设计出水上限值">
+              <el-input-number v-model="queryParams.deviceNo" :min="1" style="width: 320px;" /> 
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设计进水上限值">
+              <el-input-number v-model="queryParams.deviceNo" :min="1" style="width: 320px;" /> 
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设计进水下限值">
+              <el-input-number v-model="queryParams.deviceNo" :min="1" style="width: 320px;" /> 
+            </el-form-item>
+          </el-col>
+          
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 299 - 0
src/views/configuration/flow/index.vue

@@ -0,0 +1,299 @@
+<script setup>
+import { CirclePlus } from '@element-plus/icons-vue'
+
+import { getWorkFlowList, getAllWaterFactoryList, getPositionList, getAssayList, putWorkFlow, postWorkFlow, delWorkFlow } from '@/api/configuration'
+import { getAllDeviceList} from '@/api/client/manage'
+
+const { proxy } = getCurrentInstance();
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+});
+
+const total = ref(0);
+const loading = ref(false);
+const tableData = ref([]);
+
+const waterFactoryOptions = ref([]);
+const deviceOptions = ref([]);
+const pointOptions = ref([]);
+const assayOptions = ref([]);
+
+const formData = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+const rules = {
+  name: { required: true, message: '请输入化验流程名称', trigger: 'blur' },
+  organizationId: { required: true, message: '请选择所属水厂', trigger: 'blur' },
+  code: { required: true, message: '请输入化验流程编号', trigger: 'blur' },
+  deviceId: { required: true, message: '请选择所属设备', trigger: 'blur' },
+  totalSteps: { required: true, message: '化验总步数', trigger: 'blur' },
+  items: { type: 'array', required: true, message: '请添加化验内容', trigger: 'blur' }
+}
+
+const initPageData = async () => {
+  loading.value = true;
+
+  const { total: tableTotal, rows } = await getWorkFlowList(queryParams.value);
+
+  tableData.value = rows.map(val => ({
+    ...val,
+    items: val.items.map(item => ({
+      ...item,
+      itemId: item.id,
+      itemName: item.assayItem?.name,
+      positionId: item.position?.id,
+      positionName: item.position?.name,
+      workflowId: val.id
+    }))
+  }));
+
+  total.value = tableTotal;
+  loading.value = false;
+}
+
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10
+  }
+  initPageData();
+}
+
+// 编辑流程
+const handleTableEdit = (row) => {
+  formData.value = { ...row };
+  dialogVisible.value = true;
+}
+
+// Dialog - 新增 or 修改 化验流程
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate(async(valid, fields) => {
+    if (valid) {
+      formData.value.id ? await putWorkFlow(formData.value) : await postWorkFlow(formData.value);
+      formRef.value.resetFields();
+      dialogVisible.value = false;
+      proxy.$modal.msgSuccess("操作成功");
+      initPageData();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+// Dialog - 重置表单
+const handleDialogReset = () => {
+  formData.value = {};
+  formRef.value.resetFields();
+}
+
+// Dialog - 新增行
+const handleAddRow = () => {
+  const { itemId, positionId, id: workflowId } = formData.value;
+  if ( !itemId ) {
+    return proxy.$modal.msgError("请先选择化验项目");
+  }
+  if ( !positionId ) {
+    return proxy.$modal.msgError("请先选择取样点位");
+  }
+  formRef.value.validate("items");
+  const params = {
+    itemId,
+    itemName: assayOptions.value.find(({ id }) => id === itemId).name,
+    positionId,
+    positionName: pointOptions.value.find(({ id }) => id === positionId).name,
+    workflowId
+  }
+  if ( !formData.value.items?.length ) formData.value.items = [];
+  formData.value.items.push(params);
+}
+
+// Table - 删除
+const handleDelete = async ({ id }) => {
+  await delWorkFlow(id);
+  proxy.$modal.msgSuccess("删除成功");
+  initPageData();
+}
+
+
+// Tag - 删除行
+const handleCloseTag = (index) => {
+  formData.value.items.splice(index, 1);
+}
+
+onMounted(async () => {
+  // 水厂
+  getAllWaterFactoryList().then(({ data }) => waterFactoryOptions.value = data)
+  // 设备
+  getAllDeviceList().then(({ data }) => deviceOptions.value = data)
+  // 化验项目
+  getAssayList({ pageNum: 1, pageSize: 9999 }).then(({ rows }) => assayOptions.value = rows)
+  // 点位
+  getPositionList({ pageNum: 1, pageSize: 10000 }).then(({ rows }) => pointOptions.value = rows)
+  // 初始化数据
+  initPageData();
+})
+</script>
+
+<template>
+  <section>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="水厂名称">
+            <el-select v-model="queryParams.organizationId" placeholder="请选择水厂名称" filterable clearable>
+              <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="设备SN / 名称">
+            <el-select v-model="queryParams.deviceId" placeholder="请选择设备SN" filterable clearable>
+              <el-option v-for="item in deviceOptions" :key="item.deviceId" :label="(item.deviceSn ? item.deviceSn + '-' : '') + item.deviceName  || ''" :value="item.deviceId" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="化验流程名称">
+            <el-input v-model.trim="queryParams.name" placeholder="请输入化验流程名称" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="化验流程编号">
+            <el-input v-model.trim="queryParams.code" placeholder="请输入化验流程编号" clearable />
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="18">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增化验流程</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
+        <el-table-column prop="code" label="化验流程编号" width="180" fixed/>
+        <el-table-column prop="name" label="化验流程名称" width="180" />
+        <el-table-column prop="organization.name" label="所属水厂名称" width="180"/>
+        <el-table-column prop="bizDevice.deviceSn" label="所属设备SN" width="180"/>
+        <el-table-column prop="bizDevice.deviceName" label="所属设备名称" width="180"/>
+        <el-table-column prop="xxxxxxxx" label="化验项目" width="400" show-overflow-tooltip>
+          <template #default="{row}">
+            <div class="space-x-[4px]">
+              <el-tag v-for="item in row.items" :key="item.id">{{item?.assayItem?.name}}({{ item?.position?.name }})</el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="totalSteps" label="化验总步数" width="180"/>
+        <el-table-column prop="createUser.nickName" label="创建人" width="180"/>
+        <el-table-column prop="createTime" label="创建时间" width="180"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="100">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleTableEdit(row)">编辑</el-button>
+            <el-popconfirm title="请确认是否删除?" @confirm="handleDelete(row)" width="200">
+              <template #reference>
+                <el-button link type="primary" size="small">删除</el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize" @pagination="initPageData" />
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="formData.id ? '编辑化验流程' : '新增化验流程'" width="900" @closed="handleDialogReset">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="110px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="11">
+            <el-form-item label="化验流程名称" prop="name">
+              <el-input v-model.trim="formData.name" placeholder="请输入流程名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="所属水厂" prop="organizationId">
+              <el-select v-model="formData.organizationId" placeholder="请选择所属水厂" filterable clearable>
+                <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="化验流程编号" prop="code">
+              <el-input v-model.trim="formData.code" placeholder="请输入化验流程编号" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="所属设备" prop="deviceId">
+              <el-select v-model="formData.deviceId" placeholder="请选所属设备" filterable clearable>
+                <el-option v-for="item in deviceOptions" :key="item.deviceId" :label="item.deviceName" :value="item.deviceId" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item label="化验总步数" prop="totalSteps">
+              <el-input v-model.trim="formData.totalSteps" placeholder="请输入总步数" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider border-style="dashed" />
+
+        <el-form-item label="化验内容" prop="items">
+          <el-row class="w-full" :gutter="20">
+            <el-col :span="8">
+              <el-select v-model="formData.itemId" placeholder="请选择化验项目" filterable clearable>
+                <el-option v-for="item in assayOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-col>
+            <el-col :span="8">
+              <el-select v-model="formData.positionId" placeholder="请选择取样点位" filterable clearable>
+                <el-option v-for="item in pointOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-col>
+            <el-col :span="8">
+              <el-button type="primary" @click="handleAddRow(row)">添加</el-button>
+            </el-col>
+          </el-row>
+          <div class="w-full min-h-[100px] px-[10px] py-[10px] mt-[30px] bg-[#f3f5f9] space-x-[10px]">
+            <el-tag v-for="item, index in formData.items" :key="index" closable type="primary" @close="handleCloseTag(index)">
+              <span>{{ item.itemName }}({{ item.positionName }})</span>
+            </el-tag>
+          </div>
+        </el-form-item>
+        
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
+  </section>
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 213 - 0
src/views/configuration/point/index.vue

@@ -0,0 +1,213 @@
+<script setup>
+import { CirclePlus, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getPositionList, getAllWaterFactoryList, postPosition, putPosition, delPosition } from '@/api/configuration'
+import { getAllDeviceList} from '@/api/client/manage'
+
+
+const { proxy } = getCurrentInstance();
+
+const queryParams = ref({ 
+  pageNum: 1,
+  pageSize: 10,
+ });
+
+const total = ref(0);
+const loading = ref(false);
+const tableData = ref([]);
+
+const waterFactoryOptions = ref([]);
+const deviceOptions = ref([]);
+
+const formData = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+const rules = {
+  organizationId: { required: true, message: '请选择所属水厂', trigger: 'blur' },
+  deviceId: { required: true, message: '请选择所属设备', trigger: 'blur' },
+  name: { required: true, message: '请输入取样点位名称', trigger: 'blur' },
+  code: { required: true, message: '请输入取样点位编号', trigger: 'blur' }
+}
+
+// Table - 编辑
+const handleEdit = (row) => {
+  formData.value = { ...row };
+  dialogVisible.value = true;
+}
+
+// Table - 删除
+const handleDelete = async ({ id }) => {
+  await delPosition(id);
+  proxy.$modal.msgSuccess("删除成功");
+  initPageData();
+}
+
+const initPageData = async () => {
+  loading.value = true;
+
+  const { total: tableTotal, rows } = await getPositionList(queryParams.value);
+
+  const typeEmun = {
+    1: '化验室',
+    2: '连续检测'
+  }
+  tableData.value = rows.map(item => ({
+    ...item,
+    typeName: typeEmun[item.type]
+  }));
+  total.value = tableTotal;
+  loading.value = false;
+  
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10
+  }
+  initPageData();
+}
+
+// Dialog - 新增 or 修改
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      !formData.value.id ? await postPosition(formData.value) : await putPosition(formData.value);
+      formRef.value.resetFields();
+      dialogVisible.value = false;
+      proxy.$modal.msgSuccess("操作成功");
+      initPageData();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+// Dialog - 重置表单
+const handleDialogReset = () => {
+  formRef.value.resetFields();
+  formData.value = {};
+}
+
+onMounted(() => {
+  getAllWaterFactoryList().then(({ data }) => waterFactoryOptions.value = data);
+  getAllDeviceList().then(({ data }) => deviceOptions.value = data);
+  initPageData();
+})
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="水厂名称" prop="organizationId">
+            <el-select v-model="queryParams.organizationId" placeholder="请选择水厂名称" filterable clearable>
+              <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="设备SN" prop="deviceId">
+            <el-select v-model="queryParams.deviceId" placeholder="请选择设备SN" filterable clearable>
+              <el-option v-for="item in deviceOptions" :key="item.deviceId" :label="item.deviceName" :value="item.deviceId" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="6">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增点位</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="tableLoading" row-key="id">
+        <el-table-column prop="organization.name" label="所属水厂" width="220" fixed/>
+        <el-table-column prop="bizDevice.deviceName" label="所属设备SN" />
+        <el-table-column prop="name" label="取样点位名称"/>
+        <el-table-column prop="code" label="取样点位编号"/>
+        <el-table-column prop="createUser.userName" label="创建人"/>
+        <el-table-column prop="createTime" label="创建时间"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="120">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
+            <el-popconfirm title="请确认是否删除?" @confirm="handleDelete(row)" width="200">
+              <template #reference>
+                <el-button text type="primary" size="small">删除</el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        :total="total"
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="initPageData"
+      />
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="(formData.id ? '编辑' : '新增') + '取样点位'" width="600" @closed="handleDialogReset">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="120px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="24">
+            <el-form-item label="所属水厂" prop="organizationId">
+              <el-select v-model="formData.organizationId" placeholder="请选择所属水厂" filterable clearable>
+                <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="所属设备" prop="deviceId">
+              <el-select v-model="formData.deviceId" placeholder="请选择所属设备" filterable clearable>
+                <el-option v-for="item in deviceOptions" :key="item.deviceId" :label="item.deviceName" :value="item.deviceId" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="取样点位名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入取样点位名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="取样点位编号" prop="code">
+              <el-input v-model="formData.code" placeholder="请输入取样点位编号" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 261 - 0
src/views/configuration/sample/index.vue

@@ -0,0 +1,261 @@
+<script setup>
+import { CirclePlus } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getSampleList, getAllWaterFactoryList, postSample, putSample, getAssayList, delSample, delWorkFlow } from '@/api/configuration'
+
+const { proxy } = getCurrentInstance();
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+});
+
+const total = ref(0);
+const loading = ref(false);
+const tableData = ref([]);
+
+const waterFactoryOptions = ref([]);
+const assayOptions = ref([]);
+
+const formData = ref({});
+const formRef = ref(null);
+
+const dialogVisible = ref(false);
+const rules = {
+  deviceWorks: {required: true, message: '请选择所属水厂', trigger: 'blur' },
+  assayItem: {required: true, message: '请选择化验项目', trigger: 'blur' },
+  resultValue: {required: true, message: '请输入质控值', trigger: 'blur' },
+  // remark: {required: true, message: '请输入浮动率', trigger: 'blur' },
+  time: [
+    { type: 'array', required: true, message: '请选择有效日期', trigger: 'change', }
+  ]
+}
+
+const formatNumber = (num) => {
+  return (num == 0 || num) ? Number(num).toFixed(2) : '';
+}
+
+const handleInpChange = () => {
+  const { resultValue, remark, highValue, lowValue } = formData.value;
+  const isExistsValue = !!(resultValue && remark);
+  formData.value.highValue = isExistsValue ? formatNumber(resultValue + ( resultValue * (remark / 100) )) : highValue;
+  formData.value.lowValue = isExistsValue ?  formatNumber(resultValue - ( resultValue * (remark / 100) )) : lowValue;
+}
+
+const initPageData = async () => {
+  
+  loading.value = true;
+
+  const { total: tableTotal, rows } = await getSampleList(queryParams.value);
+
+  tableData.value = rows.map(item => {
+    return ({
+      ...item,
+      lowValue: formatNumber(item.lowValue),
+      highValue: formatNumber(item.highValue),
+    })
+  })
+
+  total.value = tableTotal;
+  loading.value = false;
+}
+
+// 查询
+const handleQuery = async() => {
+  initPageData();
+}
+
+// 重置
+const handleReset = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10
+  }
+  initPageData();
+}
+
+// Table - 编辑
+const handleTableEdit = (row) => {
+  const { beginTime, endTime, assayItem } = row
+  formData.value = { ...row, time: [ beginTime, endTime ], assayItem: Number(assayItem) };
+  dialogVisible.value = true;
+}
+
+// Table - 删除
+const handleDelete = async ({ valueId }) => {
+  await delSample(valueId);
+  proxy.$modal.msgSuccess("删除成功");
+  initPageData();
+}
+
+
+// Dialog - 新增or编辑
+const handleDialogConfirm = async () => {
+  if (!formRef.value) return
+  await formRef.value.validate(async (valid, fields) => {
+    if (valid) {
+      const [ beginTime, endTime ] = formData.value.time;
+      const params = { ...formData.value, beginTime, endTime }
+      formData.value.valueId ? await putSample(params) : await postSample(params);
+      dialogVisible.value = false;
+      formRef.value.resetFields();
+      proxy.$modal.msgSuccess("操作成功");
+      initPageData();
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+// Dialog - 重置表单
+const handleDialogReset = () => {
+  formData.value = {};
+  formRef.value.resetFields();
+}
+
+onMounted(() => {
+  // 水厂
+  getAllWaterFactoryList().then(({ data }) => waterFactoryOptions.value = data);
+  getAssayList({ pageNum: 1, pageSize: 10000 }).then(res => {
+    assayOptions.value = res.rows;
+  })
+  initPageData();
+});
+</script>
+
+<template>
+  <div>
+    <el-card shadow="never" :body-style="{ border: '0px' }" style="margin-bottom: 10px;">
+      <template #header>
+        <p class="space-x-[10px]">
+          <span class="font-bold">数据筛选</span>
+        </p>
+      </template>
+      <el-row class="pt-[5px]" justify="space-between" :gutter="20">
+        <el-col :span="6">
+          <el-form-item label="水厂名称">
+            <el-select v-model="queryParams.deviceWorks" placeholder="请选择水厂名称" filterable clearable>
+              <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6">
+          <el-form-item label="化验项目">
+            <el-select v-model="queryParams.assayItem" placeholder="请选择化验项目" filterable clearable>
+              <el-option v-for="item in assayOptions" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="6" :offset="6">
+          <div class="flex justify-end">
+            <el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
+            <el-button @click="handleReset" :loading="loading">重置</el-button>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <el-card shadow="never" :body-style="{ padding: '20px' }">
+      <div class="flex justify-between items-center mb-[10px]">
+        <el-button type="primary" @click="dialogVisible = true" :loading="loading" :icon="CirclePlus">新增质控值</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
+        <el-table-column prop="assayItemBean.name" label="化验项目" fixed/>
+        <el-table-column prop="highValue" label="上线值"/>
+        <el-table-column prop="resultValue" label="质控值"/>
+        <el-table-column prop="lowValue" label="下限值"/>
+        <el-table-column prop="beginTimeAndendTime" label="有效期" width="200">
+          <template #default="{ row }">
+            {{ row.beginTime }} - {{ row.endTime }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="organization.name" label="所属水厂"  width="220"/>
+        <el-table-column prop="createUser.nickName" label="创建人" />
+        <el-table-column prop="createTime" label="创建时间" width="160"/>
+        <el-table-column prop="address" label="操作" fixed="right" width="100">
+          <template #default="{ row }">
+            <el-button link type="primary" size="small" @click="handleTableEdit(row)">编辑</el-button>
+            <el-popconfirm title="请确认是否删除?" @confirm="handleDelete(row)" width="200">
+              <template #reference>
+                <el-button link type="primary" size="small">删除</el-button>
+              </template>
+            </el-popconfirm>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize" @pagination="initPageData" />
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="formData.valueId ? '编辑质控值' : '新增质控值'" width="500" @closed="handleDialogReset">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="80px" label-position="left" require-asterisk-position="right">
+        <el-row justify="space-between">
+          <el-col :span="24">
+            <el-form-item label="所属水厂" prop="deviceWorks">
+              <el-select v-model="formData.deviceWorks" placeholder="请选择所属水厂" filterable clearable>
+                <el-option v-for="item in waterFactoryOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="化验项目" prop="assayItem">
+              <el-select v-model="formData.assayItem" placeholder="请选择化验项目" filterable clearable>
+                <el-option v-for="item in assayOptions" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="质控值" prop="resultValue">
+              <el-input-number v-model="formData.resultValue" :min="1" style="width: 320px;" placeholder="请输入质控值" @change="handleInpChange"/> 
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="浮动率" prop="remark">
+              <div>
+                <div class="space-x-[10px]">
+                  <el-input-number v-model="formData.remark" :min="0" style="width: 320px;" placeholder="请输入浮动率" @change="handleInpChange"/> 
+                  <span>%</span>
+                </div>
+                <span class="text-[#999] text-[12px]">根据浮动率,自动计算上限值和下限值。四舍五入保留2为小数。</span>
+              </div>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="上限值" prop="highValue">
+              <el-input-number v-model="formData.highValue" :min="1" style="width: 320px;" placeholder="请输入上限值"/> 
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="下限值" prop="lowValue">
+              <el-input-number v-model="formData.lowValue" :min="1" style="width: 320px;" placeholder="请输入上限值"/> 
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="有效期" prop="time">
+              <el-date-picker
+                v-model="formData.time"
+                type="daterange"
+                range-separator="To"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="YYYY-MM-DD"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" >取 消</el-button>
+          <el-button @click="handleDialogConfirm" type="primary">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 1 - 1
vite.config.js

@@ -11,7 +11,7 @@ export default defineConfig(({ mode, command }) => {
     // 部署生产环境和开发环境下的URL。
     // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
     // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
-    base: VITE_APP_ENV === 'production' ? '/sjzc/' : '/sjzc/',
+    base: VITE_APP_ENV === 'production' ? '' : '',
     plugins: createVitePlugins(env, command === 'build'),
     resolve: {
       // https://cn.vitejs.dev/config/#resolve-alias