u
This commit is contained in:
1 parent
401a6719f6
commit
b7f50f3ccb
22 files changed
+1595
-343
No files matched your search
+2
@@ -127,6 +127,8 @@ public class FileController {
|
||||
// 发送删除请求
|
||||
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
|
||||
|
||||
System.out.println(requestUrl);
|
||||
|
||||
try {
|
||||
HttpUtils.sendGet(requestUrl);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -10,7 +10,7 @@ public class JwtUtils {
|
||||
// 秘钥
|
||||
private static final String SECRET_KEY = "G3_SECRET_KEY";
|
||||
// 过期时间,1天。
|
||||
private static final Integer EXPIRE_TIME = 1;
|
||||
private static final Integer EXPIRE_TIME = 7;
|
||||
|
||||
// 创建 Token
|
||||
public static String createToken(JwtPayload jwtPayload) {
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
VITE_APP_PORT = 8080
|
||||
|
||||
VITE_ALIYUN_BUCKET = 'g3cdcont'
|
||||
VITE_ALIYUN_BUCKET = 'g3hy'
|
||||
@@ -1,24 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
|
||||
import { dialog, isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
|
||||
import { aliyunpluploadApi, deleteAliyunFileApi, deleteFileApi, recognizeOcrBoxApi } from '@/request/api'
|
||||
import { downloadFile, formatFileSize, getFileExtension, imageCompress, isImage, uploadFileToAliyun } from '@/utils/file'
|
||||
import { aliyunpluploadApi, deleteAliyunFileApi, deleteFileApi, getUniqueIdApi, recognizeOcrBoxApi, saveObjectApi } from '@/request/api'
|
||||
import { downloadFile, getFileExtension, imageCompress, isImage, uploadFileToAliyun } from '@/utils/file'
|
||||
|
||||
const boxImage = ref('')
|
||||
const boxImage = ref(null)
|
||||
|
||||
const ocrFinish = ref(false)
|
||||
const isError = ref(false)
|
||||
const ocrScore = ref('0.00')
|
||||
|
||||
const showResult = ref(false)
|
||||
|
||||
const form = ref({
|
||||
b_id: 0,
|
||||
boxNumber: '',
|
||||
boxImageUrl: '',
|
||||
containerNo: '',
|
||||
containerImageUrl: '',
|
||||
ocrScore: 0.00,
|
||||
ocrNo: '',
|
||||
})
|
||||
|
||||
const subFileList = ref([])
|
||||
|
||||
const otherImages = ref([])
|
||||
const tipContainerNo = ref('')
|
||||
|
||||
onLoad(async () => {
|
||||
await getUniqueIdApi().then((res) => {
|
||||
if (res.success) {
|
||||
form.value.b_id = res.data
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function handleChooseBox() {
|
||||
uni.chooseImage({
|
||||
@@ -27,7 +38,7 @@ function handleChooseBox() {
|
||||
if (Array.isArray(res.tempFiles) && res.tempFiles.length > 0) {
|
||||
const tempFile: any = res.tempFiles[0]
|
||||
|
||||
boxImage.value = tempFile.path
|
||||
boxImage.value = tempFile
|
||||
|
||||
toast.loading('OCR识别中,请稍等 . . .', {
|
||||
overlay: true,
|
||||
@@ -37,20 +48,21 @@ function handleChooseBox() {
|
||||
imageCompress(tempFile.path).then((compressRes) => {
|
||||
uploadFileToAliyun(compressRes.path, tempFile.name, 'image').then((res) => {
|
||||
if (res.url) {
|
||||
form.value.boxImageUrl = res.url
|
||||
form.value.containerImageUrl = res.url
|
||||
|
||||
ocrFinish.value = false
|
||||
|
||||
recognizeOcrBoxApi({
|
||||
fileUrl: form.value.boxImageUrl,
|
||||
fileUrl: form.value.containerImageUrl,
|
||||
}).then((ocrRes) => {
|
||||
if (ocrRes.success) {
|
||||
toast.success('识别成功,请检查箱号是否正确')
|
||||
|
||||
const score = Number(ocrRes?.data?.score ?? 0)
|
||||
ocrScore.value = `${(Math.floor(score * 10000) / 100).toFixed(2)}%`
|
||||
form.value.ocrScore = Math.floor(score * 100) / 100
|
||||
|
||||
form.value.boxNumber = ocrRes.data.boxNumber
|
||||
form.value.containerNo = ocrRes.data.boxNumber
|
||||
form.value.ocrNo = ocrRes.data.boxNumber
|
||||
|
||||
isError.value = false
|
||||
}
|
||||
@@ -66,12 +78,21 @@ function handleChooseBox() {
|
||||
},
|
||||
})
|
||||
}
|
||||
const computedOcrScore = computed(() => {
|
||||
return `${(Math.floor(form.value.ocrScore * 100)).toFixed(0)}%`
|
||||
})
|
||||
|
||||
function resetForm() {
|
||||
boxImage.value = null
|
||||
form.value.containerImageUrl = ''
|
||||
form.value.containerNo = ''
|
||||
form.value.ocrNo = ''
|
||||
form.value.ocrScore = 0.00
|
||||
}
|
||||
|
||||
function handleDeleteBoxImage() {
|
||||
if (!form.value.boxImageUrl) {
|
||||
boxImage.value = ''
|
||||
form.value.boxImageUrl = ''
|
||||
form.value.boxNumber = ''
|
||||
if (!form.value.containerImageUrl) {
|
||||
resetForm()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -80,12 +101,9 @@ function handleDeleteBoxImage() {
|
||||
transparent: false,
|
||||
})
|
||||
|
||||
deleteAliyunFileApi(form.value.boxImageUrl).then((res) => {
|
||||
deleteAliyunFileApi(form.value.containerImageUrl).then((res) => {
|
||||
if (res.success) {
|
||||
boxImage.value = ''
|
||||
form.value.boxImageUrl = ''
|
||||
form.value.boxNumber = ''
|
||||
|
||||
resetForm()
|
||||
toast.success('删除成功')
|
||||
}
|
||||
})
|
||||
@@ -115,8 +133,27 @@ function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
|
||||
}
|
||||
|
||||
async function afterRead(fileItem: UploadFileItem) {
|
||||
// 设置上传中状态
|
||||
fileItem.status = 'uploading'
|
||||
fileItem.message = '正在上传'
|
||||
|
||||
fileItem.isUpload = false
|
||||
|
||||
// 上传到阿里云
|
||||
const res = await uploadFileToAliyun(
|
||||
fileItem.file.path,
|
||||
fileItem.file.name,
|
||||
fileItem.file.type,
|
||||
)
|
||||
|
||||
if (!res.url)
|
||||
throw new Error('上传失败:未获取到文件 URL')
|
||||
|
||||
// 上传完成
|
||||
fileItem.status = 'done'
|
||||
fileItem.message = '上传成功'
|
||||
fileItem.url = res.url
|
||||
|
||||
subFileList.value = [...subFileList.value]
|
||||
}
|
||||
|
||||
@@ -135,13 +172,11 @@ function beforeRemove(index: number, fileItem: UploadFileItem): boolean | Promis
|
||||
}
|
||||
|
||||
function onRemove(index: number, item: UploadFileItem) {
|
||||
console.log(item)
|
||||
|
||||
if (!item.isUpload) {
|
||||
return
|
||||
}
|
||||
|
||||
deleteFileApi(item.b_id).then((res) => {
|
||||
deleteAliyunFileApi(item.url).then((res) => {
|
||||
if (res.success) {
|
||||
toast.success('删除成功')
|
||||
}
|
||||
@@ -149,144 +184,281 @@ function onRemove(index: number, item: UploadFileItem) {
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
if (form.value.containerImageUrl === '') {
|
||||
toast.fail('集装箱号图片不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (form.value.containerNo === '') {
|
||||
toast.fail('集装箱号不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateContainerNo(form.value.containerNo)) {
|
||||
toast.fail('集装箱号不符合规则,请检查')
|
||||
return
|
||||
}
|
||||
|
||||
toast.loading('正在保存中 . . .', {
|
||||
overlay: true,
|
||||
transparent: false,
|
||||
})
|
||||
|
||||
// 图片已经上传成功,保存主表即可
|
||||
const mainTable = {
|
||||
b_id: form.value.b_id,
|
||||
b_container_no: form.value.containerNo,
|
||||
b_container_img: form.value.containerImageUrl,
|
||||
b_ocr_no: form.value.ocrNo,
|
||||
b_ocr_score: form.value.ocrScore,
|
||||
}
|
||||
|
||||
const fileInserts = []
|
||||
|
||||
const containerNo = form.value.containerNo
|
||||
|
||||
subFileList.value.forEach(async (fileItem, index) => {
|
||||
// 上传到阿里云
|
||||
const res = await uploadFileToAliyun(
|
||||
fileItem.file.path,
|
||||
fileItem.file.name,
|
||||
fileItem.file.type,
|
||||
)
|
||||
const uniqueRes = await getUniqueIdApi()
|
||||
const indexNumber = index + 2
|
||||
|
||||
if (!res.url)
|
||||
throw new Error('上传失败:未获取到文件 URL')
|
||||
|
||||
// 调用后端接口保存文件信息
|
||||
const saveRes = await aliyunpluploadApi({
|
||||
fileInserts.push({
|
||||
subid: uniqueRes.data,
|
||||
father: form.value.b_id,
|
||||
mx_xh: index,
|
||||
mx_filename: fileItem.file.name,
|
||||
mx_xh: indexNumber,
|
||||
mx_filename: buildFileName(containerNo, indexNumber, fileItem.file.name),
|
||||
mx_filesize: fileItem.file.size,
|
||||
mx_fileext: getFileExtension(fileItem.file.name),
|
||||
mx_mapfilename: res.url,
|
||||
mx_mapfilename: fileItem.url,
|
||||
mx_moduleid: 'b_container',
|
||||
mx_cate_id: '1',
|
||||
})
|
||||
})
|
||||
|
||||
if (saveRes.success) {
|
||||
fileItem.isUpload = true
|
||||
fileItem.b_id = saveRes.data
|
||||
const uniqueRes = await getUniqueIdApi()
|
||||
|
||||
fileInserts.push({
|
||||
subid: uniqueRes.data,
|
||||
father: form.value.b_id,
|
||||
mx_xh: 0,
|
||||
mx_filename: buildFileName(containerNo, 1, boxImage.value.name),
|
||||
mx_filesize: boxImage.value.size,
|
||||
mx_fileext: getFileExtension(boxImage.value.name),
|
||||
mx_mapfilename: form.value.containerImageUrl,
|
||||
mx_moduleid: 'b_container',
|
||||
mx_cate_id: '0',
|
||||
})
|
||||
|
||||
const reqData = [
|
||||
{
|
||||
tableName: 'b_container',
|
||||
keyField: 'b_id',
|
||||
inserts: [mainTable],
|
||||
updates: [],
|
||||
deletes: [],
|
||||
},
|
||||
{
|
||||
tableName: 'bf_file',
|
||||
keyField: 'subid',
|
||||
inserts: fileInserts,
|
||||
updates: [],
|
||||
deletes: [],
|
||||
},
|
||||
]
|
||||
|
||||
saveObjectApi(reqData).then((res) => {
|
||||
if (res.success) {
|
||||
toast.success('保存成功')
|
||||
tipContainerNo.value = form.value.containerNo
|
||||
showResult.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function buildFileName(containerNo, index, fileName) {
|
||||
const formattedIndex = String(index).padStart(2, '0')
|
||||
|
||||
return `${containerNo}_${formattedIndex}.${fileName.split('.').pop()}`
|
||||
}
|
||||
|
||||
// 集装箱号校验
|
||||
function validateContainerNo(containerNo) {
|
||||
// 正则表达式: 4个字母 + 7个数字
|
||||
const pattern = /^[A-Z]{4}\d{7}$/i
|
||||
|
||||
// 使用正则进行匹配
|
||||
if (pattern.test(containerNo)) {
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function continueAdd() {
|
||||
const idRes = await getUniqueIdApi()
|
||||
|
||||
if (idRes.success) {
|
||||
boxImage.value = null
|
||||
|
||||
ocrFinish.value = false
|
||||
isError.value = false
|
||||
|
||||
resetForm()
|
||||
form.value.b_id = idRes.data
|
||||
|
||||
subFileList.value = []
|
||||
showResult.value = false
|
||||
|
||||
tipContainerNo.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="g3-container">
|
||||
<view class="g3-content">
|
||||
<view class="step">
|
||||
<view class="step-header">
|
||||
<sar-tag theme="primary" mark="right" size="large">
|
||||
1、集装箱号识别(OCR自动识别)
|
||||
</sar-tag>
|
||||
<template v-if="!showResult">
|
||||
<view class="g3-content">
|
||||
<view class="step">
|
||||
<view class="step-header">
|
||||
<sar-tag theme="primary" mark="right" size="large">
|
||||
1、集装箱号识别(OCR自动识别)
|
||||
</sar-tag>
|
||||
</view>
|
||||
|
||||
<view class="step-content box">
|
||||
<view v-if="ocrFinish" class="header ">
|
||||
<sar-alert
|
||||
v-if="!isError"
|
||||
type="success"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别成功,请检查箱号是否正确!
|
||||
<view>
|
||||
准确率:{{ computedOcrScore }}
|
||||
</view>
|
||||
</sar-alert>
|
||||
<sar-alert
|
||||
v-else
|
||||
type="danger"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别失败,请检查手动填写集装箱号
|
||||
</sar-alert>
|
||||
</view>
|
||||
|
||||
<view class="box-main">
|
||||
<view v-if="!boxImage" class="box-placeholder" @click="handleChooseBox">
|
||||
<sar-icon name="image" size="56rpx" color="#999" />
|
||||
<text class="placeholder-text">
|
||||
上传箱号图片,自动识别箱号
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="box-image">
|
||||
<wd-img
|
||||
:src="boxImage.path"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:enable-preview="true"
|
||||
/>
|
||||
|
||||
<view class="delete-btn" @click="handleDeleteBoxImage()">
|
||||
<sar-icon name="close" size="20rpx" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="box-input">
|
||||
<view class="header">
|
||||
<view class="label-container">
|
||||
<view>集装箱号</view>
|
||||
<view class="required-asterisk">
|
||||
*
|
||||
</view>
|
||||
</view>
|
||||
<view class="format">
|
||||
格式: 4字母 + 7数字
|
||||
</view>
|
||||
</view>
|
||||
<sar-input
|
||||
v-model="form.containerNo"
|
||||
placeholder="请输入箱号"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="step-content box">
|
||||
<view v-if="ocrFinish" class="header ">
|
||||
<sar-alert
|
||||
v-if="!isError"
|
||||
type="success"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别成功,请检查箱号是否正确!
|
||||
<view>
|
||||
准确率:{{ ocrScore }}
|
||||
</view>
|
||||
</sar-alert>
|
||||
<sar-alert
|
||||
v-else
|
||||
type="danger"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别失败,请检查手动填写集装箱号
|
||||
</sar-alert>
|
||||
<view class="step">
|
||||
<view class="step-header">
|
||||
<sar-tag theme="primary" mark="right" size="large">
|
||||
2、其它照片录入
|
||||
</sar-tag>
|
||||
</view>
|
||||
|
||||
<view class="box-main">
|
||||
<view v-if="!boxImage" class="box-placeholder" @click="handleChooseBox">
|
||||
<sar-icon name="image" size="56rpx" color="#999" />
|
||||
<text class="placeholder-text">
|
||||
上传箱号图片,自动识别箱号
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="box-image">
|
||||
<wd-img
|
||||
:src="boxImage"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:enable-preview="true"
|
||||
/>
|
||||
|
||||
<view class="delete-btn" @click="handleDeleteBoxImage()">
|
||||
<sar-icon name="close" size="20rpx" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="box-input">
|
||||
<view class="header">
|
||||
<view>集装箱号</view>
|
||||
<view class="format">
|
||||
格式: 4字母 + 7数字
|
||||
</view>
|
||||
</view>
|
||||
<sar-input
|
||||
v-model="form.boxNumber"
|
||||
placeholder="请输入箱号"
|
||||
<view class="step-content">
|
||||
<sar-upload
|
||||
v-model="subFileList"
|
||||
multiple
|
||||
:after-read="afterRead"
|
||||
:before-read="beforeRead"
|
||||
:before-remove="beforeRemove"
|
||||
@item-click="onItemClick"
|
||||
@remove="onRemove"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="step">
|
||||
<view class="step-header">
|
||||
<sar-tag theme="primary" mark="right" size="large">
|
||||
2、其它照片录入
|
||||
</sar-tag>
|
||||
</view>
|
||||
<view class="step-content">
|
||||
<sar-upload
|
||||
v-model="subFileList"
|
||||
multiple
|
||||
:after-read="afterRead"
|
||||
:before-read="beforeRead"
|
||||
:before-remove="beforeRemove"
|
||||
@item-click="onItemClick"
|
||||
@remove="onRemove"
|
||||
/>
|
||||
<view class="g3-footer">
|
||||
<view class="footer-main">
|
||||
<view class="submit-btn" @click="handleConfirm">
|
||||
确认上传
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="g3-footer">
|
||||
<view class="footer-main">
|
||||
<view class="submit-btn" @click="handleConfirm">
|
||||
确认上传
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="g3-result">
|
||||
<sar-result status="success" title="操作成功" description="已成功录入箱号 为 xxx 的记录">
|
||||
<template #description>
|
||||
<view class="result-tip">
|
||||
已成功录入箱号
|
||||
<text class="container-no">
|
||||
"{{ tipContainerNo }}"
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<sar-button long style="width: 360rpx;font-size: 38rpx;" @click="continueAdd">
|
||||
继续录入
|
||||
</sar-button>
|
||||
</sar-result>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.g3-result {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.result-tip {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.container-no {
|
||||
font-weight: 600;
|
||||
color: #4caf50;
|
||||
}
|
||||
}
|
||||
|
||||
.g3-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -386,6 +558,18 @@ async function handleConfirm() {
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 12rpx;
|
||||
width: 100%;
|
||||
.label-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.required-asterisk {
|
||||
position: absolute;
|
||||
top: -5rpx;
|
||||
right: -12rpx;
|
||||
color: red;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.format {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { deleteFileApi } from '@/request/api'
|
||||
import { useUserStore } from '@/store'
|
||||
|
||||
const userStore = useUserStore()
|
||||
@@ -28,6 +29,16 @@ function toLinkUrl(url) {
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
function deleteAliyunFiles() {
|
||||
const subids = [
|
||||
787154320330821,
|
||||
787154320388165,
|
||||
]
|
||||
subids.forEach((subid) => {
|
||||
deleteFileApi(subid)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -75,6 +86,10 @@ function toLinkUrl(url) {
|
||||
</view>
|
||||
</sar-grid-item>
|
||||
</sar-grid>
|
||||
|
||||
<sar-button @click="deleteAliyunFiles">
|
||||
删除附件
|
||||
</sar-button>
|
||||
</view>
|
||||
<sar-result v-else status="warning" title="尚未登陆" style="margin-top: 32rpx;">
|
||||
<sar-button
|
||||
|
||||
@@ -42,9 +42,9 @@ export function recognizeOcrBoxApi(params: OcrBoxRequest) {
|
||||
return httpPost(`/ocr/box`, params)
|
||||
}
|
||||
|
||||
export function deleteFileApi(id) {
|
||||
export function deleteFileApi(subid) {
|
||||
return httpPost(`file/delete`, {
|
||||
id,
|
||||
subid,
|
||||
tableName: 'bf_file',
|
||||
bucketName: aliyunBucket,
|
||||
})
|
||||
@@ -56,3 +56,19 @@ export function deleteAliyunFileApi(fileUrl) {
|
||||
mx_mapfilename: fileUrl,
|
||||
})
|
||||
}
|
||||
|
||||
interface SaveObjectParams {
|
||||
tableName: string
|
||||
keyField: string
|
||||
inserts: any[]
|
||||
updates: any[]
|
||||
deletes: any[]
|
||||
}
|
||||
|
||||
export function saveObjectApi(params: SaveObjectParams[]) {
|
||||
return httpPost('/data/saveObject', params)
|
||||
}
|
||||
|
||||
export function getUniqueIdApi() {
|
||||
return httpPost<number>('/data/getUniqueId')
|
||||
}
|
||||
@@ -4,9 +4,9 @@ PUBLIC_APP_NAME=g3hy2026
|
||||
|
||||
# Server
|
||||
# 应用部署基础路径(路由和静态资源 base)
|
||||
PUBLIC_SERVER_PATH=/g3hy2026/
|
||||
PUBLIC_SERVER_PATH=/
|
||||
# 本地开发服务器端口
|
||||
PUBLIC_SERVER_PORT=3000
|
||||
PUBLIC_SERVER_PORT=8080
|
||||
|
||||
# API
|
||||
# 后端 API 基础地址
|
||||
|
||||
+2
-12
@@ -13,11 +13,10 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Directory: typeof import('./src/components/wang-editor/modules/directory.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
@@ -27,20 +26,10 @@ declare module 'vue' {
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSplitter: typeof import('element-plus/es')['ElSplitter']
|
||||
ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElText: typeof import('element-plus/es')['ElText']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
G3Column: typeof import('./src/components/g3-table/g3-column.vue')['default']
|
||||
@@ -50,6 +39,7 @@ declare module 'vue' {
|
||||
G3GridGroup: typeof import('./src/components/g3-grid/g3-grid-group.vue')['default']
|
||||
G3GridItem: typeof import('./src/components/g3-grid/g3-grid-item.vue')['default']
|
||||
G3Icon: typeof import('./src/components/g3-icon/index.vue')['default']
|
||||
G3Query: typeof import('./src/components/g3-query/index.vue')['default']
|
||||
G3RecursiveColumn: typeof import('./src/components/g3-table/g3-recursive-column.vue')['default']
|
||||
G3Select: typeof import('./src/components/g3-select/index.vue')['default']
|
||||
G3TableView: typeof import('./src/components/g3-table/g3-table-view.vue')['default']
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"axios": "^1.13.6",
|
||||
"dayjs": "^1.11.20",
|
||||
"element-plus": "^2.13.3",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash": "^4.17.23",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
|
||||
+620
File diff suppressed because it is too large.
Load diff
@@ -32,7 +32,7 @@ import { loadDataApi, saveObjectKeysApi, deleteFileApi } from "@/request/api";
|
||||
|
||||
const props = defineProps({
|
||||
father: {
|
||||
type: String,
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
moduleId: {
|
||||
@@ -56,6 +56,8 @@ const props = defineProps({
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(["files-deleted"]);
|
||||
const treeRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
|
||||
@@ -206,6 +208,8 @@ watch(
|
||||
if (newCategories && newCategories.length > 0) {
|
||||
categories.value[0].children.push(...newCategories);
|
||||
}
|
||||
|
||||
console.log(categories.value);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -349,11 +353,7 @@ async function uploadLocalFiles(files) {
|
||||
const tempUrl = URL.createObjectURL(blob);
|
||||
|
||||
if (file.size > 512000) {
|
||||
const {
|
||||
url,
|
||||
size,
|
||||
blob: compressedBlob,
|
||||
} = await compressImage(tempUrl);
|
||||
const { url, size, blob: compressedBlob } = await compressImage(tempUrl);
|
||||
|
||||
fileInfo.mx_mapfilename = url;
|
||||
fileInfo.mx_filesize = size;
|
||||
@@ -498,9 +498,7 @@ async function handleDownloadFile(file) {
|
||||
function handleShowPreview(file) {
|
||||
console.log(file);
|
||||
if (isImage(file.mx_fileext)) {
|
||||
const index = previewImageList.value.findIndex(
|
||||
(item) => item === file.mx_mapfilename,
|
||||
);
|
||||
const index = previewImageList.value.findIndex((item) => item === file.mx_mapfilename);
|
||||
|
||||
if (index !== -1) {
|
||||
preViewImageIndex.value = index;
|
||||
@@ -513,10 +511,7 @@ function handleShowPreview(file) {
|
||||
|
||||
if (isExcel(file.mx_fileext)) {
|
||||
// https://view.officeapps.live.com/op/view.aspx?src=
|
||||
window.open(
|
||||
`https://view.officeapps.live.com/op/view.aspx?src=${fileUrl}`,
|
||||
"_blank",
|
||||
);
|
||||
window.open(`https://view.officeapps.live.com/op/view.aspx?src=${fileUrl}`, "_blank");
|
||||
} else {
|
||||
window.open(fileUrl, "_blank");
|
||||
}
|
||||
@@ -526,9 +521,7 @@ function handleShowPreview(file) {
|
||||
function handleDownloadPreviewImage(index) {
|
||||
const previewFileUrl = previewImageList.value[index];
|
||||
|
||||
const previewFile = fileList.value.find(
|
||||
(item) => item.mx_mapfilename === previewFileUrl,
|
||||
);
|
||||
const previewFile = fileList.value.find((item) => item.mx_mapfilename === previewFileUrl);
|
||||
|
||||
if (previewFile) {
|
||||
handleDownloadFile(previewFile);
|
||||
@@ -573,6 +566,8 @@ async function deleteFiles(files) {
|
||||
}
|
||||
}
|
||||
|
||||
emits("files-deleted", files);
|
||||
|
||||
ElMessage.success("文件删除成功");
|
||||
done();
|
||||
} catch (error) {
|
||||
@@ -598,12 +593,12 @@ async function handleSave() {
|
||||
loading.value = true;
|
||||
loadingTip.value = "正在保存中 . . .";
|
||||
|
||||
const { insertdata, updatedata, deletedata, hasChanges } =
|
||||
compareAndClassifyData(fileList.value, oldFileList.value, "subid", [
|
||||
"_content",
|
||||
"_status",
|
||||
"checked",
|
||||
]);
|
||||
const { insertdata, updatedata, deletedata, hasChanges } = compareAndClassifyData(
|
||||
fileList.value,
|
||||
oldFileList.value,
|
||||
"subid",
|
||||
["_content", "_status", "checked"],
|
||||
);
|
||||
|
||||
// 处理新增的文件
|
||||
for (const item of insertdata) {
|
||||
@@ -661,6 +656,8 @@ function onClosed() {
|
||||
loading.value = false;
|
||||
|
||||
loadingTip.value = "正在加载中 . . .";
|
||||
|
||||
cateId.value = "";
|
||||
}
|
||||
|
||||
function handleTreeNodeClick(data) {
|
||||
@@ -690,9 +687,7 @@ function handleTreeNodeClick(data) {
|
||||
<div class="g3-title">
|
||||
附件管理({{ fileList.length }})
|
||||
|
||||
<template v-if="!props.readonly">
|
||||
未上传 ({{ pendingFileList.length }})
|
||||
</template>
|
||||
<template v-if="!props.readonly"> 未上传 ({{ pendingFileList.length }}) </template>
|
||||
</div>
|
||||
</template>
|
||||
<input
|
||||
@@ -792,11 +787,7 @@ function handleTreeNodeClick(data) {
|
||||
</div>
|
||||
|
||||
<div v-else-if="displayMode === 0" class="g3-files">
|
||||
<div
|
||||
v-for="(file, index) in fileList"
|
||||
:key="index"
|
||||
class="g3-file-item"
|
||||
>
|
||||
<div v-for="(file, index) in fileList" :key="index" class="g3-file-item">
|
||||
<div class="g3-file-checkbox">
|
||||
<el-checkbox v-model="file._checked" size="large" />
|
||||
</div>
|
||||
@@ -808,15 +799,9 @@ function handleTreeNodeClick(data) {
|
||||
<span v-if="file._status === 'pending'">未上传</span>
|
||||
<span v-if="file._status === 'failed'">上传失败</span>
|
||||
</div>
|
||||
<div
|
||||
class="g3-file-preview"
|
||||
@click.stop="handleShowPreview(file)"
|
||||
>
|
||||
<div class="g3-file-preview" @click.stop="handleShowPreview(file)">
|
||||
<template v-if="getFileIcon(file.mx_fileext) === 'image'">
|
||||
<el-image
|
||||
class="g3-file-preview-img"
|
||||
:src="file.mx_mapfilename"
|
||||
/>
|
||||
<el-image class="g3-file-preview-img" :src="file.mx_mapfilename" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<G3Icon :icon="getFileIcon(file.mx_fileext)" size="88" />
|
||||
@@ -832,15 +817,9 @@ function handleTreeNodeClick(data) {
|
||||
<div>{{ formatFileSize(file.mx_filesize) }}</div>
|
||||
<div>
|
||||
{{ file.mx_updateuser_id || file.mx_inputuser_id }}
|
||||
<template
|
||||
v-if="file.mx_inputdatetime || file.mx_updatedatetime"
|
||||
>
|
||||
<template v-if="file.mx_inputdatetime || file.mx_updatedatetime">
|
||||
|
|
||||
{{
|
||||
formatTime(
|
||||
file.mx_updatedatetime || file.mx_inputdatetime,
|
||||
)
|
||||
}}
|
||||
{{ formatTime(file.mx_updatedatetime || file.mx_inputdatetime) }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -862,17 +841,8 @@ function handleTreeNodeClick(data) {
|
||||
>
|
||||
<G3Icon icon="lucide:download" />
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
v-if="!props.readonly"
|
||||
placement="top"
|
||||
trigger="click"
|
||||
>
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
circle
|
||||
style="margin-left: 12px"
|
||||
>
|
||||
<el-dropdown v-if="!props.readonly" placement="top" trigger="click">
|
||||
<el-button type="info" size="small" circle style="margin-left: 12px">
|
||||
<G3Icon icon="lucide:ellipsis" />
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
@@ -912,28 +882,14 @@ function handleTreeNodeClick(data) {
|
||||
<el-checkbox v-model="row._checked" size="large" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="mx_filename"
|
||||
title="文件名"
|
||||
sortable
|
||||
align="center"
|
||||
>
|
||||
<vxe-column field="mx_filename" title="文件名" sortable align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click.stop="handleShowPreview(row)"
|
||||
>
|
||||
<el-button type="primary" link @click.stop="handleShowPreview(row)">
|
||||
{{ row.mx_filename }}
|
||||
</el-button>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="mx_inputdatetime"
|
||||
title="上传日期"
|
||||
sortable
|
||||
align="center"
|
||||
>
|
||||
<vxe-column field="mx_inputdatetime" title="上传日期" sortable align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatTime(row.mx_inputdatetime) }}
|
||||
</template>
|
||||
@@ -974,26 +930,14 @@ function handleTreeNodeClick(data) {
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click.stop="handleDownloadFile(row)"
|
||||
>
|
||||
<el-button type="primary" link @click.stop="handleDownloadFile(row)">
|
||||
下载
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
v-if="!props.readonly"
|
||||
placement="top"
|
||||
trigger="click"
|
||||
>
|
||||
<el-button type="info" link style="margin-left: 12px">
|
||||
更多
|
||||
</el-button>
|
||||
<el-dropdown v-if="!props.readonly" placement="top" trigger="click">
|
||||
<el-button type="info" link style="margin-left: 12px"> 更多 </el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
@click="handleShowUpdateDialog(row)"
|
||||
>
|
||||
<el-dropdown-item @click="handleShowUpdateDialog(row)">
|
||||
修改文件信息
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
@@ -1029,12 +973,9 @@ function handleTreeNodeClick(data) {
|
||||
:url-list="previewImageList"
|
||||
:initial-index="preViewImageIndex"
|
||||
hide-on-click-modal
|
||||
preview-teleported
|
||||
@close="showPreview = false"
|
||||
>
|
||||
<template
|
||||
#toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
|
||||
>
|
||||
<template #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }">
|
||||
<ElIcon @click="prev">
|
||||
<Back />
|
||||
</ElIcon>
|
||||
@@ -1047,16 +988,10 @@ function handleTreeNodeClick(data) {
|
||||
<ElIcon @click="actions('zoomOut')">
|
||||
<ZoomOut />
|
||||
</ElIcon>
|
||||
<ElIcon
|
||||
@click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })"
|
||||
>
|
||||
<ElIcon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
|
||||
<ZoomIn />
|
||||
</ElIcon>
|
||||
<ElIcon
|
||||
@click="
|
||||
actions('clockwise', { rotateDeg: 180, enableTransition: false })
|
||||
"
|
||||
>
|
||||
<ElIcon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
|
||||
<RefreshRight />
|
||||
</ElIcon>
|
||||
<ElIcon @click="actions('anticlockwise')">
|
||||
@@ -1086,11 +1021,7 @@ function handleTreeNodeClick(data) {
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showUpdateDialog = false"> 取消 </el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="updateLoading"
|
||||
@click="handleConfirmUpdate"
|
||||
>
|
||||
<el-button type="primary" :loading="updateLoading" @click="handleConfirmUpdate">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="js" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class=""></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
@@ -23,9 +23,9 @@ const menuList = ref([
|
||||
icon: "lucide:house",
|
||||
},
|
||||
{
|
||||
label: "业务管理",
|
||||
icon: "lucide:briefcase-business",
|
||||
children: [],
|
||||
label: "集装箱管理",
|
||||
icon: "lucide:box",
|
||||
path: "/container",
|
||||
},
|
||||
{
|
||||
label: "系统管理",
|
||||
|
||||
@@ -22,6 +22,14 @@ export const routes = [
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/container",
|
||||
component: () => import("@/views/container/index.vue"),
|
||||
meta: {
|
||||
title: "集装箱管理",
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
// ==================== 系统管理 ====================
|
||||
{
|
||||
path: "/system/field",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { updateColumnsApi } from "@/request/api";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { formatToDate, formatToDateTime } from "./time";
|
||||
|
||||
export function updateEditTableColumns(tableRef) {
|
||||
const $table = unref(tableRef);
|
||||
@@ -39,19 +40,24 @@ export function updateViewTableColumns(tableRef) {
|
||||
}
|
||||
|
||||
const columns = $table.getColumns();
|
||||
console.log(columns);
|
||||
|
||||
if (!$table || $table.length === 0) {
|
||||
ElMessage.warning("未获取到栏位信息");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const newColumns = columns
|
||||
.filter((item) => item.params.b_id !== undefined)
|
||||
.filter((item) => item.type === undefined && item.params && item.params.b_id)
|
||||
.map((item, index) => ({
|
||||
b_id: item.params.b_id,
|
||||
col_Width: item.renderWidth,
|
||||
col_Position: index,
|
||||
}));
|
||||
|
||||
console.log(newColumns);
|
||||
|
||||
updateColumnsApi(newColumns).then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("保存成功");
|
||||
@@ -103,9 +109,11 @@ export function formatValueByFormat(value, format) {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
}).format(num);
|
||||
} else if (fmt.startsWith("date")) {
|
||||
} else if (fmt === "date") {
|
||||
return formatToDate(value);
|
||||
} else if (fmt.startsWith("number")) {
|
||||
} else if (fmt === "datetime") {
|
||||
return formatToDateTime(value);
|
||||
} else if (fmt === "number") {
|
||||
return Number(value) || 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { getAliyunKeyApi, insertFileApi } from "@/request/api";
|
||||
import { saveAs } from "file-saver";
|
||||
import JSZip from "jszip";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
/**
|
||||
* 选择并压缩图片
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
<script lang="js" setup>
|
||||
import {
|
||||
deleteFileApi,
|
||||
loadDataApi,
|
||||
loadPageDataApi,
|
||||
loadViewColumnsApi,
|
||||
saveObjectApi,
|
||||
} from "@/request/api";
|
||||
import { getAlignByFormat, formatValueByFormat } from "@/utils/columns";
|
||||
import { updateViewTableColumns } from "@/utils/columns";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const tableRef = ref();
|
||||
|
||||
const columns = ref([]);
|
||||
const tableData = ref([]);
|
||||
|
||||
const showSearch = ref(true);
|
||||
const loading = reactive({
|
||||
table: true,
|
||||
});
|
||||
|
||||
const searchForm = ref({
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
containerNo: "",
|
||||
username: "",
|
||||
});
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
size: 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const fileCategories = [
|
||||
{
|
||||
label: "集装箱号",
|
||||
cateId: "0",
|
||||
},
|
||||
{
|
||||
label: "其它照片",
|
||||
cateId: "1",
|
||||
},
|
||||
];
|
||||
|
||||
const viewName = "v_container";
|
||||
|
||||
const showPreview = ref(false);
|
||||
const previewIndex = ref(-1);
|
||||
const previewImageList = computed(() => {
|
||||
return tableData.value.map((item) => item.b_container_img).filter((img) => img);
|
||||
});
|
||||
|
||||
const currentRow = ref({});
|
||||
const showFile = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
loading.table = true;
|
||||
|
||||
await loadTableColumns();
|
||||
await loadTableDatas();
|
||||
|
||||
loading.table = false;
|
||||
});
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pagination.size = val;
|
||||
|
||||
loadTableDatas();
|
||||
};
|
||||
const handleCurrentChange = (val) => {
|
||||
pagination.page = val;
|
||||
|
||||
loadTableDatas();
|
||||
};
|
||||
|
||||
async function loadTableColumns() {
|
||||
const columnRes = await loadViewColumnsApi(viewName);
|
||||
|
||||
if (columnRes.success) {
|
||||
columns.value = columnRes.data;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTableDatas() {
|
||||
loading.table = true;
|
||||
|
||||
let searchCondition = "";
|
||||
|
||||
if (searchForm.value.startTime) {
|
||||
searchCondition += `b_inputdatetime >= '${searchForm.value.startTime}' AND `;
|
||||
}
|
||||
|
||||
if (searchForm.value.endTime) {
|
||||
searchCondition += `b_inputdatetime <= '${searchForm.value.endTime}' AND `;
|
||||
}
|
||||
|
||||
if (searchForm.value.containerNo) {
|
||||
searchCondition += `b_container_no LIKE '%${searchForm.value.containerNo}%' AND `;
|
||||
}
|
||||
|
||||
if (searchForm.value.username) {
|
||||
searchCondition += `v_username LIKE '%${searchForm.value.username}%' AND `;
|
||||
}
|
||||
|
||||
if (searchCondition.endsWith(" AND ")) {
|
||||
searchCondition = searchCondition.slice(0, -4);
|
||||
}
|
||||
|
||||
await loadPageDataApi({
|
||||
viewName,
|
||||
page: pagination.page,
|
||||
size: pagination.size,
|
||||
searchCondition,
|
||||
orderField: "b_inputdatetime DESC",
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
tableData.value = res.data.rows;
|
||||
pagination.total = res.data.total;
|
||||
|
||||
loading.table = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 删除单行
|
||||
async function handleDeleteRow(row) {
|
||||
if (!row) return;
|
||||
const $table = tableRef.value;
|
||||
|
||||
if ($table) {
|
||||
deleteRows([row]);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除多选行
|
||||
async function handleDeleteRows() {
|
||||
const $table = tableRef.value;
|
||||
if ($table) {
|
||||
deleteRows($table.getCheckboxRecords());
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRows(rows = []) {
|
||||
const $table = tableRef.value;
|
||||
|
||||
if (!$table) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rows.length) {
|
||||
ElMessage.warning("请先选择要删除的行");
|
||||
return;
|
||||
}
|
||||
|
||||
const containerNos = rows.map((row) => row.b_container_no);
|
||||
const deleteIds = rows.map((row) => row.b_id);
|
||||
|
||||
const tipMessage = `确定要删除以下箱号记录吗?<br/><strong>${containerNos.join(", ")}</strong>`;
|
||||
|
||||
await ElMessageBox.confirm(tipMessage, "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
dangerouslyUseHTMLString: true,
|
||||
});
|
||||
|
||||
if (deleteIds.length === 0) {
|
||||
ElMessage.success("删除成功");
|
||||
loadTableDatas();
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保 deleteIds 数组中的值是数字,防止 SQL 注入
|
||||
const safeDeleteIds = deleteIds.map((id) => Number(id));
|
||||
|
||||
// 使用安全的 SQL 查询
|
||||
const fileRes = await loadDataApi("bf_file", `father IN (${safeDeleteIds.join(",")})`, "", [
|
||||
"subid",
|
||||
]);
|
||||
|
||||
if (!fileRes.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of fileRes.data) {
|
||||
await deleteFileApi(file.subid);
|
||||
}
|
||||
|
||||
const requestData = [
|
||||
{
|
||||
tableName: "b_container",
|
||||
keyField: "b_id",
|
||||
inserts: [],
|
||||
updates: [],
|
||||
deletes: deleteIds.map((id) => ({ b_id: id })),
|
||||
},
|
||||
];
|
||||
|
||||
await saveObjectApi(requestData).then((res) => {
|
||||
if (res.success) {
|
||||
ElMessage.success("删除成功");
|
||||
loadTableDatas();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1;
|
||||
|
||||
loadTableDatas();
|
||||
}
|
||||
|
||||
function handleSearchReset() {
|
||||
searchForm.value.startTime = "";
|
||||
searchForm.value.endTime = "";
|
||||
searchForm.value.containerNo = "";
|
||||
searchForm.value.username = "";
|
||||
}
|
||||
|
||||
function handlePreviewShow(imgUrl) {
|
||||
const index = previewImageList.value.indexOf(imgUrl);
|
||||
|
||||
if (index !== -1) {
|
||||
previewIndex.value = index;
|
||||
showPreview.value = true;
|
||||
} else {
|
||||
showPreview.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePreviewClose() {
|
||||
showPreview.value = false;
|
||||
previewIndex.value = -1;
|
||||
}
|
||||
|
||||
function handleFileShow(data) {
|
||||
currentRow.value = data;
|
||||
|
||||
showFile.value = true;
|
||||
}
|
||||
|
||||
function handleDownloadOne(data) {
|
||||
console.log(data);
|
||||
// 获取
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="g3-container">
|
||||
<div v-if="showSearch" class="g3-search">
|
||||
<G3Grid class="grid">
|
||||
<G3GridGroup :label-width="80" :column="2">
|
||||
<G3GridItem label="上传日期" :span="2">
|
||||
<el-date-picker
|
||||
type="datetime"
|
||||
placeholder="开始日期"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
v-model="searchForm.startTime"
|
||||
/>
|
||||
<el-divider direction="vertical" />
|
||||
<el-date-picker
|
||||
type="datetime"
|
||||
placeholder="结束日期"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
v-model="searchForm.endTime"
|
||||
/>
|
||||
</G3GridItem>
|
||||
<G3GridItem label="箱号">
|
||||
<el-input v-model="searchForm.containerNo" />
|
||||
</G3GridItem>
|
||||
|
||||
<G3GridItem label="操作员">
|
||||
<el-input v-model="searchForm.username" />
|
||||
</G3GridItem>
|
||||
</G3GridGroup>
|
||||
</G3Grid>
|
||||
|
||||
<div class="search-extra">
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:search" />
|
||||
</el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
|
||||
<el-button @click="handleSearchReset">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:refresh-ccw" />
|
||||
</el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="g3-content">
|
||||
<div class="content-top">
|
||||
<div>
|
||||
<el-button type="primary">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:cloud-download" />
|
||||
</el-icon>
|
||||
批量下载
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleDeleteRows">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:trash-2" />
|
||||
</el-icon>
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip content="显示/关闭 搜索">
|
||||
<el-button circle @click="showSearch = !showSearch">
|
||||
<el-icon>
|
||||
<G3Icon icon="lucide:search" />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="刷新">
|
||||
<el-button circle>
|
||||
<el-icon>
|
||||
<G3Icon icon="lucide:refresh-ccw" />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-dropdown>
|
||||
<el-button circle style="margin-left: 12px">
|
||||
<el-icon>
|
||||
<G3Icon icon="lucide:ellipsis" />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="updateViewTableColumns(tableRef)">
|
||||
栏位保存
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>导出Execl</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="g3-table">
|
||||
<vxe-table
|
||||
ref="tableRef"
|
||||
:loading="loading.table"
|
||||
:data="tableData"
|
||||
:cell-config="{ height: 100 }"
|
||||
:column-config="{
|
||||
resizable: true,
|
||||
drag: true,
|
||||
}"
|
||||
:row-config="{
|
||||
useKey: true,
|
||||
isCurrent: true,
|
||||
isHover: true,
|
||||
}"
|
||||
:filter-config="{
|
||||
showIcon: false,
|
||||
}"
|
||||
:column-drag-config="{
|
||||
showIcon: false,
|
||||
trigger: 'cell',
|
||||
}"
|
||||
height="100%"
|
||||
>
|
||||
<vxe-column field="seq" type="seq" width="50" align="center" title=" " fixed="left" />
|
||||
<vxe-column type="checkbox" width="50" align="center" fixed="left" />
|
||||
<vxe-column
|
||||
v-for="column in columns"
|
||||
:key="column.col_FieldName"
|
||||
:field="column.col_FieldName"
|
||||
:title="column.col_Caption"
|
||||
:width="column.col_Width"
|
||||
align="center"
|
||||
:params="column"
|
||||
:sortable="column.col_FieldName !== 'b_container_img'"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<template v-if="column.col_FieldName === 'b_container_img'">
|
||||
<el-image
|
||||
style="width: 100px; height: 100px; cursor: pointer"
|
||||
:src="row.b_container_img"
|
||||
@click="handlePreviewShow(row.b_container_img)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatValueByFormat(row[column.col_FieldName], column.col_DisplayFormat) }}
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column width="220" align="center" fixed="right" title="操作">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleDownloadOne(row)">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:cloud-download" />
|
||||
</el-icon>
|
||||
下载
|
||||
</el-button>
|
||||
<el-button link type="primary" @click="handleFileShow(row)">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:file-image" />
|
||||
</el-icon>
|
||||
图片
|
||||
</el-button>
|
||||
<el-button link type="danger" @click="handleDeleteRow(row)">
|
||||
<el-icon class="el-icon--left">
|
||||
<G3Icon icon="lucide:trash-2" />
|
||||
</el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
|
||||
<div class="g3-pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.size"
|
||||
:page-sizes="[20, 50, 100, 200]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-image-viewer
|
||||
v-if="showPreview"
|
||||
:url-list="previewImageList"
|
||||
show-progress
|
||||
:initial-index="previewIndex"
|
||||
@close="handlePreviewClose"
|
||||
/>
|
||||
|
||||
<G3File
|
||||
v-model="showFile"
|
||||
:father="currentRow.b_id"
|
||||
module-id="b_container"
|
||||
:categories="fileCategories"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.g3-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.g3-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.grid {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.search-extra {
|
||||
display: flex;
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.g3-content {
|
||||
flex-grow: 1;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.content-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
::v-deep(.el-button) {
|
||||
height: 32px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.g3-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.g3-pagination {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -24,9 +24,7 @@ const isCollapse = ref(false);
|
||||
|
||||
const showSql = ref(false);
|
||||
|
||||
const treeData = [
|
||||
{ b_id: "root", mc_b: "全部分类", isLeaf: false, children: [] },
|
||||
];
|
||||
const treeData = [{ b_id: "root", mc_b: "全部分类", isLeaf: false, children: [] }];
|
||||
|
||||
const treeProps = {
|
||||
children: "children",
|
||||
@@ -344,18 +342,15 @@ async function handleTreeLoad(node, resolve) {
|
||||
}
|
||||
|
||||
async function handleTreeNodeClick(nodeData) {
|
||||
console.log("===node===")
|
||||
console.log(nodeData)
|
||||
console.log("===node===");
|
||||
console.log(nodeData);
|
||||
if (!nodeData || nodeData.b_id === "root" || !nodeData.b_moduleid) return;
|
||||
|
||||
currentNode.value = nodeData;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const res = await loadDataApi(
|
||||
"s_module",
|
||||
`b_id = '${nodeData.b_moduleid}'`,
|
||||
);
|
||||
const res = await loadDataApi("s_module", `b_id = '${nodeData.b_moduleid}'`);
|
||||
|
||||
if (res.success) {
|
||||
currentModule.value = res.data[0] || null;
|
||||
@@ -493,17 +488,10 @@ function handleCheckColumns() {
|
||||
<div class="content">
|
||||
<div class="content-head">
|
||||
<div class="content-head-left">
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!isSelectedNode"
|
||||
@click="handleCheckColumns"
|
||||
>检查</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!isSelectedNode"
|
||||
@click="showPreview = true"
|
||||
>
|
||||
<el-button type="primary" :disabled="!isSelectedNode" @click="handleCheckColumns">
|
||||
检查
|
||||
</el-button>
|
||||
<el-button type="primary" :disabled="!isSelectedNode" @click="showPreview = true">
|
||||
浏览栏位预览
|
||||
</el-button>
|
||||
<el-button
|
||||
@@ -524,18 +512,10 @@ function handleCheckColumns() {
|
||||
>
|
||||
属性预览
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!isSelectedRow"
|
||||
@click="showTag = !showTag"
|
||||
>
|
||||
<el-button type="primary" :disabled="!isSelectedRow" @click="showTag = !showTag">
|
||||
修改标签
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!isSelectedNode"
|
||||
@click="showSql = !showSql"
|
||||
>
|
||||
<el-button type="primary" :disabled="!isSelectedNode" @click="showSql = !showSql">
|
||||
SQL语句
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -643,21 +623,11 @@ function handleCheckColumns() {
|
||||
</el-splitter-panel>
|
||||
</el-splitter>
|
||||
|
||||
<PreviewSql
|
||||
v-model="showSql"
|
||||
:data="tableData"
|
||||
:view-name="currentModule?.b_ViewTable"
|
||||
/>
|
||||
<PreviewSql v-model="showSql" :data="tableData" :view-name="currentModule?.b_ViewTable" />
|
||||
|
||||
<PreviewEdit
|
||||
v-model="showEditPreview"
|
||||
:view-name="currentModule?.b_ViewTable"
|
||||
/>
|
||||
<PreviewEdit v-model="showEditPreview" :view-name="currentModule?.b_ViewTable" />
|
||||
|
||||
<PreviewView
|
||||
v-model="showPreview"
|
||||
:view-name="currentModule?.b_ViewTable"
|
||||
/>
|
||||
<PreviewView v-model="showPreview" :view-name="currentModule?.b_ViewTable" />
|
||||
|
||||
<TagUpdate v-model="showTag" :data="currentRow" />
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,8 @@ watch(
|
||||
() => props.node,
|
||||
(val) => {
|
||||
if (val) {
|
||||
console.log(val)
|
||||
|
||||
if (props.mode == "add") {
|
||||
isDisabled.value = false;
|
||||
if (val.id != "all") {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
<script lang="js" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { diffObject } from "@/utils/diff";
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@@ -15,6 +15,7 @@ const emit = defineEmits(["update:visible", "submit"]);
|
||||
|
||||
// Local state
|
||||
const form = ref({});
|
||||
const oldForm = ref({});
|
||||
const visible = ref(props.visible);
|
||||
const isDisabled = ref(false); // 控制是否禁用
|
||||
|
||||
@@ -33,10 +34,12 @@ watch(
|
||||
if (props.mode == "add") {
|
||||
isDisabled.value = false;
|
||||
form.value = val;
|
||||
oldForm.value = JSON.parse(JSON.stringify(val));
|
||||
}
|
||||
if (props.mode == "edit") {
|
||||
isDisabled.value = true;
|
||||
form.value = val;
|
||||
oldForm.value = JSON.parse(JSON.stringify(val));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -49,7 +52,14 @@ const handleSubmit = () => {
|
||||
ElMessage.warning("模块代码、类别不能为空");
|
||||
return;
|
||||
}
|
||||
emit("submit", { ...form.value });
|
||||
|
||||
const diffRes = diffObject(form.value, oldForm.value, ["b_id"], []);
|
||||
|
||||
if (diffRes.isChanged) {
|
||||
diffRes.result.b_Cate_ID = form.value.b_Cate_ID;
|
||||
emit("submit", diffRes.result);
|
||||
}
|
||||
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
@@ -172,12 +182,7 @@ const resetForm = () => {
|
||||
</div>
|
||||
<div style="display: flex; margin-top: 3px; align-items: center">
|
||||
<label class="font-medium">顺序:</label>
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="form.b_Order"
|
||||
placeholder="请输入顺序"
|
||||
style="width: 374px"
|
||||
/>
|
||||
<el-input type="text" v-model="form.b_Order" placeholder="请输入顺序" style="width: 374px" />
|
||||
</div>
|
||||
<div style="display: flex; margin-top: 3px; align-items: center">
|
||||
<label class="font-medium">编辑路径:</label>
|
||||
@@ -191,12 +196,7 @@ const resetForm = () => {
|
||||
<div style="display: flex; margin-top: 3px; align-items: center">
|
||||
<div style="display: flex; margin-top: 3px; align-items: center">
|
||||
<label class="font-medium">主模块</label>
|
||||
<el-checkbox
|
||||
v-model="form.b_isMainModule"
|
||||
:true-label="1"
|
||||
:false-label="0"
|
||||
size="large"
|
||||
/>
|
||||
<el-checkbox v-model="form.b_isMainModule" :true-label="1" :false-label="0" size="large" />
|
||||
</div>
|
||||
<div style="display: flex; margin-top: 3px; align-items: center">
|
||||
<label class="font-medium">虚拟</label>
|
||||
@@ -209,21 +209,11 @@ const resetForm = () => {
|
||||
</div>
|
||||
<div style="display: flex; margin-top: 3px; align-items: center">
|
||||
<label class="font-medium">自动编码</label>
|
||||
<el-checkbox
|
||||
v-model="form.b_autoid"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
size="large"
|
||||
/>
|
||||
<el-checkbox v-model="form.b_autoid" :true-value="1" :false-value="0" size="large" />
|
||||
</div>
|
||||
<div style="display: flex; margin-top: 3px; align-items: center">
|
||||
<label class="font-medium">审核中心</label>
|
||||
<el-checkbox
|
||||
v-model="form.b_SH"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
size="large"
|
||||
/>
|
||||
<el-checkbox v-model="form.b_SH" :true-value="1" :false-value="0" size="large" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -138,20 +138,12 @@ function onClosed() {
|
||||
|
||||
<div class="form-item">
|
||||
<div class="label">发布</div>
|
||||
<el-switch
|
||||
v-model="form.b_visible"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
/>
|
||||
<el-switch v-model="form.b_visible" active-value="1" inactive-value="0" />
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<div class="label">公告内容</div>
|
||||
<WangEditor
|
||||
v-model="form.b_info"
|
||||
:father="form.b_id"
|
||||
module-id="oa_notices"
|
||||
/>
|
||||
<WangEditor v-model="form.b_info" :father="form.b_id" module-id="oa_notices" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -165,9 +157,7 @@ function onClosed() {
|
||||
|
||||
<div>
|
||||
<el-button @click="showVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="loading.save" @click="handleSave"
|
||||
>确认</el-button
|
||||
>
|
||||
<el-button type="primary" :loading="loading.save" @click="handleSave"> 确认 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,6 +20,7 @@ import Employee from "./employee.vue";
|
||||
background-color: #fff;
|
||||
:deep(.el-tabs__content) {
|
||||
overflow-y: auto;
|
||||
padding: 6px 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user