文件上传完成

master
贾肃 4 months ago
parent 889ccabb94
commit a2299cce9c
  1. 2
      package.json
  2. 3
      src/api/basicData/breedingBaseApi.js
  3. 67
      src/api/basicData/cropVarietyApi.js
  4. 18
      src/api/basicData/dataGatherApi.js
  5. 181
      src/api/basicData/genoManageApi.js
  6. 65
      src/api/upload.js
  7. 5
      src/assets/icons/svg/annex.svg
  8. 3
      src/assets/styles/ruoyi.scss
  9. 2
      src/components/baseSelection/index.vue
  10. 24
      src/components/cropSelection/index.vue
  11. 288
      src/components/fileUploader/index.vue
  12. 8
      src/main.js
  13. 2
      src/router/index.js
  14. 3
      src/utils/EventBus.js
  15. 41
      src/utils/md5.js
  16. 14
      src/utils/request.js
  17. 11
      src/utils/ruoyi.js
  18. 18
      src/views/basicData/breedingBase/breedingBaseForm.vue
  19. 2
      src/views/basicData/breedingBase/index.vue
  20. 212
      src/views/basicData/cropVariety/index.vue
  21. 141
      src/views/basicData/dataGather/index.vue
  22. 360
      src/views/genoTypeData/genoManage/index.vue
  23. 151
      src/views/genoTypeData/genoManage/uploadGeno.vue
  24. 1
      src/views/system/base/mapMarkers.vue
  25. 253
      src/views/tableTypeData/plantTableData/index.vue
  26. 11
      src/views/tableTypeData/rawData/index.vue
  27. 11
      src/views/tableTypeData/tabularData/index.vue

@ -32,8 +32,10 @@
"fuse.js": "7.0.0", "fuse.js": "7.0.0",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"jsencrypt": "3.3.2", "jsencrypt": "3.3.2",
"mitt": "^3.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "2.1.7", "pinia": "2.1.7",
"promise-queue-plus": "^1.2.2",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"vue": "3.4.27", "vue": "3.4.27",
"vue-cropper": "1.1.1", "vue-cropper": "1.1.1",

