Przeglądaj źródła

feature:增加送样预约和送样记录

liuzhenlin 2 tygodni temu
rodzic
commit
24c17dc6fe

+ 2 - 0
components.d.ts

@@ -24,6 +24,8 @@ declare module 'vue' {
     VanCheckbox: typeof import('vant/es')['Checkbox']
     VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
     VanCol: typeof import('vant/es')['Col']
+    VanCollapse: typeof import('vant/es')['Collapse']
+    VanCollapseItem: typeof import('vant/es')['CollapseItem']
     VanDatePicker: typeof import('vant/es')['DatePicker']
     VanDialog: typeof import('vant/es')['Dialog']
     VanEmpty: typeof import('vant/es')['Empty']

+ 30 - 0
src/api/instr/sample.ts

@@ -0,0 +1,30 @@
+/*
+ * @Author: liuzhenlin 461480418@qq.ocm
+ * @Date: 2023-01-19 14:06:58
+ * @LastEditors: liuzhenlin
+ * @LastEditTime: 2023-02-15 18:45:00
+ * @Description: file content
+ * @FilePath: \oms\api\system\user.js
+ */
+import request from '/@/utils/micro_request.js'
+const instrPath = import.meta.env.VITE_INSTR_ADMIN
+
+export function useSampleApi() {
+  return {
+    // 预添加送样预约
+    add(query?: object) {
+      return request.postRequest(instrPath, 'TusInstrumentSampleDelivery', 'Create', query)
+    },
+    // 获取送样选项
+    getSampleTestOption(query?: object) {
+      return request.postRequest(instrPath, 'TusInstrumentSampleDelivery', 'OptionByUser', query)
+    },
+    getList(query?: object) {
+      return request.postRequest(instrPath, 'TusInstrumentSampleDelivery', 'GetListByInst', query)
+    },
+    // 使用人取消送样预约
+    userCancelAppoint(query?: object) {
+      return request.postRequest(instrPath, 'TusInstrumentSampleDelivery', 'UserCancel', query)
+    },
+  }
+}

+ 8 - 0
src/router.ts

@@ -66,6 +66,14 @@ const routes = [
       title: '仪器预约'
     }
   },