@ -11,6 +11,9 @@ export function selBreedingBasePage(query) {
// 查询所有基地 // 查询所有基地
export function selAllBreedingBase(query) { export function selAllBreedingBase(query) {
return request({ return request({
headers: {
repeatSubmit: false
},
url: '/baseNurseryInfo/selectByPage', url: '/baseNurseryInfo/selectByPage',
method: 'post', method: 'post',
params: query params: query

@ -0,0 +1,67 @@
import request from '@/utils/request'
// 查询作物列表
export function selCropInfo(query) {
return request({
headers: {
repeatSubmit: false
},
url: '/crop_info/selCropInfo',
method: 'post',
data: query
})
}
// 查询作物品种列表
export function queryCropVarietyPage(query) {
return request({
url: '/breed_info/queryByPage',
method: 'get',
params: query
})
}
// 查询作物品种不分页
export function selCropVariety(query) {
return request({
url: '/breed_info/selectByPage',
method: 'post',
data: query
})
}
// 通过id查询作物品种
export function selCropVarietyById(query) {
return request({
url: '/breed_info/queryById',
method: 'get',
params: query
})
}
// 添加作物品种
export function addCropVariety(query) {
return request({
url: '/breed_info/add',
method: 'post',
data: query
})
}
// 修改作物品种
export function updateCropVariety(query) {
return request({
url: '/breed_info/edit',
method: 'put',
data: query
})
}
// 删除作物品种
export function delCropVariety(query) {
return request({
url: '/breed_info/deleteById',
method: 'get',
params: query
})
}

@ -0,0 +1,18 @@
import request from '@/utils/request'
// 查询数据采集设备列表
export function selDataGather(query) {
return request({
url: '/device_info/queryByPage',
method: 'get',
params: query
})
}
// 修改设备所属基地
export function updateDataGather(query) {
return request({
url: '/device_info/updBaseNursery',
method: 'post',
data: query
})
}

@ -0,0 +1,181 @@
import request from '@/utils/request'
// 查询界
export function selKingdomInfo(query) {
return new Promise((resolve, reject) => {
request({
url: '/kingdom_info/selKingdomInfo',
method: 'post',
data: query
}).then(res => {
resolve(res.data.map(item => {
return {
level: 1,
label: item.kingdomName,
value: item.id,
prop:'kingdomId',
children: [],
key:`kingdomId-${item.id}`
}
}))
}).catch(reject)
})
}
// 查询门
export function selPhylumInfo(query) {
return new Promise((resolve, reject) => {
request({
url: '/phylum_info/selPhylumInfo',
method: 'post',
data: query
}).then(res => {
resolve(res.data.map(item => {
return {
level: 2,
label: item.phylumName,
value: item.id,
prop:'phylumId',
children: [],
key:`phylumId-${item.id}`,
parent: `kingdomId-${item.parentId}`
}
}))
}).catch(reject)
})
}
// 查询纲
export function selClassInfo(query) {
return new Promise((resolve, reject) => {
request({
url: '/class_info/selClassInfo',
method: 'post',
data: query
}).then(res => {
resolve(res.data.map(item => {
return {
level: 3,
label: item.className,
value: item.id,
prop:'classId',
children: [],
key:`classId-${item.id}`,
parent: `phylumId-${item.parentId}`
}
}))
}).catch(reject)
})
}
// 查询目
export function selOrderInfo(query) {
return new Promise((resolve, reject) => {
request({
url: '/order_info/selOrderInfo',
method: 'post',
data: query
}).then(res => {
resolve(res.data.map(item => {
return {
level: 4,
label: item.orderName,
value: item.id,
prop:'orderId',
children: [],
key:`orderId-${item.id}`,
parent: `classId-${item.parentId}`
}
}))
}).catch(reject)
})
}
// 查询科
export function selFamilyInfo(query) {
return new Promise((resolve, reject) => {
request({
url: '/family_info/selFamilyInfo',
method: 'post',
data: query
}).then(res => {
resolve(res.data.map(item => {
return {
level: 5,
label: item.familyName,
value: item.id,
prop:'familyId',
children: [],
key:`familyId-${item.id}`,
parent: `orderId-${item.parentId}`
}
}))
}).catch(reject)
})
}
// 查询属
export function selGenusInfo(query) {
return new Promise((resolve, reject) => {
request({
url: '/genus_info/selGenusInfo',
method: 'post',
data: query
}).then(res => {
resolve(res.data.map(item => {
return {
level: 6,
label: item.genusName,
value: item.id,
prop:'genusId',
children: [],
key:`genusId-${item.id}`,
parent: `familyId-${item.parentId}`
}
}))
}).catch(reject)
})
}
// 添加基因数据
export function addGeno(data) {
return request({
url: '/gene_data/add',
method: 'post',
data: data,
upload:true
})
}
//分页查询基因数据
export function selGenoPage(query) {
return request({
url: '/gene_data/queryByPage',
method: 'get',
params: query
})
}
//通过id查询基因数据
export function selGenoById(query) {
return request({
url: '/gene_data/queryById',
method: 'get',
params: query
})
}
//修改基因数据
export function editGeno(data) {
return request({
url: '/gene_data/edit',
method: 'post',
data: data,
upload:true
})
}
//通过id删除基因数据
export function delGeno(query) {
return request({
url: '/gene_data/deleteById',
method: 'get',
params: query
})
}

@ -0,0 +1,65 @@
import axios from 'axios'
import {getToken} from "@/utils/auth.js";
const http = axios.create({
maxConcurrent: 5, //并发为1
queueOptions: {
retry: 3, //请求失败时,最多会重试3次
retryIsJump: false //是否立即重试, 否则将在请求队列尾部插入重试请求
},
baseURL: import.meta.env.VITE_APP_BASE_API,
headers:{
'Authorization': 'Bearer ' + getToken()
}
})
http.interceptors.response.use(response => {
return response.data
})
/**
* 根据文件的md5获取未上传完的任务
* @param identifier 文件md5
* @returns {Promise<AxiosResponse<any>>}
*/
const taskInfo = (identifier) => {
return http.get(`/sys_upload_task/taskInfo/${identifier}`)
}
/**
* 初始化一个分片上传任务
* @param identifier 文件md5
* @param fileName 文件名称
* @param totalSize 文件大小
* @param chunkSize 分块大小
* @returns {Promise<AxiosResponse<any>>}
*/
const initTask = ({ identifier, fileName, totalSize, chunkSize }) => {
return http.post('/sys_upload_task/initTask/', {identifier, fileName, totalSize, chunkSize})
}
/**
* 获取预签名分片上传地址
* @param identifier 文件md5
* @param partNumber 分片编号
* @returns {Promise<AxiosResponse<any>>}
*/
const preSignUrl = ({ identifier, partNumber }) => {
return http.get(`/sys_upload_task/${identifier}/${partNumber}`)
}
/**
* 合并分片
* @param identifier
* @returns {Promise<AxiosResponse<any>>}
*/
const merge = (identifier,data) => {
return http.post(`/sys_upload_task/merge/${identifier}`,data)
}
export {
taskInfo,
initTask,
preSignUrl,
merge
}

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon / PaperClipOutlined">
<path id="Vector" d="M18.2639 4.60898C16.0561 2.40117 12.4608 2.40117 10.2553 4.60898L4.13811 10.7215C4.09826 10.7613 4.07717 10.8152 4.07717 10.8715C4.07717 10.9277 4.09826 10.9816 4.13811 11.0215L5.00295 11.8863C5.04248 11.9257 5.096 11.9478 5.15178 11.9478C5.20756 11.9478 5.26108 11.9257 5.30061 11.8863L11.4178 5.77383C12.1772 5.01445 13.1873 4.59727 14.2608 4.59727C15.3342 4.59727 16.3444 5.01445 17.1014 5.77383C17.8608 6.5332 18.278 7.54336 18.278 8.61445C18.278 9.68789 17.8608 10.6957 17.1014 11.4551L10.867 17.6871L9.85686 18.6973C8.91233 19.6418 7.37717 19.6418 6.43264 18.6973C5.97561 18.2402 5.72483 17.6332 5.72483 16.9863C5.72483 16.3395 5.97561 15.7324 6.43264 15.2754L12.6178 9.09258C12.7748 8.93789 12.9811 8.85117 13.2014 8.85117H13.2037C13.424 8.85117 13.628 8.93789 13.7826 9.09258C13.9397 9.24961 14.024 9.45586 14.024 9.67617C14.024 9.89414 13.9373 10.1004 13.7826 10.2551L8.72717 15.3059C8.68733 15.3457 8.66623 15.3996 8.66623 15.4559C8.66623 15.5121 8.68733 15.566 8.72717 15.6059L9.59201 16.4707C9.63155 16.5101 9.68506 16.5322 9.74084 16.5322C9.79663 16.5322 9.85014 16.5101 9.88967 16.4707L14.9428 11.4176C15.4092 10.9512 15.6647 10.3324 15.6647 9.67383C15.6647 9.01523 15.4069 8.39414 14.9428 7.93008C13.9795 6.9668 12.4139 6.96914 11.4506 7.93008L10.8506 8.53242L5.2678 14.1129C4.88888 14.4896 4.58853 14.9377 4.38414 15.4314C4.17976 15.9251 4.07542 16.4544 4.07717 16.9887C4.07717 18.0738 4.50139 19.0934 5.2678 19.8598C6.06233 20.652 7.10295 21.048 8.14358 21.048C9.1842 21.048 10.2248 20.652 11.017 19.8598L18.2639 12.6176C19.3303 11.5488 19.9209 10.1262 19.9209 8.61445C19.9233 7.10039 19.3326 5.67773 18.2639 4.60898Z" fill="black" fill-opacity="0.45"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -75,6 +75,9 @@
} }
.el-table { .el-table {
.el-table__cell{
position: static;
}
.el-table__header-wrapper, .el-table__fixed-header-wrapper { .el-table__header-wrapper, .el-table__fixed-header-wrapper {
th { th {
word-break: break-word; word-break: break-word;

@ -26,6 +26,8 @@ const selBaseList = () => {
router.replace({ query: {} }); router.replace({ query: {} });
} }
}) })
}else if (baseList.value.length > 0){
clickBase(baseList.value[0])
} }
}).finally(()=>{ }).finally(()=>{
loading.value = false loading.value = false

@ -1,22 +1,30 @@
<!--作物列表--> <!--作物列表-->
<script setup> <script setup>
import useSettingsStore from "@/store/modules/settings.js"; import useSettingsStore from "@/store/modules/settings.js";
import {selCropInfo} from "@/api/basicData/cropVarietyApi.js";
const cropList = ref([ const loading = ref(false)
{cropId: 1, cropName: '甘蔗'}, const cropList = ref([])
])
const route = useRoute(); const route = useRoute();
onMounted(()=>{ onMounted(()=>{
selList()
})
const selList = () => {
loading.value = true
selCropInfo({}).then(res =>{
cropList.value = res.data
if (route.query.cropId){ if (route.query.cropId){
cropList.value.forEach(item => { cropList.value.forEach(item => {
if (item.cropId === Number(route.query.cropId)){ if (item.id === Number(route.query.cropId)){
clickCrop(item) clickCrop(item)
} }
}) })
}else{ }else{
clickCrop(cropList.value[0]) clickCrop(cropList.value[0])
} }
}).finally(() => {
loading.value = false
}) })
}
const currentCrop = defineModel({type:Object,default:()=>({})}) const currentCrop = defineModel({type:Object,default:()=>({})})
const maxHeight = ref(window.innerHeight - 130); const maxHeight = ref(window.innerHeight - 130);
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
@ -30,11 +38,11 @@ const clickCrop = (item) => {
</script> </script>
<template> <template>
<div class="Crop-box" :style="{height:maxHeight+'px'}"> <div class="Crop-box" :style="{height:maxHeight+'px'}" v-loading="loading">
<title-divider title="作物列表"></title-divider> <title-divider title="作物列表"></title-divider>
<div class="Crop-list"> <div class="Crop-list">
<div class="Crop-item" :class="{'Crop-active':currentCrop.cropId === item.cropId}" v-for="item in cropList" @click="clickCrop(item)"> <div class="Crop-item" :class="{'Crop-active':currentCrop.id === item.id}" v-for="item in cropList" @click="clickCrop(item)">
<i-local-two theme="filled" size="22" :fill="currentCrop.cropId !== item.cropId ? '#6c6c6c' : theme"/> <i-sapling size="22" :fill="currentCrop.id !== item.id ? '#6c6c6c' : theme"/>
<div class="Crop-name" >{{item.cropName}}</div> <div class="Crop-name" >{{item.cropName}}</div>
</div> </div>
</div> </div>

@ -0,0 +1,288 @@
<script setup>
import {ElNotification} from "element-plus";
import Queue from 'promise-queue-plus';
import axios from 'axios'
import {ref} from 'vue'
import md5 from "@/utils/md5.js";
import {initTask, merge, preSignUrl, taskInfo} from "@/api/upload.js";
import useSettingsStore from "@/store/modules/settings.js";
const modelValue = defineModel({type:Array})
const emit = defineEmits(['beforeUpload','uploadSuccess'])
const props = defineProps({
params: {
type: Object,
default: () => {
}
}
})
//
const initChunkSize = 5 * 1024 * 1024
// keyfileUid value queue object
const fileUploadChunkQueue = ref({}).value
const mergeParams = ref({})
/**
* 获取一个上传任务没有则初始化一个
*/
const getTaskInfo = async (file) => {
let task;
const identifier = await md5(file)
const {code, data, msg} = await taskInfo(identifier)
if (code === 200) {
task = data
if (!task) {
const initTaskData = {
...props.params,
identifier,
fileName: file.name,
totalSize: file.size,
chunkSize: initChunkSize,
}
const {code, data, msg} = await initTask(initTaskData)
if (code === 200) {
task = data
} else {
ElNotification.error({
title: '文件上传错误',
message: msg
})
}
}
} else {
ElNotification.error({
title: '文件上传错误',
message: msg
})
}
return task
}
/**
* 上传逻辑处理如果文件已经上传完成完成分块合并操作则不会进入到此方法中
*/
const handleUpload = (file, taskRecord, options) => {
let lastUploadedSize = 0; //
let uploadedSize = 0 //
const totalSize = file.size || 0 //
let startMs = new Date().getTime(); //
const {exitPartList, chunkSize, chunkNum, fileIdentifier} = taskRecord
// byte/s
const getSpeed = () => {
// - = byte
const intervalSize = uploadedSize - lastUploadedSize
const nowMs = new Date().getTime()
// s
const intervalTime = (nowMs - startMs) / 1000
return intervalSize / intervalTime
}
const uploadNext = async (partNumber) => {
const start = Number(chunkSize) * (partNumber - 1)
const end = start + Number(chunkSize)
const blob = file.slice(start, end)
const {code, data, msg} = await preSignUrl({identifier: fileIdentifier, partNumber: partNumber})
if (code === 200 && msg) {
await axios.request({
url: msg,
method: 'PUT',
data: blob,
headers: {'Content-Type': 'application/octet-stream'}
})
return Promise.resolve({partNumber: partNumber, uploadedSize: blob.size})
}
return Promise.reject(`分片${partNumber} 获取上传地址失败`)
}
/**
* 更新上传进度
* @param increment 为已上传的进度增加的字节量
*/
const updateProcess = (increment) => {
increment = Number(increment)
const {onProgress} = options
let factor = 1000; // 1000 byte
let from = 0;
//
while (from <= increment) {
from += factor
uploadedSize += factor
const newPercent = Math.round(uploadedSize / totalSize * 100).toFixed(2);
// onProgress({percent: percent})
options.percent= newPercent
}
const speed = getSpeed();
const remainingTime = speed != 0 ? Math.ceil((totalSize - uploadedSize) / speed) + 's' : '未知'
console.log('剩余大小:', (totalSize - uploadedSize) / 1024 / 1024, 'mb');
console.log('当前速度:', (speed / 1024 / 1024).toFixed(2), 'mbps');
console.log('预计完成:', remainingTime);
}
return new Promise(resolve => {
const failArr = [];
const queue = Queue(5, {
"retry": 3, //Number of retries
"retryIsJump": false, //retry now?
"workReject": function (reason, queue) {
failArr.push(reason)
},
"queueEnd": function (queue) {
resolve(failArr);
}
})
fileUploadChunkQueue[file.uid] = queue
for (let partNumber = 1; partNumber <= chunkNum; partNumber++) {
const exitPart = (exitPartList || []).find(exitPart => exitPart.partNumber == partNumber)
if (exitPart) {
// ,
lastUploadedSize += Number(exitPart.size)
updateProcess(exitPart.size)
} else {
queue.push(() => uploadNext(partNumber).then(res => {
//
updateProcess(res.uploadedSize)
}))
}
}
if (queue.getLength() == 0) {
// return
resolve(failArr);
return;
}
queue.start()
})
}
/**
* el-upload 自定义上传方法入口
*/
const handleHttpRequest = async (options) => {
const file = options.file
options.indeterminate = true
const task = await getTaskInfo(file)
options.indeterminate = false
if (task) {
const {finished, path, taskRecord} = task
const {fileIdentifier: identifier} = taskRecord
if (finished) {
options.percent = 100
return path
} else {
const errorList = await handleUpload(file, taskRecord, options)
if (errorList.length > 0) {
ElNotification.error({
title: '文件上传错误',
message: '部分分片上次失败,请尝试重新上传文件'
})
return;
}
let params = {
fileName:file.name,
fileSize:file.size,
fileMemo:options.fileMemo,
...mergeParams.value
}
const {code, data, msg} = await merge(identifier,params)
if (code === 200) {
return path;
} else {
ElNotification.error({
title: '文件上传错误',
message: msg
})
}
}
} else {
ElNotification.error({
title: '文件上传错误',
message: '获取上传任务失败'
})
}
return false
}
/**
* 移除文件列表中的文件
* 如果文件存在上传队列任务对象则停止该队列的任务
*/
const handleRemoveFile = (uploadFile, uploadFiles) => {
if (!uploadFile.id){
const queueObject = fileUploadChunkQueue[uploadFile.file.uid]
if (queueObject) {
queueObject.stop()
fileUploadChunkQueue[uploadFile.file.uid] = undefined
}
modelValue.value = modelValue.value.filter(item => item.file.uid!==uploadFile.file.uid)
}else{
modelValue.value = modelValue.value.filter(item => item.id!==uploadFile.id)
}
}
const settingsStore = useSettingsStore()
const theme = computed(() => settingsStore.theme);
const startUpload = async (params) => {
mergeParams.value = params || {}
let newList = modelValue.value.filter(item => !item.id)
for (const item of newList) {
item.startUpload = true
await handleHttpRequest(item)
}
emit('uploadSuccess')
}
const beforeUpload = (options) => {
emit('beforeUpload',options)
// modelValue.value.push({
// fileSize:options.file.size,
// fileName:options.file.name,
// fileMemo:'',
// ...options
// })
return false
}
defineExpose({
startUpload,
handleRemoveFile
})
</script>
<template>
<el-upload
class="avatar-uploader"
drag
action="/"
multiple
:show-file-list="false"
:http-request="beforeUpload"
:on-remove="handleRemoveFile">
<svg-icon class-name="upload-icon" icon-class="upload" :color="theme"/>
<div class="el-upload__text">
<em>点击上传文件</em>
<div style="color: rgba(0,0,0,0.25);">或将文件拖动至此上传</div>
</div>
</el-upload>
</template>
<style lang="scss" scoped>
.avatar-uploader :deep(.el-upload) {
background: #f6f6f6;
border: 1px dashed var(--el-border-color);
border-radius: 10px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
//.avatar-uploader :deep(.el-upload:hover) {
// border-color: var(--el-color-primary);
//}
:deep(.el-upload-dragger) {
background: #f6f6f6;
border-radius: 10px;
}
.upload-icon{
height: 48px;
width: 48px;
}
</style>

@ -43,6 +43,7 @@ import baseSelection from "@/components/baseSelection"
import cropSelection from "@/components/cropSelection" import cropSelection from "@/components/cropSelection"
// 文件上传组件 // 文件上传组件
import FileUpload from "@/components/FileUpload" import FileUpload from "@/components/FileUpload"
import FileUploader from "@/components/FileUploader"
// 图片上传组件 // 图片上传组件
import ImageUpload from "@/components/ImageUpload" import ImageUpload from "@/components/ImageUpload"
// 图片预览组件 // 图片预览组件
@ -66,9 +67,12 @@ initAMapApiLoader({
//} // 如果需要使用loca组件库需要加载Loca //} // 如果需要使用loca组件库需要加载Loca
}) })
install(app, 'i'); // use custom prefix 'i', eg: icon is People, name is i-people. install(app, 'i'); // use custom prefix 'i', eg: icon is People, name is i-people.
// 在main.js或其他初始化文件中
import mitt from 'mitt';
const bus = mitt();
// 全局方法挂载 // 全局方法挂载
app.config.globalProperties.useDict = useDict app.config.globalProperties.useDict = useDict
app.config.globalProperties.$bus = bus
app.config.globalProperties.download = download app.config.globalProperties.download = download
app.config.globalProperties.parseTime = parseTime app.config.globalProperties.parseTime = parseTime
app.config.globalProperties.resetForm = resetForm app.config.globalProperties.resetForm = resetForm
@ -76,9 +80,9 @@ app.config.globalProperties.handleTree = handleTree
app.config.globalProperties.addDateRange = addDateRange app.config.globalProperties.addDateRange = addDateRange
app.config.globalProperties.selectDictLabel = selectDictLabel app.config.globalProperties.selectDictLabel = selectDictLabel
app.config.globalProperties.selectDictLabels = selectDictLabels app.config.globalProperties.selectDictLabels = selectDictLabels
// 全局组件挂载 // 全局组件挂载
app.component('DictTag', DictTag) app.component('DictTag', DictTag)
app.component('fileUploader', FileUploader)
app.component('Pagination', Pagination) app.component('Pagination', Pagination)
app.component('TreeSelect', TreeSelect) app.component('TreeSelect', TreeSelect)
app.component('FileUpload', FileUpload) app.component('FileUpload', FileUpload)

@ -220,7 +220,7 @@ export const constantRoutes = [
path: 'genoManage', path: 'genoManage',
component: () => import('@/views/genoTypeData/genoManage/index.vue'), component: () => import('@/views/genoTypeData/genoManage/index.vue'),
name: 'genoManage', name: 'genoManage',
meta: { title: '作物品种管理' } meta: { title: '基因型数据管理' }
} }
] ]
}, },