+  {
+    name: 'sampleAppoint',
+    path: '/sample-appoint',
+    component: () => import('/@/view/instr/sampleAppoint.vue'),
+    meta: {
+      title: '送样预约'
+    }
+  },
   {
     name: 'instrCalendar',
     path: '/instr-calendar',

+ 373 - 0
src/view/instr/appointList/SampleAppointList.vue

@@ -0,0 +1,373 @@
+<template>
+  <div class="panel-wrap">
+    <van-empty v-if="state.appointList.length === 0 && state.finished" description="暂无预约记录" />
+    <van-list v-else v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad"
+      class="data-list">
+      <div class="inst-item mb20" v-for="(v, index) in state.appointList" :key="index">
+        <div class="flex flex-between mb20">
+          <div>
+            <div class="mr10">
+              <span class="fontSize14 primary-color bold">{{ v.instName }}</span>
+            </div>
+          </div>
+          <!-- Status Tag moved here -->
+        </div>
+
+        <div class="flex mb20">
+          <div class="equ-tit">
+            <span class="fontSize14 bold">送样时间:</span>
+          </div>
+          <div>
+            <span class="fontSize14">{{ formatDate(new Date(v.deliverTime), 'YYYY-mm-dd HH:MM') }}</span>
+          </div>
+        </div>
+        <div class="flex mb20">
+          <div class="equ-tit">
+            <span class="fontSize14 bold">检测时间:</span>
+          </div>
+          <div>
+            <span class="fontSize14">{{ formatDate(new Date(v.testTime), 'YYYY-mm-dd HH:MM') }}</span>
+          </div>
+        </div>
+        <div class="flex mb20">
+          <div class="equ-tit">
+            <span class="fontSize14 bold">样品数:</span>
+          </div>
+          <div>
+            <span class="fontSize14">{{ v.sampleNum }}</span>
+          </div>
+        </div>
+        <div class="flex mb20">
+          <div class="equ-tit">
+            <span class="fontSize14 bold">申请人:</span>
+          </div>
+          <div>
+            <span class="fontSize14">{{ v.userName }}</span>
+          </div>
+        </div>
+        <div class="flex mb20">
+          <div class="equ-tit">
+            <span class="fontSize14 bold">状态:</span>
+          </div>
+          <van-tag :type="getStatusType(v.deliverStatus)">{{ setStatus(v.deliverStatus) }}</van-tag>
+        </div>
+        <!-- Detection Info Clickable -->
+        <div class="flex mb20" v-if="v.sampleItem || v.testSampleItem">
+          <div class="equ-tit">
+            <span class="fontSize14 bold">检测信息:</span>
+          </div>
+          <div>
+            <span v-if="v.sampleItem" class="fontSize14 primary-color pointer mr10"
+              @click="showSampleItem(v.sampleItem, '预约信息')">预约检测项目</span>
+            <span v-if="v.testSampleItem" class="fontSize14 primary-color pointer"
+              @click="showSampleItem(v.testSampleItem, '实测信息')">实测检测项目</span>
+          </div>
+        </div>
+
+        <div class="flex mb20" v-if="v.testResultName">
+          <div class="equ-tit">
+            <span class="fontSize14 bold">检测结果:</span>
+          </div>
+          <div style="flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
+            <span class="fontSize14 primary-color pointer">
+              <a :href="v.testResult" target="_blank" class="file-link">
+                {{ v.testResultName }}
+              </a>
+            </span>
+          </div>
+        </div>
+
+        <div v-if="v.order">
+          <van-collapse v-model="activeNames">
+            <van-collapse-item title="收费明细" :name="v.id">
+              <div class="flex mb10">
+                <div class="equ-tit">
+                  <span class="fontSize14 bold">计费编号:</span>
+                </div>
+                <div>
+                  <span class="fontSize14">{{ v.order.fboCode }}</span>
+                </div>
+              </div>
+              <div class="flex mb10">
+                <div class="equ-tit">
+                  <span class="fontSize14 bold">收费:</span>
+                </div>
+                <div>
+                  <span class="fontSize14 price">¥{{ v.order.fboActualAmount }}</span>
+                </div>
+              </div>
+              <div class="flex mb10">
+                <div class="equ-tit">
+                  <span class="fontSize14 bold">预估费用:</span>
+                </div>
+                <div>
+                  <span class="fontSize14 price">¥{{ v.order.fboExpectedAmount }}</span>
+                </div>
+              </div>
+            </van-collapse-item>
+          </van-collapse>
+        </div>
+
+        <!-- Cancel Button moved to bottom -->
+        <div class="flex mt20" style="justify-content: flex-end;"
+          v-if="v.deliverStatus == '10' || v.deliverStatus == '20'">
+          <van-button class="scan-txt" plain type="danger" size="small" @click.stop="handleCancelAppoint(v)">
+            取消预约
+          </van-button>
+        </div>
+      </div>
+    </van-list>
+
+    <!-- Dialog for Sample Items -->
+    <van-dialog v-model:show="itemsDialogShow" :title="dialogTitle">
+      <div class="dialog-content">
+        <div class="flex mb10 flex-col" style="padding: 10px;">
+          <div class="flex mb4" v-for="(test, idx) in currentSampleItems" :key="idx">
+            <span class="fontSize14">{{ testItemInfo(test) }}</span>
+          </div>
+        </div>
+      </div>
+    </van-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref, onMounted } from 'vue'
+import { useSampleApi } from '/@/api/instr/sample'
+import to from 'await-to-js'
+import { formatDate } from '/@/utils/formatTime'
+import { showDialog, showNotify } from 'vant'
+
+const props = defineProps({
+  instId: {
+    type: Number,
+    default: 0
+  }
+})
+
+const sampleApi = useSampleApi()
+const activeNames = ref([])
+
+const state = reactive({
+  loading: false,
+  finished: false,
+  appointList: [] as any[],
+  queryForm: {
+    pageNum: 1,
+    pageSize: 10,
+    instId: props.instId
+  },
+  total: 0,
+})
+
+const toParse = (str: string) => {
+  try {
+    return JSON.parse(str) || []
+  } catch (e) {
+    return []
+  }
+}
+
+const testItemInfo = (test: any) => {
+  return `检测项:${test.name},数量:${test.count}`
+}
+
+const itemsDialogShow = ref(false)
+const currentSampleItems = ref<any[]>([])
+const dialogTitle = ref('检测信息')
+
+const showSampleItem = (itemsStr: string, title: string = '检测信息') => {
+  currentSampleItems.value = toParse(itemsStr)
+  dialogTitle.value = title
+  itemsDialogShow.value = true
+}
+
+const getStatusType = (key: string) => {
+  let type = 'default'
+  switch (key) {
+    case '10': // 待审核
+      type = 'warning'
+      break
+    case '20': // 已通过
+      type = 'success'
+      break
+    case '30': // 已驳回
+      type = 'danger'
+      break
+    case '50': // 已检测
+      type = 'primary'
+      break
+    case '40': // 已取消
+    case '11': // 已退回
+      type = 'default'
+      break
+  }
+  return type as 'primary' | 'success' | 'warning' | 'danger' | 'default'
+}
+
+const setStatus = (key: string) => {
+  let str = ''
+  switch (key) {
+    case '10':
+      str = '待审核'
+      break
+    case '11':
+      str = '已退回'
+      break
+    case '20':
+      str = '已通过'
+      break
+    case '30':
+      str = '已驳回'
+      break
+    case '40':
+      str = '已取消'
+      break
+    case '50':
+      str = '已检测'
+      break
+  }
+  return str
+}
+
+const handleCancelAppoint = (row: any) => {
+  showDialog({
+    title: '提示',
+    message: '确认取消预约?',
+    showCancelButton: true,
+  })
+    .then(async () => {
+      const params = { id: row.id }
+      const [err, res]: any = await to(sampleApi.userCancelAppoint(params))
+      if (err) return
+      if (res?.code === 200) {
+        showNotify({ type: 'success', message: '取消成功' })
+        state.queryForm.pageNum = 1
+        state.appointList = []
+        state.finished = false
+        onLoad()
+      }
+    })
+    .catch(() => {
+      // on cancel
+    })
+}
+const onLoad = async () => {
+  state.loading = true
+  state.queryForm.instId = props.instId
+
+  const [err, res]: any = await to(sampleApi.getList(state.queryForm))
+  state.loading = false
+  if (err) {
+    state.finished = true
+    return
+  }
+
+  if (res?.code === 200) {
+    const list = res?.data?.list || []
+    if (state.queryForm.pageNum === 1) {
+      state.appointList = list
+    } else {
+      state.appointList = [...state.appointList, ...list]
+    }
+
+    state.total = res?.data?.total || 0
+    state.queryForm.pageNum++
+
+    if (state.appointList.length >= state.total) {
+      state.finished = true
+    }
+  } else {
+    state.finished = true
+  }
+}
+
+onMounted(() => {
+  // onLoad will be triggered by van-list automatically initially
+})
+// Expose onLoad for parent component to call
+defineExpose({
+  onLoad: () => {
+    state.queryForm.pageNum = 1
+    state.finished = false
+    // Need to reset list? The onLoad usually appends... 
+    // If called from parent for refresh, we should probably reset.
+    // Let's modify logic slightly or just set pageNum to 1 and empty list
+    state.appointList = []
+    onLoad()
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+* {
+  box-sizing: border-box;
+}
+
+.panel-wrap {
+  height: 100%;
+
+  .data-list {
+    .inst-item {
+      border-radius: 10px;
+      padding: 15px;
+      box-shadow: -2px 0px 9px rgba(0, 0, 0, 0.12);
+      margin-bottom: 20px;
+      background-color: #fff;
+
+      .equ-tit {
+        width: 80px;
+        min-width: 80px;
+      }
+    }
+  }
+}
+
+.fontSize14 {
+  font-size: 14px;
+}
+
+.bold {
+  font-weight: bold;
+}
+
+.primary-color {
+  color: #1989fa;
+}
+
+.price {
+  color: #ee0a24;
+}
+
+.flex {
+  display: flex;
+}
+
+.flex-between {
+  justify-content: space-between;
+}
+
+.mb20 {
+  margin-bottom: 20px;
+}
+
+.mb10 {
+  margin-bottom: 10px;
+}
+
+.mr10 {
+  margin-right: 10px;
+}
+
+.mt20 {
+  margin-top: 20px;
+}
+
+.flex-col {
+  flex-direction: column;
+}
+
+.pointer {
+  cursor: pointer;
+}
+</style>

+ 32 - 24
src/view/instr/appointList/index.vue

@@ -10,28 +10,33 @@
   <!-- 页面内容 -->
   <div class="home">
     <!-- <van-pull-refresh v-model="loading" @refresh="onRefresh"> -->
-    <van-tabs v-model:active="active" @click-tab="onClickTab">
-      <van-tab title="即将上机">
-        <div class="list-container">
-          <soon-geton v-if="active === 0" ref="soonGetonRef" />
-        </div>
-      </van-tab>
-      <van-tab title="正在上机">
-        <div class="list-container">
-          <in-progress v-if="active === 1" ref="inProgressRef" />
-        </div>
-      </van-tab>
-      <van-tab title="等待审核">
-        <div class="list-container">
-          <my-appoint v-if="active === 2" ref="myAppointRef" />
-        </div>
-      </van-tab>
-      <van-tab title="预约记录">
-        <div class="list-container">
-          <appoint-record v-if="active === 3" ref="recordRef" />
-        </div>
-      </van-tab>
-    </van-tabs>
+          <van-tabs v-model:active="active" @click-tab="onClickTab">
+            <van-tab title="即将上机">
+              <div class="list-container">
+                <soon-geton v-if="active === 0" ref="soonGetonRef" />
+              </div>
+            </van-tab>
+            <van-tab title="正在上机">
+              <div class="list-container">
+                <in-progress v-if="active === 1" ref="inProgressRef" />
+              </div>
+            </van-tab>
+            <van-tab title="等待审核">
+              <div class="list-container">
+                <my-appoint v-if="active === 2" ref="myAppointRef" />
+              </div>
+            </van-tab>
+            <van-tab title="预约记录">
+              <div class="list-container">
+                <appoint-record v-if="active === 3" ref="recordRef" />
+              </div>
+            </van-tab>
+            <van-tab title="送样预约">
+              <div class="list-container">
+                <sample-appoint-list v-if="active === 4" ref="sampleAppointListRef" />
+              </div>
+            </van-tab>
+          </van-tabs>
     <!-- </van-pull-refresh> -->
     <van-tabbar route :placeholder="true">
       <van-tabbar-item replace to="/home" icon="wap-home-o">首页</van-tabbar-item>
@@ -46,13 +51,16 @@
   import InProgress from './inProgress/index.vue'
   import MyAppoint from './myAppoint/index.vue'
   import AppointRecord from './appointRecord/index.vue'
+  import SampleAppointList from './SampleAppointList.vue'
+
   export default {
     name: 'appointList',
-    components: { SoonGeton, InProgress, MyAppoint, AppointRecord },
+    components: { SoonGeton, InProgress, MyAppoint, AppointRecord, SampleAppointList },
     data() {
       return {
         loading: false,
-        active: 0
+        active: 0,
+        activeTab: 0
       }
     },
     // onLoad(option) {

+ 16 - 4
src/view/instr/detail.vue

@@ -290,8 +290,14 @@
     <van-action-bar-button
       v-if="state.instDetail.instStatus == '10' && state.instDetail.isAppointment == '10'"
       type="primary"
-      text="立即预约"
-      @click="onAppoint"
+      text="使用预约"
+      @click="onAppoint('use')"
+    />
+    <van-action-bar-button
+      v-if="state.instDetail.instStatus == '10' && state.instDetail.isSampleDelivery == '10'"
+      type="warning"
+      text="送样预约"
+      @click="onAppoint('sample')"
     />
   </van-action-bar>
   <!-- 通知 -->
@@ -381,6 +387,7 @@
     list: [] as any[],
     popupShow: false,
     needToKnowShow: false,
+    appointType: '',
   })
   const noticeInfo = ref({ noticeTitle: '', noticeContent: '' })
 
@@ -517,7 +524,8 @@
     showNotify({ type: 'success', message: !state.instDetail.following ? '收藏成功' : '已取消收藏' })
     getDetail(state.instDetail.id)
   }
-  const onAppoint = async () => {
+  const onAppoint = async (type: string) => {
+    state.appointType = type
     state.needToKnowShow = true
   }
   const confirmAppoint = async () => {
@@ -527,7 +535,11 @@
       showNotify({ type: 'danger', message: '您已被拉入黑名单,无法预约,请联系管理员' })
       return
     }
-    onRouterPush('/instr-appoint', { id: state.instDetail.id })
+    if (state.appointType === 'sample') {
+      onRouterPush('/sample-appoint', { id: state.instDetail.id })
+    } else {
+      onRouterPush('/instr-appoint', { id: state.instDetail.id })
+    }
   }
   const onRouterPush = (val: string, params?: any) => {
     router.push({

+ 8 - 1
src/view/instr/list.vue

@@ -65,6 +65,13 @@
                     >
                       可预约
                     </van-tag>
+                    <van-tag
+                      v-if="v.instStatus == '10' && v.isSampleDelivery == '10'"
+                      type="warning"
+                      class="status-tag ml6"
+                    >
+                      可送样
+                    </van-tag>
                   </div>
                   <footer>
                     <div class="flex flex-top mb4 mt-auto">
@@ -572,7 +579,7 @@
 
     &.name {
       font-weight: bold;
-      font-size: 16px;
+      font-size: 14px;
     }
   }
 

+ 444 - 0
src/view/instr/sampleAppoint.vue

@@ -0,0 +1,444 @@
+<!--
+ * @Author: wanglj wanglijie@dashoo.cn
+ * @Date: 2025-03-24 09:17:15
+ * @LastEditors: wanglj wanglijie@dashoo.cn
+ * @LastEditTime: 2025-03-28 11:44:38
+ * @FilePath: \labsop_h5\src\view\instr\detail.vue
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+-->
+<template>
+  <div class="container">
+    <van-form ref="formRef" required="auto">
+      <van-cell-group>
+        <van-field
+          v-model="state.form.deliverTime"
+          is-link
+          readonly
+          label="送样时间"
+          placeholder="请选择计划送样时间"
+          @click="state.showDeliverTime = true"
+          :rules="[{ required: true, message: '送样时间不能为空' }]"
+        />
+        <van-field
+          v-model="state.form.testTime"
+          is-link
+          readonly
+          label="检测时间"
+          placeholder="请选择检测时间"
+          @click="state.showTestTime = true"
+          :rules="[{ required: false }]"
+        />
+        <van-field
+          label="课题"
+          placeholder="课题"
+          readonly
+          v-model="state.form.projectName"
+          :rules="[{ required: false }]"
+        />
+        <van-field
+          v-if="state.InstCfgCharge"
+          label="经费卡"
+          placeholder="请选择经费卡"
+          is-link
+          readonly
+          @click="state.showExpenseCard = true"
+          v-model="state.form.expenseCardName"
+          :rules="[{ required: true, message: '经费卡不能为空' }]"
+        ></van-field>
+        <van-field
+          label="样品说明"
+          placeholder="输入样品说明"
+          v-model="state.form.sampleDesc"
+          :rules="[{ required: false }]"
+        ></van-field>
+        <van-field
+          label="联系电话"
+          placeholder="输入联系电话"
+          v-model="state.form.userContact"
+          :rules="[{ required: false }]"
+        ></van-field>
+      </van-cell-group>
+
+      <div class="mt10 card-wrap">
+        <h4>检测信息</h4>
+        <div
+          class="card-item"
+          v-for="(v, i) in state.form.sampleItem"
+          :key="i"
+        >
+          <div class="flex flex-between mb10">
+            <span class="label">检测项:</span>
+            <span class="value bold">{{ v.name }}</span>
+          </div>
+          <div class="flex flex-between mb10 flex-center">
+            <span class="label">样本数量:</span>
+            <van-stepper
+              v-model="v.count"
+              min="0"
+              integer
+            />
+          </div>
+          <div class="flex flex-between mb10">
+            <span class="label">单价:</span>
+            <span class="value price">¥{{ v.price }}</span>
+          </div>
+          <div class="flex flex-between">
+            <span class="label">总金额:</span>
+            <span class="value price bold">¥{{ (v.count * v.price).toFixed(2) }}</span>
+          </div>
+        </div>
+      </div>
+
+      <CustomForm
+        ref="customFormRef"
+        :formData="state.form.sampleForm"
+      ></CustomForm>
+    </van-form>
+  </div>
+  <van-action-bar placeholder>
+    <van-action-bar-button
+      class="w100"
+      type="primary"
+      text="保存"
+      @click="onClickButton"
+      :loading="state.loading"
+    />
+  </van-action-bar>
+
+  <!-- 送样时间 -->
+  <van-popup
+    v-model:show="state.showDeliverTime"
+    position="bottom"
+  >
+    <van-date-picker
+      v-model="state.currentDate"
+      title="选择送样时间"
+      @confirm="onConfirmDeliverDate"
+      @cancel="state.showDeliverTime = false"
+    />
+  </van-popup>
+  <van-popup
+    v-model:show="state.showDeliverTimeTime"
+    position="bottom"
+  >
+    <van-time-picker
+      v-model="state.currentTime"
+      title="选择送样时间"
+      @confirm="onConfirmDeliverTime"
+      @cancel="state.showDeliverTimeTime = false"
+    />
+  </van-popup>
+
+  <!-- 检测时间 -->
+  <van-popup
+    v-model:show="state.showTestTime"
+    position="bottom"
+  >
+    <van-date-picker
+      v-model="state.currentDateTest"
+      title="选择检测时间"
+      @confirm="onConfirmTestDate"
+      @cancel="state.showTestTime = false"
+    />
+  </van-popup>
+    <van-popup
+    v-model:show="state.showTestTimeTime"
+    position="bottom"
+  >
+    <van-time-picker
+      v-model="state.currentTimeTest"
+      title="选择检测时间"
+      @confirm="onConfirmTestTime"
+      @cancel="state.showTestTimeTime = false"
+    />
+  </van-popup>
+
+  <!-- 选择经费卡 -->
+  <van-popup
+    v-model:show="state.showExpenseCard"
+    position="bottom"
+  >
+    <van-picker
+      :columns="fundsList"
+      :columns-field-names="{ text: 'finAccount', value: 'id' }"
+      @confirm="pickExpenseCard"
+      @cancel="state.showExpenseCard = false"
+    />
+  </van-popup>
+</template>
+
+<script lang="ts" setup>
+  import to from 'await-to-js'
+  import { useRoute, useRouter } from 'vue-router'
+  import { useInstrApi } from '/@/api/instr'
+  import { defineAsyncComponent, onMounted, reactive, ref } from 'vue'
+  import { formatDate } from '/@/utils/formatTime'
+  import { showNotify, showToast } from 'vant'
+  import { useProApi } from '/@/api/project'
+  import { useSampleApi } from '/@/api/instr/sample'
+
+  const CustomForm = defineAsyncComponent(() => import('/@/components/CustomForm.vue'))
+  const route = useRoute()
+  const router = useRouter()
+  const projApi = useProApi()
+  const instApi = useInstrApi()
+  const sampleApi = useSampleApi()
+
+  const fundsList = ref([])
+  const formRef = ref()
+  const customFormRef = ref()
+
+  const state = reactive({
+    loading: false,
+    InstCfgCharge: false,
+    showDeliverTime: false,
+    showDeliverTimeTime: false,
+    currentDate: [],
+    currentTime: [],
+    showTestTime: false,
+    showTestTimeTime: false,
+    currentDateTest: [],
+    currentTimeTest: [],
+    showExpenseCard: false,
+    form: {
+      instId: 0,
+      deliverTime: '',
+      testTime: '',
+      projectName: '',
+      projectId: null,
+      expenseCardId: 0,
+      expenseCardName: '',
+      sampleDesc: '',
+      sampleItem: [] as any[],
+      userContact: '',
+      sampleForm: [] as any[],
+    },
+  })
+
+  // Determine Default Time
+  const now = new Date()
+  const dateStr = formatDate(now, 'YYYY-mm-dd')
+  const timeStr = formatDate(now, 'HH:MM')
+  state.currentDate = dateStr.split('-')
+  state.currentTime = timeStr.split(':')
+  state.currentDateTest = dateStr.split('-')
+  state.currentTimeTest = timeStr.split(':')
+  state.form.deliverTime = `${dateStr} ${timeStr}`
+  state.form.testTime = `${dateStr} ${timeStr}`
+
+
+  const onConfirmDeliverDate = ({ selectedValues }) => {
+      state.currentDate = selectedValues
+      state.showDeliverTime = false
+      state.showDeliverTimeTime = true
+  }
+  const onConfirmDeliverTime = ({ selectedValues }) => {
+      state.currentTime = selectedValues
+      state.form.deliverTime = `${state.currentDate.join('-')} ${state.currentTime.join(':')}`
+      state.showDeliverTimeTime = false
+  }
+
+    const onConfirmTestDate = ({ selectedValues }) => {
+      state.currentDateTest = selectedValues
+      state.showTestTime = false
+      state.showTestTimeTime = true
+  }
+  const onConfirmTestTime = ({ selectedValues }) => {
+      state.currentTimeTest = selectedValues
+      state.form.testTime = `${state.currentDateTest.join('-')} ${state.currentTimeTest.join(':')}`
+      state.showTestTimeTime = false
+  }
+
+
+  const init = async () => {
+    getSampleConfig()
+    getChargeConfig()
+  }
+
+  // 送样配置信息
+  const getSampleConfig = async () => {
+    const params = {
+      instId: state.form.instId,
+      code: 'InstCfgSample',
+    }
+    const [err, res]: ToResponse = await to(instApi.getSettingDetail({ ...params }))
+    if (err) return
+    state.form.sampleForm = res?.data?.config.sampleForm ? JSON.parse(res.data.config.sampleForm) : []
+  }
+
+  // 计费配置信息
+  const getChargeConfig = async () => {
+    const params = {
+      instId: state.form.instId,
+      code: 'InstCfgCharge',
+    }
+    const [err, res]: ToResponse = await to(instApi.getSettingDetail({ ...params }))
+    if (err) return
+    state.InstCfgCharge = res?.data.config.enable && !res?.data.config.sampleFreeEnable
+    state.form.sampleItem = res?.data?.config?.sampleItemPrice || []
+    state.form.sampleItem = state.form.sampleItem.map((item) => {
+      return {
+        ...item,
+        count: 1,
+      }
+    })
+    await getMyProjectInfo()
+  }
+
+  const getMyProjectInfo = async () => {
+    const [err, res]: ToResponse = await to(projApi.getMySelfProjectGroup({}))
+    if (err) return
+    state.form.projectName = res?.data.pgName || ''
+    state.form.projectId = res?.data.id || null
+    if (state.form.projectId) {
+      getFundsData()
+      getTestList()
+    }
+  }
+
+  const getFundsData = async () => {
+    const [err, res]: ToResponse = await to(projApi.getFinanceAccountList({ projId: state.form.projectId }))
+    if (err) return
+    fundsList.value = res?.data.list ? [res?.data.list] : []
+    if (fundsList.value && fundsList.value.length > 0 && fundsList.value[0].length > 0) {
+      state.form.expenseCardId = fundsList.value[0][0].id
+      state.form.expenseCardName = fundsList.value[0][0].finAccount
+    }
+  }
+
+  const getTestList = async () => {
+    const [err, res]: ToResponse = await to(
+      sampleApi.getSampleTestOption({
+        instId: state.form.instId,
+        projectId: state.form.projectId,
+      }),
+    )
+    if (err) return
+    if (res.data.length > 0) {
+      state.form.sampleItem.forEach((item) => {
+        item.price = res.data.find((price) => price.name === item.name)?.price || 0
+      })
+    }
+  }
+
+  // 经费卡选择
+  const pickExpenseCard = ({ selectedOptions }) => {
+    state.form.expenseCardId = selectedOptions[0].id
+    state.form.expenseCardName = selectedOptions[0].finAccount
+    state.showExpenseCard = false
+  }
+
+  const onClickButton = async () => {
+    state.loading = true
+    const [errValid] = await to(formRef.value.validate())
+    if (errValid) {
+       state.loading = false
+       return
+    }
+
+    if (state.InstCfgCharge && !state.form.expenseCardId) {
+      showNotify({ type: 'warning', message: '请选择经费卡' })
+      state.loading = false
+      return
+    }
+
+    const customForm = customFormRef.value.getFormData()
+    if (state.form.sampleForm.length > 0 && !customForm) {
+      state.loading = false
+      return
+    }
+
+    const sampleItem = state.form.sampleItem.map((item) => {
+      return {
+        name: item.name,
+        count: Number(item.count),
+      }
+    })
+
+    let params: any = Object.assign({}, state.form)
+    params.sampleItem = sampleItem
+    params.sampleForm = JSON.stringify(customForm || [])
+
+    const [err, res]: ToResponse = await to(sampleApi.add(params))
+    state.loading = false
+    if (err) return
+    if (res && res.code == 200) {
+       showToast({
+          type: 'success',
+          message: '提交成功'
+       })
+       router.back()
+    }
+  }
+
+  onMounted(() => {
+    state.form.instId = route.query.id ? +route.query.id : 0
+    init()
+  })
+</script>
+
+<style lang="scss" scoped>
+  .container {
+    flex: 1;
+    padding: 10px;
+    background-color: #f7f8fa;
+    overflow-y: auto;
+
+    h4 {
+      height: 18px;
+      line-height: 18px;
+      display: flex;
+      margin: 10px 0;
+      span {
+        font-weight: normal;
+        margin-left: auto;
+      }
+      &::before {
+        display: inline-block;
+        content: '';
+        width: 3px;
+        height: 18px;
+        background-color: #1c9bfd;
+        margin-right: 4px;
+        vertical-align: middle;
+      }
+    }
+  }
+  .card-wrap {
+    padding-bottom: 20px;
+  }
+  .card-item {
+    background: #fff;
+    padding: 15px;
+    border-radius: 8px;
+    margin-bottom: 10px;
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+
+    .label {
+      color: #646566;
+      font-size: 14px;
+    }
+    .value {
+      color: #323233;
+      font-size: 14px;
+      &.bold {
+        font-weight: bold;
+      }
+      &.price {
+        color: #ee0a24;
+      }
+    }
+  }
+  .flex {
+    display: flex;
+  }
+  .flex-between {
+    justify-content: space-between;
+  }
+  .flex-center {
+    align-items: center;
+  }
+  .mb10 {
+    margin-bottom: 10px;
+  }
+</style>