@ -0,0 +1,3 @@
import mitt from 'mitt'
const EventBus = new mitt()
export default EventBus

@ -0,0 +1,41 @@
import SparkMD5 from 'spark-md5'
const DEFAULT_SIZE = 20 * 1024 * 1024
const md5 = (file, chunkSize = DEFAULT_SIZE) => {
return new Promise((resolve, reject) => {
const startMs = new Date().getTime();
let blobSlice =
File.prototype.slice ||
File.prototype.mozSlice ||
File.prototype.webkitSlice;
let chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
let spark = new SparkMD5.ArrayBuffer(); //追加数组缓冲区。
let fileReader = new FileReader(); //读取文件
fileReader.onload = function (e) {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
const md5 = spark.end(); //完成md5的计算返回十六进制结果。
console.log('文件md5计算结束总耗时', (new Date().getTime() - startMs) / 1000, 's')
console.log( 'md5',md5)
resolve(md5);
}
};
fileReader.onerror = function (e) {
reject(e);
};
function loadNext() {
console.log('当前part number', currentChunk+1, '总块数:', chunks);
let start = currentChunk * chunkSize;
let end = start + chunkSize;
(end > file.size) && (end = file.size);
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}
loadNext();
});
}
export default md5

@ -36,7 +36,9 @@ service.interceptors.request.use(config => {
config.params = {}; config.params = {};
config.url = url; config.url = url;
} }
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { if (config.upload) {
config.headers['Content-Type'] = 'multipart/form-data'
} else if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = { const requestObj = {
url: config.url, url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
@ -84,7 +86,11 @@ service.interceptors.response.use(res => {
if (code === 401) { if (code === 401) {
if (!isRelogin.show) { if (!isRelogin.show) {
isRelogin.show = true; isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
isRelogin.show = false; isRelogin.show = false;
useUserStore().logOut().then(() => { useUserStore().logOut().then(() => {
location.href = '/index'; location.href = '/index';
@ -126,7 +132,9 @@ service.interceptors.response.use(res => {
export function download(url, params, filename, config) { export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)",}) downloadLoadingInstance = ElLoading.service({text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)",})
return service.post(url, params, { return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }], transformRequest: [(params) => {
return tansParams(params)
}],
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
responseType: 'blob', responseType: 'blob',
...config ...config

@ -403,3 +403,14 @@ export function fileToBlobURL(file) {
} }
}) })
} }
// 千分显示并保留两位小数
export function processDot(num, precision) {
return (+(Math.round(+(num + "e" + precision)) + "e" + -precision)).toFixed(
precision,
);
}
export function formatNumber(num) {
const result = processDot(num, 2);
return result.replace(/\d(?=(\d{3})+\.)/g, "$&,");
}

@ -41,7 +41,7 @@ const toUpdate = (row) => {
geyById(row) geyById(row)
} }
const geyById = (row) => { const geyById = (row) => {
formRef.value.clearValidate() formRef.value?.clearValidate()
showDrawer.value = true showDrawer.value = true
selLoading.value = true selLoading.value = true
breedingQueryById({id:row.id}).then(res =>{ breedingQueryById({id:row.id}).then(res =>{
@ -125,7 +125,7 @@ const changeArea = (value) =>{
<el-col :span="24"> <el-col :span="24">
<el-form-item label="基地/资源圃名称" prop="baseName"> <el-form-item label="基地/资源圃名称" prop="baseName">
<el-input placeholder="请输入基地/资源圃名称" v-if="formStatus!==3" v-model="formData.baseName"></el-input> <el-input placeholder="请输入基地/资源圃名称" v-if="formStatus!==3" v-model="formData.baseName"></el-input>
<div v-else>{{formData.baseName}}</div> <div v-else>{{formData.baseName || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
@ -133,7 +133,7 @@ const changeArea = (value) =>{
<el-select v-model="formData.baseType" filterable clearable v-if="formStatus!==3"> <el-select v-model="formData.baseType" filterable clearable v-if="formStatus!==3">
<el-option v-for="item in selectOptions.baseTypeOptions" :label="item.label" :value="item.value"></el-option> <el-option v-for="item in selectOptions.baseTypeOptions" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
<div v-else>{{formData.baseType}}</div> <div v-else>{{formData.baseType || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
@ -141,38 +141,38 @@ const changeArea = (value) =>{
<el-select v-model="formData.administrativeDivisionId" filterable clearable v-if="formStatus!==3" @change="changeArea"> <el-select v-model="formData.administrativeDivisionId" filterable clearable v-if="formStatus!==3" @change="changeArea">
<el-option v-for="item in selectOptions.provincesOptions" :label="item.label" :value="item.value"></el-option> <el-option v-for="item in selectOptions.provincesOptions" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
<div v-else>{{formData.administrativeDivisionName}}</div> <div v-else>{{formData.administrativeDivisionName || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="地理位置" prop="position"> <el-form-item label="地理位置" prop="position">
<el-input placeholder="请输入地理位置" v-if="formStatus!==3" v-model="formData.position"></el-input> <el-input placeholder="请输入地理位置" v-if="formStatus!==3" v-model="formData.position"></el-input>
<div v-else>{{formData.position}}</div> <div v-else>{{formData.position || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="占地面积(亩)" prop="area"> <el-form-item label="占地面积(亩)" prop="area">
<el-input-number precision="2" :controls="false" placeholder="请输入占地面积(亩)" v-if="formStatus!==3" v-model="formData.area" controls-position="right"></el-input-number> <el-input-number precision="2" :controls="false" placeholder="请输入占地面积(亩)" v-if="formStatus!==3" v-model="formData.area" controls-position="right"></el-input-number>
<div v-else>{{formData.area}}</div> <div v-else>{{formData.area || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="基地坐标(经度)" prop="longitude"> <el-form-item label="基地坐标(经度)" prop="longitude">
<el-input-number placeholder="请输入基地坐标(经度)" v-if="formStatus!==3" v-model="formData.longitude" controls-position="right"></el-input-number> <el-input-number placeholder="请输入基地坐标(经度)" v-if="formStatus!==3" v-model="formData.longitude" controls-position="right"></el-input-number>
<div v-else>{{formData.longitude}}</div> <div v-else>{{formData.longitude || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="基地坐标(纬度)" prop="latitude"> <el-form-item label="基地坐标(纬度)" prop="latitude">
<el-input-number placeholder="请输入基地坐标(纬度)" v-if="formStatus!==3" v-model="formData.latitude" controls-position="right"></el-input-number> <el-input-number placeholder="请输入基地坐标(纬度)" v-if="formStatus!==3" v-model="formData.latitude" controls-position="right"></el-input-number>
<div v-else>{{formData.latitude}}</div> <div v-else>{{formData.latitude || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="备注" prop="intro"> <el-form-item label="备注" prop="intro">
<el-input type="textarea" rows="5" placeholder="请输入备注" v-if="formStatus!==3" v-model="formData.intro"></el-input> <el-input type="textarea" rows="5" placeholder="请输入备注" v-if="formStatus!==3" v-model="formData.intro"></el-input>
<div v-else>{{formData.intro}}</div> <div v-else>{{formData.intro || '--'}}</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>

@ -155,7 +155,7 @@ const data = reactive({
const delBreedingBase = (item) => { const delBreedingBase = (item) => {
ElMessageBox.confirm( ElMessageBox.confirm(
'基地删除后,将无法被找回,对应的业务数据也将无法找回,是否仍要删除基地?', '基地删除后,将无法被找回,对应的业务数据也将无法找回,是否仍要删除基地?',
'删除', '删除地',
{ {
confirmButtonText: '确认删除', confirmButtonText: '确认删除',
cancelButtonText: '不,我再想想', cancelButtonText: '不,我再想想',

@ -4,19 +4,19 @@
<el-row :gutter="20"> <el-row :gutter="20">
<!--育种基地/资源圃选择--> <!--育种基地/资源圃选择-->
<el-col :span="4" :xs="24"> <el-col :span="4" :xs="24">
<crop-selection v-model="currentCrop"></crop-selection> <crop-selection v-model="currentCrop" @click-crop="getList"></crop-selection>
</el-col> </el-col>
<!--用户数据--> <!--用户数据-->
<el-col :span="20" :xs="24"> <el-col :span="20" :xs="24">
<el-row class="crop-title"> <el-row>
<el-col :span="6" class="current-crop-name">当前作物{{currentCrop.cropName || '请选择作物'}}</el-col> <el-col :span="6" class="current-title">当前作物{{currentCrop.cropName || '请选择作物'}}</el-col>
</el-row> </el-row>
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="6"> <el-col :span="6">
<el-form-item label="品种名称"> <el-form-item label="品种名称">
<el-input <el-input
v-model="queryParams.userName" v-model="queryParams.breedName"
placeholder="请输入品种名称" placeholder="请输入品种名称"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
@ -32,18 +32,17 @@
</el-row> </el-row>
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-button type="primary" icon="Download" @click="exportExcel">Excel</el-button> <el-button type="primary" @click="exportExcel">Excel</el-button>
<el-button type="primary" icon="Plus" @click="toAdd" :disabled="!currentCrop.cropId">新增品种</el-button> <el-button type="primary" @click="toAdd" :disabled="!currentCrop.id" plain>新增品种</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row> </el-row>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" /> <el-table-column label="品种名称" align="center" key="breedName" prop="breedName" v-if="columns[0].visible" />
<el-table-column label="品种名称" align="center" key="userId" prop="userId" v-if="columns[0].visible" /> <el-table-column label="所属作物" align="center" key="cropIdName" prop="cropIdName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
<el-table-column label="所属作物" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" /> <el-table-column label="父本品种" align="center" key="fartherBreadName" prop="fartherBreadName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
<el-table-column label="副本品种" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" /> <el-table-column label="母本品种" align="center" key="motherBreadName" prop="motherBreadName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
<el-table-column label="母本品种" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" /> <el-table-column label="创建人" align="center" prop="createUserName" v-if="columns[4].visible" width="160"></el-table-column>
<el-table-column label="创建人" align="center" prop="createTime" v-if="columns[4].visible" width="160"></el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[5].visible" width="160"> <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[5].visible" width="160">
<template #default="scope"> <template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
@ -51,15 +50,9 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="详情" placement="top"> <el-link type="info" @click="handleView(scope.row)"></el-link>
<el-button link type="primary" icon="View" @click="handleUpdate(scope.row)"></el-button> <el-link type="primary" @click="handleUpdate(scope.row)"></el-link>
</el-tooltip> <el-link type="danger" @click="delCrop(scope.row)"></el-link>
<el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1">
<el-button link type="primary" icon="Delete" @click="delMassif(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
</el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
<template #empty> <template #empty>
@ -86,83 +79,147 @@
<div v-if="formStatus === 2"></div> <div v-if="formStatus === 2"></div>
<div v-if="formStatus === 3"></div> <div v-if="formStatus === 3"></div>
</div> </div>
<div class="form" style="margin-top: 30px;padding: 0 30px"> <div class="form" style="margin-top: 30px;padding: 0 30px" v-loading="selLoading">
<el-form :model="formData" :rules="rules" ref="formRef"> <el-form :model="formData" :rules="rules" ref="formRef">
<el-row :gutter="20"> <el-row :gutter="20">
<el-form-item label="品种名称" prop="baseName"> <el-form-item label="品种名称" prop="breedName">
<el-input placeholder="请输入品种名称" v-if="formStatus!==3" v-model="formData.baseName"></el-input> <el-input placeholder="请输入品种名称" v-if="formStatus!==3" v-model="formData.breedName"></el-input>
<div v-else>{{formData.baseName}}</div> <div v-else>{{formData.breedName || '--'}}</div>
</el-form-item> </el-form-item>
<el-form-item label="所属作物" prop="baseName"> <el-form-item label="所属作物" prop="cropId">
<el-input placeholder="请输入所属作物" v-if="formStatus!==3" v-model="formData.baseName"></el-input> <el-select v-model="formData.cropId" v-if="formStatus!==3">
<div v-else>{{formData.baseName}}</div> <el-option v-for="item in options.cropType" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
<div v-else>{{formData.cropIdName || '--'}}</div>
</el-form-item> </el-form-item>
<el-form-item label="父本品种" prop="baseName"> <el-form-item label="父本品种" prop="fartherBreadName">
<el-input placeholder="请输入父本品种" v-if="formStatus!==3" v-model="formData.baseName"></el-input> <el-input placeholder="请输入父本品种" v-if="formStatus!==3" v-model="formData.fartherBreadName"></el-input>
<div v-else>{{formData.baseName}}</div> <div v-else>{{formData.fartherBreadName || '--'}}</div>
</el-form-item> </el-form-item>
<el-form-item label="母本品种" prop="baseName"> <el-form-item label="母本品种" prop="motherBreadName">
<el-input placeholder="请输入母本品种" v-if="formStatus!==3" v-model="formData.baseName"></el-input> <el-input placeholder="请输入母本品种" v-if="formStatus!==3" v-model="formData.motherBreadName"></el-input>
<div v-else>{{formData.baseName}}</div> <div v-else>{{formData.motherBreadName || '--'}}</div>
</el-form-item> </el-form-item>
</el-row> </el-row>
</el-form> </el-form>
</div> </div>
<template #footer> <template #footer>
<el-button v-if="formStatus!==3" type="primary" :loading="loading" @click="saveMassif"></el-button> <el-button v-if="formStatus!==3" type="primary" :loading="loading" @click="saveCrop"></el-button>
<el-button @click="showDrawer = false">取消</el-button> <el-button @click="showForm = false">取消</el-button>
</template> </template>
</left-drawer> </left-drawer>
</div> </div>
</template> </template>
<script setup name="User"> <script setup name="User">
import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser, deptTreeSelect } from "@/api/system/user";
import {ElMessage, ElMessageBox} from "element-plus"; import {ElMessage, ElMessageBox} from "element-plus";
const massifFormRef = ref(null) import dayjs from "dayjs";
const router = useRouter(); import {
addCropVariety,
delCropVariety,
queryCropVarietyPage, selCropInfo,
selCropVarietyById, updateCropVariety
} from "@/api/basicData/cropVarietyApi.js";
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const currentCrop = ref({}) const currentCrop = ref({})
const formData = ref({}) const formData = ref({})
const userList = ref([]); const userList = ref([]);
const open = ref(false);
const loading = ref(false); const loading = ref(false);
const selLoading = ref(false);
const showForm = ref(false); const showForm = ref(false);
// 1 2 3
const formStatus = ref(1); const formStatus = ref(1);
const showSearch = ref(true); const showSearch = ref(true);
const ids = ref([]); const ids = ref([]);
const formRef = ref(null);
const single = ref(true); const single = ref(true);
const multiple = ref(true); const multiple = ref(true);
const total = ref(0); const total = ref(0);
const title = ref("");
const dateRange = ref([]); const dateRange = ref([]);
const options = reactive({
cropType:[],
})
onMounted(()=>{
getOptions()
})
const getOptions = () => {
selCropInfo({}).then(res=>{
options.cropType = res.data.map(item=>{
return {
value:item.id,
label:item.cropName
}
})
})
}
// //
const columns = ref([ const columns = ref([
{ key: 0, label: `品种名称`, visible: true }, { key: 0, label: `品种名称`, visible: true },
{ key: 1, label: `所属作物`, visible: true }, { key: 1, label: `所属作物`, visible: true },
{ key: 2, label: `父本品种`, visible: true }, { key: 2, label: `父本品种`, visible: true },
{ key: 3, label: `母本品种`, visible: true }, { key: 3, label: `母本品种`, visible: true },
{ key: 7, label: `创建人`, visible: true }, { key: 4, label: `创建人`, visible: true },
{ key: 7, label: `创建人`, visible: true }, { key: 5, label: `创建时间`, visible: true },
{ key: 8, label: `创建时间`, visible: true },
]); ]);
// //
const toAdd = () => { const toAdd = () => {
formData.value = {} formStatus.value = 1
formData.value = {
cropId:1
}
showForm.value = true showForm.value = true
} }
// const saveCrop = () => {
const delMassif = (item) => { formRef.value.validate(valid => {
if (valid){
loading.value = true
let api = formData.value.id ? updateCropVariety : addCropVariety
api(formData.value).then(res =>{
proxy.$modal.msgSuccess(formData.value.id ?'修改成功':"保存成功");
getList()
showForm.value = false
}).finally(()=>{
loading.value = false
})
}else{
proxy.$modal.msgError('保存失败,请确认所有信息均已填写');
}
})
}
const openFormModel = (value = {}) => {
showForm.value = true
selLoading.value = true
formRef.value?.clearValidate()
selCropVarietyById({id:value.id}).then(res =>{
formData.value = res
}).finally(()=>{
selLoading.value = false
})
}
const handleView = (row) => {
formStatus.value = 3
openFormModel(row)
}
const handleUpdate = (row) => {
formStatus.value = 2
openFormModel(row)
}
//
const delCrop = (item) => {
ElMessageBox.confirm( ElMessageBox.confirm(
'品种删除后,将无法被找回,是否确认删除当前品种?', '品种删除后,将无法被找回,是否确认删除当前品种?',
'删除地块', '删除品种',
{ {
confirmButtonText: '确认删除', confirmButtonText: '确认删除',
cancelButtonText: '不,我再想想', cancelButtonText: '不,我再想想',
customClass: 'message-box' customClass: 'message-box'
} }
).then(() => { ).then(() => {
ElMessage.success(`${item.label || '地块'}删除成功`) delCropVariety({id:item.id}).then(res =>{
ElMessage.success(`${item.breedName}删除成功`)
getList()
})
}) })
} }
const data = reactive({ const data = reactive({
@ -170,25 +227,24 @@ const data = reactive({
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
userName: undefined, breedName: undefined,
plantingSeason: 2023, cropId: undefined
status: undefined,
deptId: undefined
}, },
}); rules:{
// breedName: [{ required: true, message: "品种名不能为空", trigger: "blur" }],
const submitSuccess = (value) =>{ cropId: [{ required: true, message: "请选择所属作物", trigger: "blur" }],
} }
});
const { queryParams, form, rules ,} = toRefs(data); const { queryParams, form, rules ,} = toRefs(data);
/** 查询用户列表 */ /** 查询用户列表 */
function getList() { function getList() {
loading.value = true; loading.value = true;
listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => { queryCropVarietyPage({...queryParams.value,cropId:currentCrop.value.id}).then(res => {
loading.value = false;
userList.value = res.rows; userList.value = res.rows;
total.value = res.total; total.value = res.total;
}).finally(()=>{
loading.value = false;
}); });
}; };
/** 搜索按钮操作 */ /** 搜索按钮操作 */
@ -199,47 +255,23 @@ function handleQuery() {
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
dateRange.value = []; dateRange.value = [];
proxy.resetForm("queryRef"); queryParams.value = {}
queryParams.value.deptId = undefined;
proxy.$refs.deptTreeRef.setCurrentKey(null);
handleQuery(); handleQuery();
}; };
/** 删除按钮操作 */
function handleDelete(row) {
const userIds = row.userId || ids.value;
proxy.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function () {
return delUser(userIds);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
};
/** 选择条数 */ /** 选择条数 */
function handleSelectionChange(selection) { function handleSelectionChange(selection) {
ids.value = selection.map(item => item.userId); ids.value = selection.map(item => item.userId);
single.value = selection.length != 1; single.value = selection.length != 1;
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
/** 修改按钮操作 */ const exportExcel = () => {
function handleUpdate(row) { proxy.download("/breed_info/export", {
reset(); ...queryParams.value,
const userId = row.userId || ids.value; cropId:currentCrop.value.id
getUser(userId).then(response => { }, `作物品种列表-${dayjs().format('YYYY年MM月DD日')}.xlsx`);
form.value = response.data; }
open.value = true;
title.value = "修改用户";
form.password = "";
});
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.crop-title{
margin: 10px 0;
.handoff-model{
padding-right: 20px;
text-align: right;
}
}
.el-form-item{ .el-form-item{
width: 100%; width: 100%;
} }

@ -6,38 +6,38 @@
<el-col :span="24" :xs="24"> <el-col :span="24" :xs="24">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="6"> <el-col :span="5">
<el-form-item label="设备编码"> <el-form-item label="设备编码">
<el-input <el-input
v-model="queryParams.userName" v-model="queryParams.deviceNum"
placeholder="请输入设备编码" placeholder="请输入设备编码"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="5">
<el-form-item label="设备类型"> <el-form-item label="设备类型">
<el-input <el-input
v-model="queryParams.userName" v-model="queryParams.deviceType"
placeholder="请输入设备类型" placeholder="请输入设备类型"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="5">
<el-form-item label="所属基地"> <el-form-item label="所属基地/资源圃">
<el-select v-model="queryParams.areaCode" filterable clearable> <el-select v-model="queryParams.baseNurseryId" filterable clearable>
<el-option v-for="item in data.pcTextArr" :label="item.label" :value="item.value"></el-option> <el-option v-for="item in options.baseList" :key="item.id" :label="item.baseName" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="5">
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="状态" clearable style="width: 200px"> <el-select v-model="queryParams.status" placeholder="状态" clearable style="width: 200px">
<el-option <el-option
v-for="dict in sys_normal_disable" v-for="dict in options.status"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
@ -45,7 +45,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="4">
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button> <el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button> <el-button icon="Refresh" @click="resetQuery"></el-button>
@ -59,20 +59,23 @@
</el-row> </el-row>
<el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center"/> <el-table-column label="设备ID" align="center" key="deviceId" prop="deviceId" v-if="columns[0].visible"/>
<el-table-column label="设备ID" align="center" key="userId" prop="userId" v-if="columns[0].visible"/> <el-table-column label="设备编码" align="center" key="deviceNum" prop="deviceNum" v-if="columns[1].visible"
<el-table-column label="设备编码" align="center" key="userName" prop="userName" v-if="columns[1].visible"
:show-overflow-tooltip="true"/> :show-overflow-tooltip="true"/>
<el-table-column label="设备类型" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" <el-table-column label="设备类型" align="center" key="deviceType" prop="deviceType" v-if="columns[2].visible"
:show-overflow-tooltip="true"/> :show-overflow-tooltip="true"/>
<el-table-column label="设备型号" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" <el-table-column label="设备型号" align="center" key="deviceModel" prop="deviceModel" v-if="columns[3].visible"
:show-overflow-tooltip="true"/> :show-overflow-tooltip="true"/>
<el-table-column label="设备厂商" align="center" key="phonenumber" prop="phonenumber" <el-table-column label="设备厂商" align="center" key="manufacturerName" prop="manufacturerName"
v-if="columns[4].visible" width="120"/> v-if="columns[4].visible" width="120"/>
<el-table-column label="所属基地/资源圃" align="center" key="phonenumber" prop="phonenumber" <el-table-column label="所属基地/资源圃" align="center" key="baseNurseryName" prop="baseNurseryName"
v-if="columns[5].visible" width="120"/> v-if="columns[5].visible" width="120"/>
<el-table-column label="当前状态" align="center" key="phonenumber" prop="phonenumber" <el-table-column label="当前状态" align="center" key="status" prop="status"
v-if="columns[6].visible" width="120"/> v-if="columns[6].visible" width="120">
<template #default="scope">
<el-switch :model-value="scope.row.status" :active-value="0" :inactive-value="1"/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="toView(scope.row)"></el-button> <el-button link type="primary" @click="toView(scope.row)"></el-button>
@ -100,32 +103,32 @@
<left-drawer v-model="showView" title="设备详情"> <left-drawer v-model="showView" title="设备详情">
<div> <div>
<el-form :model="formData" ref="formRef"> <el-form :model="formData" ref="formRef">
<el-form-item label="设备ID" prop="baseName"> <el-form-item label="设备ID" prop="deviceId">
<div>{{ formData.baseName }}</div> <div>{{ formData.deviceId || '--' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="设备编码:" prop="baseName"> <el-form-item label="设备编码:" prop="deviceNum">
<div>{{ formData.baseName }}</div> <div>{{ formData.deviceNum || '--' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="设备类型:" prop="baseName"> <el-form-item label="设备类型:" prop="deviceType">
<div>{{ formData.baseName }}</div> <div>{{ formData.deviceType || '--' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="设备型号:" prop="baseName"> <el-form-item label="设备型号:" prop="deviceModel">
<div>{{ formData.baseName }}</div> <div>{{ formData.deviceModel || '--' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="设备厂商:" prop="baseName"> <el-form-item label="设备厂商:" prop="manufacturerName">
<div>{{ formData.baseName }}</div> <div>{{ formData.manufacturerName || '--' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="设备主体:" prop="baseName"> <el-form-item label="设备主体:" prop="mainBodyName">
<div>{{ formData.baseName }}</div> <div>{{ formData.mainBodyName || '--' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="所属项目:" prop="baseName"> <el-form-item label="所属项目:" prop="projectName">
<div>{{ formData.baseName }}</div> <div>{{ formData.projectName || '--' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="当前状态:" prop="baseName"> <el-form-item label="当前状态:" prop="statusName">
<div>{{ formData.baseName }}</div> <div>{{ formData.statusName || '停用' }}</div>
</el-form-item> </el-form-item>
<el-form-item label="所属基地/资源圃:" prop="baseName"> <el-form-item label="所属基地/资源圃:" prop="baseNurseryName">
<div>{{ formData.baseName }}</div> <div>{{ formData.baseNurseryName || '--' }}</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@ -135,30 +138,30 @@
</left-drawer> </left-drawer>
<left-drawer v-model="showUpdateBase" title="配置设备所属基地"> <left-drawer v-model="showUpdateBase" title="配置设备所属基地">
<div> <div>
<el-form :model="formData" ref="formRef" label-position="top"> <el-form :rules="rules" :model="formData" ref="formRef" label-position="top">
<div class="belongs-base-info"> <div class="belongs-base-info">
<div class="belongs-base-info-item"> <div class="belongs-base-info-item">
<div class="label">设备ID</div> <div class="label">设备ID</div>
<div>{{ formData.baseName }}111</div> <div>{{ formData.deviceId || '--' }}</div>
</div> </div>
<div class="belongs-base-info-item"> <div class="belongs-base-info-item">
<div class="label">设备编码</div> <div class="label">设备编码</div>
<div>{{ formData.baseName }}222</div> <div>{{ formData.deviceNum || '--' }}</div>
</div> </div>
<div class="belongs-base-info-item"> <div class="belongs-base-info-item">
<div class="label">设备型号</div> <div class="label">设备型号</div>
<div>{{ formData.baseName }}333</div> <div>{{ formData.deviceModel || '--' }}</div>
</div> </div>
</div> </div>
<el-form-item label="所属基地/资源圃" prop="baseName"> <el-form-item label="所属基地/资源圃" prop="baseNurseryName">
<el-select v-model="queryParams.areaCode" filterable clearable> <el-select v-model="formData.baseNurseryId" filterable clearable>
<el-option v-for="item in data.pcTextArr" :label="item.label" :value="item.value"></el-option> <el-option v-for="item in options.baseList" :label="item.baseName" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<template #footer> <template #footer>
<el-button type="primary" :loading="loading" @click="saveMassif"></el-button> <el-button type="primary" :loading="loading" @click="submitUpdate"></el-button>
<el-button @click="showUpdateBase = false">取消</el-button> <el-button @click="showUpdateBase = false">取消</el-button>
</template> </template>
</left-drawer> </left-drawer>
@ -176,10 +179,12 @@ import {
addUser, addUser,
deptTreeSelect deptTreeSelect
} from "@/api/system/user"; } from "@/api/system/user";
import {selAllBreedingBase} from "@/api/basicData/breedingBaseApi.js";
import dayjs from "dayjs";
import {selDataGather, updateDataGather} from "@/api/basicData/dataGatherApi.js";
const router = useRouter(); const router = useRouter();
const {proxy} = getCurrentInstance(); const {proxy} = getCurrentInstance();
const {sys_normal_disable} = proxy.useDict("sys_normal_disable");
const tableList = ref([]); const tableList = ref([]);
const open = ref(false); const open = ref(false);
const loading = ref(false); const loading = ref(false);
@ -188,11 +193,17 @@ const ids = ref([]);
const single = ref(true); const single = ref(true);
const multiple = ref(true); const multiple = ref(true);
const total = ref(0); const total = ref(0);
const title = ref("");
// //
const showView = ref(false) const showView = ref(false)
// //
const showUpdateBase = ref(false) const showUpdateBase = ref(false)
const options = reactive({
baseList:[],
status:[
{label:'开启',value:0},
{label:'关闭',value:1},
]
})
const dateRange = ref([]); const dateRange = ref([]);
// //
const columns = ref([ const columns = ref([
@ -204,8 +215,15 @@ const columns = ref([
{key: 5, label: `所属基地/资源圃`, visible: true}, {key: 5, label: `所属基地/资源圃`, visible: true},
{key: 6, label: `当前状态`, visible: true}, {key: 6, label: `当前状态`, visible: true},
]); ]);
const selOptions = () => {
selAllBreedingBase({}).then(res => {
options.baseList = res.data
})
}
const exportExcel = () => { const exportExcel = () => {
proxy.download("/device_info/export", {
...queryParams.value,
}, `数据采集设备列表-${dayjs().format('YYYY年MM月DD日')}.xlsx`);
} }
const formData = ref({}) const formData = ref({})
const data = reactive({ const data = reactive({
@ -218,13 +236,16 @@ const data = reactive({
status: undefined, status: undefined,
deptId: undefined deptId: undefined
}, },
rules:{
baseNurseryId:[{required:true,message:'请选择基地/资源圃',trigger:'blur'}]
}
}); });
const {queryParams, form,} = toRefs(data); const {queryParams, form,rules} = toRefs(data);
/** 查询用户列表 */ /** 查询用户列表 */
function getList() { function getList() {
loading.value = true; loading.value = true;
listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => { selDataGather(queryParams.value).then(res => {
loading.value = false; loading.value = false;
tableList.value = res.rows; tableList.value = res.rows;
total.value = res.total; total.value = res.total;
@ -261,6 +282,24 @@ const handleUpdate = (row) => {
formData.value = row formData.value = row
showUpdateBase.value = true showUpdateBase.value = true
} }
const submitUpdate = () => {
let params = {
id:formData.value.id,
baseNurseryId:formData.value.baseNurseryId
}
loading.value = true
updateDataGather(params).then(res =>{
proxy.$modal.msgSuccess('修改成功')
showUpdateBase.value = false
getList()
}).finally(()=>{
loading.value = false
})
}
onMounted(()=>{
selOptions()
getList()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.base-title { .base-title {

@ -9,7 +9,7 @@
<el-col :span="6"> <el-col :span="6">
<el-form-item label="物种名称"> <el-form-item label="物种名称">
<el-input <el-input
v-model="queryParams.userName" v-model="queryParams.speciesName"
placeholder="请输入物种名称" placeholder="请输入物种名称"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
@ -17,13 +17,10 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="属类"> <el-form-item label="所属界">
<el-input <el-select v-model="queryParams.kingdomId" filterable clearable @change="selPhylum">
v-model="queryParams.userName" <el-option v-for="item in options.kingdomInfo" :label="item.label" :value="item.value"></el-option>
placeholder="请输入属类" </el-select>
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
@ -36,31 +33,42 @@
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-button type="primary" icon="Plus" @click="toAdd"></el-button> <el-button type="primary" icon="Plus" @click="toAdd"></el-button>
<el-button type="primary" icon="Download" @click="exportExcel">Excel</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row> </el-row>
<el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange" border> <el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange" border>
<el-table-column label="图片" align="center" key="userId" prop="userId" v-if="columns[0].visible"> <el-table-column label="图片" align="center" key="thumbnailUrl" prop="thumbnailUrl" v-if="columns[0].visible"
width="120">
<template #default="scope">
<el-image :src="scope.row.thumbnailUrl" style="width: 100px; height: 100px;"
:preview-src-list="[scope.row.thumbnailUrl]"></el-image>
</template>
</el-table-column>
<el-table-column label="物种" align="center" key="speciesName" prop="speciesName" v-if="columns[1].visible"
:show-overflow-tooltip="true" width="120"/>
<el-table-column label="属类" align="center" key="genus" prop="genus" v-if="columns[2].visible"
:show-overflow-tooltip="true">
<template #default="scope"> <template #default="scope">
<el-image :src="scope.row.avatar" style="width: 100px; height: 100px;" :preview-src-list="[scope.row.avatar]"></el-image> <div>
{{ `${scope.row.phylumName || '--'} / ${scope.row.className || '--'} / ${scope.row.orderName || '--'} / ${scope.row.familyName || '--'} / ${scope.row.genusName || '--'}` }}
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="物种" align="center" key="userName" prop="userName" v-if="columns[1].visible" <el-table-column label="所属界" align="center" key="kingdomName" prop="kingdomName" v-if="columns[3].visible"
:show-overflow-tooltip="true"/> :show-overflow-tooltip="true" width="90"/>
<el-table-column label="属类" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" <el-table-column label="创建人" align="center" key="createUserName" prop="createUserName"
:show-overflow-tooltip="true"/>
<el-table-column label="所属界" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible"
:show-overflow-tooltip="true"/>
<el-table-column label="创建人" align="center" key="phonenumber" prop="phonenumber"
v-if="columns[4].visible" width="120"/> v-if="columns[4].visible" width="120"/>
<el-table-column label="创建时间" align="center" key="phonenumber" prop="phonenumber" <el-table-column label="创建时间" align="center" key="createTime" prop="createTime"
v-if="columns[5].visible" width="120"/> v-if="columns[5].visible" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="toView(scope.row)"></el-button> <el-link type="info" @click="toView(scope.row)"></el-link>
<el-button link type="primary" @click="toEdit(scope.row)"></el-button> <el-link type="primary" class="operate-edit" @click="toEdit(scope.row)"></el-link>
<el-button link type="primary" @click="handleDelete(scope.row)"></el-button> <el-link type="danger" @click="handleDelete(scope.row)"></el-link>
</template> </template>
</el-table-column> </el-table-column>
<template #empty> <template #empty>
@ -81,39 +89,64 @@
/> />
</el-col> </el-col>
</el-row> </el-row>
<left-drawer v-model="showView" title="设备详情" width="35%"> <left-drawer v-model="showView" title="上传基因型数据" width="40%">
<div>
<title-divider title="基本信息"/>
<el-form :model="formData" ref="formRef" :rules="rules" label-width="90px">
<el-form-item label="物种名称" prop="speciesName">
{{ formData.speciesName }}
</el-form-item>
<el-form-item label="所属界" prop="kingdomId">
{{ formData.kingdomName }}
</el-form-item>
<el-form-item label="物种属类" prop="genus">
<div> <div>
<el-form :model="formData" ref="formRef"> {{ `${formData.phylumName || '--'} / ${formData.className || '--'} / ${formData.orderName || '--'} / ${formData.familyName || '--'} / ${formData.genusName || '--'}` }}
<el-form-item label="所属基地/资源圃:" prop="baseName"> </div>
<div>{{ formData.baseName }}</div> </el-form-item>
<el-form-item label="物种图片" prop="thumbnailUrl">
<div class="avatar-uploader-image">
<el-image :src="formData.thumbnailUrl" style="width: 130px;height: 130px"></el-image>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-tabs v-model="currentTabs">
<el-tab-pane label="Genome" :name="1">
</el-tab-pane>
<el-tab-pane label="Annotation" :name="2">
</el-tab-pane>
<el-tab-pane label="Molecular maker" :name="3">
</el-tab-pane>
<el-tab-pane label="Gene Expression Level (FPKM) Data" :name="4">
</el-tab-pane>
</el-tabs>
</div> </div>
<template #footer> <template #footer>
<el-button @click="showView = false">关闭</el-button> <el-button @click="showView = false">关闭</el-button>
</template> </template>
</left-drawer> </left-drawer>
<left-drawer v-model="showGenoForm" title="上传基因型数据" width="35%"> <left-drawer v-model="showGenoForm" title="上传基因型数据" width="40%">
<div> <div>
<title-divider title="基本信息"/> <title-divider title="基本信息"/>
<el-form :model="formData" ref="formRef"> <el-form :model="formData" ref="formRef" :rules="rules">
<el-form-item label="物种名称"> <el-form-item label="物种名称" prop="speciesName">
<el-input placeholder="请输入物种名称"></el-input> <el-input placeholder="请输入物种名称" v-model="formData.speciesName"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="所属界" prop="baseName"> <el-form-item label="所属界" prop="kingdomId">
<el-select v-model="queryParams.areaCode" filterable clearable> <el-select v-model="formData.kingdomId" filterable clearable @change="selPhylum">
<el-option v-for="item in data.pcTextArr" :label="item.label" :value="item.value"></el-option> <el-option v-for="item in options.kingdomInfo" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="物种属类" prop="baseName"> <el-form-item label="物种属类" prop="genus">
<el-select v-model="queryParams.areaCode" filterable clearable> <el-cascader ref="cascaderRef" :props="cascaderProps" v-model="formData.genus" :options="options.genusInfo"
<el-option v-for="item in data.pcTextArr" :label="item.label" :value="item.value"></el-option> style="width: 100%"/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="物种图片" prop="baseName"> <el-form-item label="物种图片" prop="thumbnailUrl">
<div v-if="formData.tempFileImage" class="avatar-uploader-image"> <div v-if="formData.thumbnailUrl" class="avatar-uploader-image">
<div class="image" @click="delete formData.tempFileImage"><i-close theme="outline" size="24" fill="#FFFFFF"/></div> <div class="image" @click="delFile">
<el-image :src="formData.tempFileImage" style="width: 130px;height: 130px"></el-image> <i-close theme="outline" size="24" fill="#FFFFFF"/>
</div>
<el-image :src="formData.thumbnailUrl" style="width: 130px;height: 130px"></el-image>
</div> </div>
<el-upload <el-upload
v-else v-else
@ -124,7 +157,7 @@
> >
<div class="avatar-uploader"> <div class="avatar-uploader">
<div class="avatar-uploader-icon"> <div class="avatar-uploader-icon">
<i-upload theme="outline" size="24" :fill="theme"/> <svg-icon class-name="upload-icon" icon-class="upload" :color="theme"/>
</div> </div>
<div :style="{color:theme}">请上传</div> <div :style="{color:theme}">请上传</div>
</div> </div>
@ -132,22 +165,15 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-tabs v-model="currentTabs"> <el-tabs v-model="currentTabs">
<el-tab-pane label="Genome" :name="1"> <el-tab-pane label="Genome" :name="1"/>
<upload-geno></upload-geno> <el-tab-pane label="Annotation" :name="2"/>
</el-tab-pane> <el-tab-pane label="Molecular maker" :name="3"/>
<el-tab-pane label="Annotation" :name="2"> <el-tab-pane label="Gene Expression Level (FPKM) Data" :name="4"/>
<upload-geno></upload-geno>
</el-tab-pane>
<el-tab-pane label="Molecular maker" :name="3">
<upload-geno></upload-geno>
</el-tab-pane>
<el-tab-pane label="Gene Expression Level (FPKM) Data" :name="4">
<upload-geno></upload-geno>
</el-tab-pane>
</el-tabs> </el-tabs>
<upload-geno v-model="formData.tFiles" :gene-file-type="currentTabs" @uploadSuccess="uploadSuccess" @del-file="delFileGeno" ref="uploadGenoRef"></upload-geno>
</div> </div>
<template #footer> <template #footer>
<el-button type="primary" :loading="loading" @click="saveMassif"></el-button> <el-button type="primary" :loading="submitLoading" @click="saveGeno"></el-button>
<el-button @click="showGenoForm = false">取消</el-button> <el-button @click="showGenoForm = false">取消</el-button>
</template> </template>
</left-drawer> </left-drawer>
@ -155,27 +181,30 @@
</template> </template>
<script setup name="User"> <script setup name="User">
import {
changeUserStatus,
listUser,
resetUserPwd,
delUser,
getUser,
updateUser,
addUser,
deptTreeSelect
} from "@/api/system/user";
import TitleDivider from "@/components/titleDivider/index.vue"; import TitleDivider from "@/components/titleDivider/index.vue";
import useSettingsStore from "@/store/modules/settings.js"; import useSettingsStore from "@/store/modules/settings.js";
import UploadGeno from "@/views/genoTypeData/genoManage/uploadGeno.vue"; import UploadGeno from "@/views/genoTypeData/genoManage/uploadGeno.vue";
import {fileToBlobURL} from "@/utils/ruoyi.js"; import {fileToBlobURL, handleTree} from "@/utils/ruoyi.js";
import {
addGeno, delGeno, editGeno,
selClassInfo,
selFamilyInfo, selGenoById, selGenoPage,
selGenusInfo,
selKingdomInfo, selOrderInfo,
selPhylumInfo
} from "@/api/basicData/genoManageApi.js";
import {ElMessageBox} from "element-plus";
import EventBus from "@/utils/EventBus.js";
import {isArray} from "@/utils/validate.js";
const router = useRouter(); const router = useRouter();
const cascaderRef = ref(null)
const formRef = ref(null)
const {proxy} = getCurrentInstance(); const {proxy} = getCurrentInstance();
const {sys_normal_disable} = proxy.useDict("sys_normal_disable");
const tableList = ref([]); const tableList = ref([]);
const open = ref(false); const open = ref(false);
const loading = ref(false); const loading = ref(false);
const submitLoading = ref(false);
const showSearch = ref(true); const showSearch = ref(true);
const ids = ref([]); const ids = ref([]);
const single = ref(true); const single = ref(true);
@ -199,15 +228,63 @@ const columns = ref([
{key: 4, label: `创建人`, visible: true}, {key: 4, label: `创建人`, visible: true},
{key: 5, label: `创建时间`, visible: true}, {key: 5, label: `创建时间`, visible: true},
]); ]);
const exportExcel = () => { const options = reactive({
//
kingdomInfo: [],
//
phylumInfo: [],
//
classInfo: [],
//
orderInfo: [],
//
familyInfo: [],
//
genusInfo: [],
levelMap: {
1: selPhylumInfo,
2: selClassInfo,
3: selOrderInfo,
4: selFamilyInfo,
5: selGenusInfo,
}
})
const cascaderProps = {
lazy: false,
checkStrictly: false,
value: 'key',
lazyLoad(node, resolve) {
if (node.data.level) {
options.levelMap[node.data.level]({id: node.value}).then(res => {
resolve(res)
})
} else {
selPhylumInfo({id: 1}).then(res => {
options.phylumInfo = res
resolve(res)
})
} }
},
}
const selOptions = () => {
selKingdomInfo({}).then(res => {
options.kingdomInfo = res
})
Promise.all([selPhylumInfo({}), selClassInfo({}), selOrderInfo({}), selFamilyInfo({}), selGenusInfo({})]).then(res => {
let list = []
res.forEach(item => {
list.push(...item)
})
options.genusInfo = handleTree(list, 'key', 'parent')
})
}
onMounted(() => {
selOptions()
getList()
})
const formData = ref({ const formData = ref({
tempFileImage:undefined, thumbnailUrl: undefined,
Genome:[], tFiles: [],
Annotation:[],
Molecular:[],
GeneLevel:[],
}) })
const data = reactive({ const data = reactive({
form: {}, form: {},
@ -219,31 +296,93 @@ const data = reactive({
status: undefined, status: undefined,
deptId: undefined deptId: undefined
}, },
rules: {
speciesName: [
{required: true, message: '请输入物种名称', trigger: 'blur'},
],
kingdomId: [
{required: true, message: '请选择所属界', trigger: 'blur'},
],
genus: [
{required: true, message: '请选择物种属类', trigger: 'blur'},
],
thumbnailUrl: [
{required: true, message: '请上传物种图片', trigger: 'blur'},
]
}
}); });
const {queryParams, form,} = toRefs(data); const {queryParams, form, rules} = toRefs(data);
/** 查询用户列表 */ /** 查询用户列表 */
function getList() { function getList() {
loading.value = true; loading.value = true;
listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => { selGenoPage(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
loading.value = false; loading.value = false;
tableList.value = res.rows; tableList.value = res.rows;
total.value = res.total; total.value = res.total;
}); });
}; };
const selPhylum = (value) => {
if (value) {
selPhylumInfo({id: value}).then(res => {
options.phylumInfo = res
})
}
}
const uploadGenoRef = ref(null)
const saveGeno = () => {
if (formData.value.genus) {
formData.value.genus.forEach(item => {
let obj = item.split('-')
formData.value[obj[0]] = Number(obj[1])
})
}
let api = formData.value.id ? editGeno : addGeno
const params = {
...formData.value,
genus: undefined,
tFiles:formData.value.tFiles.filter(item => !!item.id)
}
formRef.value.validate(valid => {
if (valid) {
submitLoading.value = true
api(params).then(res => {
uploadGenoRef.value.uploadFile(res.data)
// proxy.$modal.msgSuccess('');
// getList()
// showGenoForm.value = false
}).catch(() => {
submitLoading.value = false
})
} else {
proxy.$modal.msgError('保存失败,请确认所有信息均已填写');
}
})
}
const uploadSuccess = () => {
submitLoading.value = false
proxy.$modal.msgSuccess('保存成功');
getList()
showGenoForm.value = false
}
/** 搜索按钮操作 */ /** 搜索按钮操作 */
function handleQuery() { function handleQuery() {
queryParams.value.pageNum = 1; queryParams.value.pageNum = 1;
getList(); getList();
}; };
const beforeAvatarUpload = (file) => { const beforeAvatarUpload = (file) => {
formData.value.file = file
formData.value.fileName = file.name
fileToBlobURL(file).then(res => { fileToBlobURL(file).then(res => {
formData.value.tempFileImage = res formData.value.thumbnailUrl = res
}) })
console.log(file);
return false return false
} }
const delFile = () => {
formData.value.file = undefined
formData.value.thumbnailUrl = undefined
}
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
dateRange.value = []; dateRange.value = [];
@ -259,19 +398,59 @@ function handleSelectionChange(selection) {
single.value = selection.length != 1; single.value = selection.length != 1;
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
const echoGenus = (form) => {
formData.value.genus = [`phylumId-${form.phylumId}`, `classId-${form.classId}`,
`orderId-${form.orderId}`, `familyId-${form.familyId}`, `genusId-${form.genusId}`]
}
const toView = (row) => { const toView = (row) => {
formData.value = row
showView.value = true showView.value = true
selGenoById({id: row.id}).then(res => {
formData.value = res
echoGenus(res)
})
}
const toEdit = (row) => {
showGenoForm.value = true
selGenoById({id: row.id}).then(res => {
formData.value = res
echoGenus(res)
})
}
const handleDelete = (row) => {
ElMessageBox.confirm(
'数据删除后,将无法被找回,是否仍要删除?',
'删除数据',
{
confirmButtonText: '确认删除',
cancelButtonText: '不,我再想想',
customClass: 'message-box'
}
).then(() => {
delGeno({id: row.id}).then(res => {
proxy.$modal.msgSuccess('删除成功');
getList()
})
}).catch()
} }
const toAdd = () => { const toAdd = () => {
formData.value = {} formData.value = {
kingdomId: 1,
tFiles:[]
}
submitLoading.value = false
showGenoForm.value = true showGenoForm.value = true
} }
const handleUpdate = (row) => { const handleUpdate = (row) => {
formData.value = row formData.value = row
showGenoForm.value = true showGenoForm.value = true
} }
const delFileGeno = (item) => {
if (isArray(formData.value.delFileIds)){
formData.value.delFileIds.push(item.id)
}else{
formData.value.delFileIds = [item.id]
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.base-title { .base-title {
@ -289,11 +468,13 @@ const handleUpdate = (row) => {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin: 10px 0; margin: 10px 0;
.label { .label {
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
font-size: var(--el-form-label-font-size); font-size: var(--el-form-label-font-size);
} }
} }
border: 1px solid #d0d0d0; border: 1px solid #d0d0d0;
border-radius: 10px; border-radius: 10px;
padding: 10px 20px; padding: 10px 20px;
@ -303,14 +484,17 @@ const handleUpdate = (row) => {
.el-form-item { .el-form-item {
width: 100%; width: 100%;
} }
.avatar-uploader .avatar { .avatar-uploader .avatar {
width: 130px; width: 130px;
height: 130px; height: 130px;
display: block; display: block;
} }
.avatar-uploader :deep(.el-upload) { .avatar-uploader :deep(.el-upload) {
background: #f6f6f6;
border: 1px dashed var(--el-border-color); border: 1px dashed var(--el-border-color);
border-radius: 6px; border-radius: 10px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@ -320,8 +504,13 @@ const handleUpdate = (row) => {
.avatar-uploader :deep(.el-upload:hover) { .avatar-uploader :deep(.el-upload:hover) {
border-color: var(--el-color-primary); border-color: var(--el-color-primary);
} }
.upload-icon{
height: 24px;
width: 24px;
}
.avatar-uploader-image { .avatar-uploader-image {
position: relative; position: relative;
.image { .image {
position: absolute; position: absolute;
top: 0; top: 0;
@ -334,12 +523,15 @@ const handleUpdate = (row) => {
cursor: pointer; cursor: pointer;
} }
} }
.avatar-uploader { .avatar-uploader {
width: 130px; width: 130px;
height: 130px; height: 130px;
text-align: center; text-align: center;
} }
.avatar-uploader-icon { .avatar-uploader-icon {
margin-top: 30px; margin-top: 30px;
} }
</style> </style>

@ -1,31 +1,150 @@
<script setup> <script setup>
import useSettingsStore from "@/store/modules/settings.js"; import {formatNumber, processDot} from "@/utils/ruoyi.js";
import FileUploader from "@/components/fileUploader/index.vue";
import EventBus from "@/utils/EventBus.js";
import {isArray} from "@/utils/validate.js";
const props = defineProps({ const props = defineProps({
uploadType:Number // 1=Gnome 2=Annotation 3=Molecular maker 4=Gene Expression Level(FPKM)
geneFileType: Number,
formData:Object
}) })
const emit = defineEmits(['delFile','uploadSuccess'])
const modelValue = defineModel({type: Array}) const modelValue = defineModel({type: Array})
const settingsStore = useSettingsStore() const formatFileSize = (fileSize)=>{
const theme = computed(() => settingsStore.theme); return formatNumber(fileSize/1024)
}
const delFile = (item,index) => {
if (item.id){
emit('delFile',item)
}
uploadRef.value.handleRemoveFile(item)
}
const uploadRef = ref(null)
const uploadFile = (data) => {
if (modelValue.value.length>0){
uploadRef.value.startUpload({
// id
tableId:data.id,
// 1=2=3=
tableType:3,
// 1=Gnome 2=Annotation 3=Molecular maker 4=Gene Expression Level(FPKM)
geneFileType:props.geneFileType,
// =1 =2=3
fileType:3
})
}else{
uploadSuccess()
}
}
const uploadSuccess = () => {
emit('uploadSuccess')
}
defineExpose({
uploadFile,
})
const showFileList = computed(()=>{
if (isArray(modelValue.value)){
return modelValue.value.filter(item =>item.geneFileType === props.geneFileType)
}else{
return []
}
})
const beforeUpload = (options) => {
modelValue.value.push({
fileSize:options.file.size,
fileName:options.file.name,
geneFileType:props.geneFileType,
fileMemo:'',
...options
})
return false
}
</script> </script>
<template> <template>
<div> <div>
<el-upload <file-uploader @upload-success="uploadSuccess" ref="uploadRef" v-model="modelValue" @beforeUpload="beforeUpload"/>
class="upload-demo" <div v-for="(item, index) in showFileList" style="margin-top: 10px">
drag <div class="file-list-item">
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" <div class="file-list-title">
multiple <div class="file-name">
> <svg-icon class-name="annex-icon" icon-class="annex"></svg-icon> {{ item.fileName }}
<i-upload theme="outline" size="24" :fill="theme"/> </div>
<div class="el-upload__text"> <div class="file-size">
<em>点击上传附件</em> {{formatFileSize(item.fileSize)}} KB <i-close-one theme="outline" size="24" fill="#d73833" class="file-close-icon" @click="delFile(item,index)"/>
<div>或将附件拖动至此上传</div> </div>
</div>
<div class="file-remark">
<el-input placeholder="请输入文件说明" v-model="item.fileMemo"></el-input>
<el-progress
v-if="item.startUpload"
striped
striped-flowx
:indeterminate="item.indeterminate"
:percentage="item.percent"
/>
</div>
</div>
</div> </div>
</el-upload>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.avatar-uploader :deep(.el-upload) {
background: #f6f6f6;
border: 1px dashed var(--el-border-color);
border-radius: 10px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
//
//.avatar-uploader :deep(.el-upload:hover) {
// border-color: var(--el-color-primary);
//}
:deep(.el-upload-dragger) {
background: #f6f6f6;
border-radius: 10px;
}
.file-list-item{
margin: 10px 0;
border-bottom: 1px solid #e6e6e6;
.annex-icon{
height: 24px;
width: 24px;
margin-right: 10px;
}
.file-name{
display: flex;
align-items: center;
float: left;
}
.file-remark{
margin: 0 30px;
:deep(.el-input__wrapper){
box-shadow: none;
}
}
.file-close-icon{
margin-left: 10px;
//
cursor: pointer;
}
.file-size{
margin-left: 20px;
display: flex;
align-items: center;
float: right;
}
.file-list-title{
height: 32px;
margin-top: 5px;
}
}
.upload-icon{
height: 48px;
width: 48px;
}
</style> </style>

@ -110,6 +110,7 @@ const selectPoi = ({poi}) => {
@select="selectPoi" @select="selectPoi"
/> />
<div class="top-block"> <div class="top-block">
<!--右侧控制栏-->
<base-right-control v-model="control" is-base-map/> <base-right-control v-model="control" is-base-map/>
</div> </div>
</el-amap> </el-amap>

@ -0,0 +1,253 @@
<!--气象数据-->
<template>
<div class="app-container">
<el-row :gutter="20">
<!--育种基地/资源圃选择-->
<el-col :span="4" :xs="24">
<base-selection v-model="currentBase"></base-selection>
</el-col>
<!--用户数据-->
<el-col :span="20" :xs="24">
<el-row class="base-title">
<el-col :span="6" class="current-base-name">当前基地{{currentBase.baseName || '请选择基地'}}</el-col>
</el-row>
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-row :gutter="10">
<el-col :span="6">
<el-form-item label="种植季">
<el-select v-model="queryParams.plantingSeason">
<el-option v-for="item in plantingSeasonList" :value="item.value" :label="item.label"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="植株编号">
<el-input v-model="queryParams.no" placeholder="请输入植株编号"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="作物品种">
<el-select v-model="queryParams.pz" placeholder="请选择作物品种">
<el-option></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="所属地块名称">
<el-input v-model="queryParams.no" placeholder="请输入所属地块名称"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="植株位置(垄)">
<el-input v-model="queryParams.no" placeholder="请输入垄数"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="植株位置(株)">
<el-input v-model="queryParams.no" placeholder="请输入株数"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-row :gutter="10" class="mb8">
<el-button type="primary" icon="Download" @click="exportExcel">Excel</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table border v-loading="loading" :data="tableList" @selection-change="handleSelectionChange">
<el-table-column label="植株编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
<el-table-column label="所属地块编号" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
<el-table-column label="所属地块名称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
<el-table-column label="植株位置(垄)" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
<el-table-column label="植株位置(株)" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
<el-table-column label="作物名称" align="center" key="phonenumber" prop="phonenumber" v-if="columns[5].visible" width="120" />
<el-table-column label="数据量" align="center" key="status" v-if="columns[6].visible"></el-table-column>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="详情" placement="top">
<el-button link type="primary" icon="View" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
<template #empty>
<el-result
title="暂无数据">
<template #icon>
<i-database-fail theme="outline" size="38" fill="#9b9b9b"/>
</template>
</el-result>
</template>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-col>
</el-row>
<left-drawer title="植株表型数据详情" width="80%" v-model="showModel" >
<div>
<title-divider title="植株基本信息"/>
<el-form>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="种植季信息:"></el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="植株编号:"></el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="作物名称:"></el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="作物品种:"></el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="所属基地/资源圃:"></el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="所属地块编号:"></el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="所属地块名称:"></el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="植株位置:"></el-form-item>
</el-col>
</el-row>
</el-form>
<title-divider title="历史表型数据"/>
<el-table border v-loading="loading" :data="tableList" @selection-change="handleSelectionChange">
<el-table-column label="数据采集时间" align="center" key="userId" prop="userId"/>
<el-table-column label="株高(cm)" align="center" key="userId" prop="userId"/>
<el-table-column label="分蘖数" align="center" key="userId" prop="userId"/>
<el-table-column label="节间直径(cm)" align="center" key="userId" prop="userId"/>
<el-table-column label="节数" align="center" key="userId" prop="userId"/>
<el-table-column label="节间长(cm)" align="center" key="userId" prop="userId"/>
<el-table-column label="叶夹角(°)" align="center" key="userId" prop="userId"/>
<el-table-column label="叶色" align="center" key="userId" prop="userId"/>
<el-table-column label="叶面积(cm)" align="center" key="userId" prop="userId"/>
<el-table-column label="叶片含氮量(%)" align="center" key="userId" prop="userId"/>
<el-table-column label="病害" align="center" key="userId" prop="userId"/>
<el-table-column label="虫害" align="center" key="userId" prop="userId"/>
<el-table-column label="茎秆每节含糖量(%)" align="center" key="userId" prop="userId"/>
<el-table-column label="鲜重(kg)" align="center" key="userId" prop="userId"/>
<el-table-column label="图片/视频" align="center" key="userId" prop="userId">
<template #default="scope">
<el-button link type="primary" @click="handleShowView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</left-drawer>
</div>
</template>
<script setup name="User">
import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser, deptTreeSelect } from "@/api/system/user";
import LeftDrawer from "@/components/LeftDrawer/index.vue";
import TitleDivider from "@/components/titleDivider/index.vue";
const massifFormRef = ref(null)
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
const currentBase = ref({})
const tableList = ref([]);
const open = ref(false);
const showModel = ref(false);
const loading = ref(false);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");
const dateRange = ref([]);
//
const columns = ref([
{ key: 0, label: `植株编号`, visible: true },
{ key: 1, label: `所属地块编号`, visible: true },
{ key: 2, label: `所属地块名称`, visible: true },
{ key: 3, label: `植株位置(垄)`, visible: true },
{ key: 4, label: `植株位置(株)`, visible: true },
{ key: 5, label: `作物名称`, visible: true },
{ key: 6, label: `作物品种`, visible: true },
{ key: 7, label: `数据量`, visible: true },
]);
const exportExcel = () => {
}
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
plantingSeason: 2023,
status: undefined,
deptId: undefined
},
});
const { queryParams, form, rules} = toRefs(data);
/** 查询用户列表 */
function getList() {
loading.value = true;
listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
loading.value = false;
tableList.value = res.rows;
total.value = res.total;
});
};
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = [];
proxy.resetForm("queryRef");
queryParams.value.deptId = undefined;
proxy.$refs.deptTreeRef.setCurrentKey(null);
handleQuery();
};
/** 选择条数 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.userId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const userId = row.userId || ids.value;
getUser(userId).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改用户";
form.password = "";
});
};
</script>
<style scoped lang="scss">
.base-title{
margin: 10px 0;
.handoff-model{
padding-right: 20px;
text-align: right;
}
}
.el-form-item{
width: 100%;
}
</style>

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

@ -0,0 +1,11 @@
<script setup>
</script>
<template>
</template>
<style scoped lang="scss">
</style>
Loading…
Cancel
Save