This commit is contained in:
oneao committed 2026-03-27 17:22:22 +08:00
1 parent 26721a142e
commit 840d33ef3b
32 files changed
+2067 -589

No files matched your search

@@ -99,6 +99,56 @@ public class AuthController {
}
@PostMapping("/loginPhone")
public Result<Map<String,Object>> loginPhone(@RequestBody Map<String,Object> params) {
String orgid = (String) params.get("orgid");
String userid = (String) params.get("userid");
if (StringUtils.isBlank(orgid)) {
return Result.fail("机构码不能为空");
}
if (StringUtils.isBlank(userid)) {
return Result.fail("账号不能为空");
}
try {
DataSourceManager.getOrCreateDataSource(orgid);
} catch (Exception e) {
return Result.fail("机构码错误");
}
DataSourceContextHolder.setDataSourceKey(orgid);
String sql = "SELECT b_id,b_name,b_password,b_canuse FROM b_user WHERE b_id = ?";
List<Map<String, Object>> list = dbUtils.loadData(sql, userid);
if (list.isEmpty()) {
return Result.fail("账号不存在");
}
Map<String, Object> user = list.get(0);
String canuse = Objects.toString(user.get("b_canuse"), "");
if (!canuse.equals("1")){
return Result.fail("账号未启用");
}
// 校验成功
String token = JwtUtils.createToken(new JwtPayload(orgid, userid));
Map<String,Object> map = new HashMap<>();
map.put("token",token);
map.put("username",Objects.toString(user.get("b_name"), ""));
asyncService.invokeAsyncLoginLog(userid);
return Result.ok(map);
}
@PostMapping("/updatePassword")
@Transactional
public Result<Map<String,Object>> updatePassword(@RequestBody Map<String,Object> params){
@@ -96,6 +96,34 @@ public class DataListController {
return Result.ok(result);
}
/**
* 分页查询接口
*
* @param params 参数集合,包含:
* - viewName: 视图名称(必填)
* - start: 页码(默认 1)
* - end: 每页条数(默认 10)
* - searchCondition: 查询条件
* - searchColumns: 查询列(未指定时默认查询所有列)
* - orderField: 排序字段
* @return 总页数
*/
@PostMapping("/range")
public Result<List<Map<String,Object>>> range(@RequestBody Map<String, Object> params){
ValidationUtils.checkRequiredFields(params, "viewName");
Integer start = (Integer) params.getOrDefault("start", 1);
Integer end = (Integer) params.getOrDefault("end", 10);
String viewName = (String) params.get("viewName");
String searchCondition = (String) params.get("searchCondition");
List<String> searchColumns = (List<String>) params.get("searchColumns");
String orderField = (String) params.get("orderField");
List<Map<String,Object>> result = dbUtils.range(start, end, viewName, searchCondition, orderField, searchColumns);
return Result.ok(result);
}
/**
* 树形数据查询接口
*
@@ -25,7 +25,7 @@ public class OcrController {
String fileUrl = (String) params.get("fileUrl");
String requestUrl = "http://192.168.1.137:8867/ocr";
String requestUrl = "http://127.0.0.1:8867/ocr";
Map<String, Object> reqPayload = new HashMap<>();
reqPayload.put("file", fileUrl);
@@ -208,6 +208,93 @@ public class DbUtils {
return result;
}
public List<Map<String, Object>> range(
int start,
int end,
String viewName,
String searchCondition,
String orderField,
List<String> searchColumns
) {
validateIdentifier(viewName);
validateIdentifier(orderField);
// ======================
// 参数兜底
// ======================
if (start < 1) start = 1;
if (end < 1) end = 1;
if (start > end) {
int temp = start;
start = end;
end = temp;
}
List<Object> params = new ArrayList<>();
// ======================
// SELECT 字段
// ======================
String selectSql;
String outerSelectSql;
if (searchColumns != null && !searchColumns.isEmpty()) {
searchColumns.forEach(this::validateIdentifier);
selectSql = searchColumns.stream()
.map(c -> "t." + c)
.collect(Collectors.joining(","));
outerSelectSql = searchColumns.stream()
.map(c -> "sub." + c)
.collect(Collectors.joining(","));
} else {
selectSql = "t.*";
outerSelectSql = "sub.*";
}
// ======================
// WHERE 条件
// ======================
StringBuilder whereSql = new StringBuilder("1=1");
if (StringUtils.isNotBlank(searchCondition)) {
whereSql.append(" AND ").append(searchCondition);
}
// ======================
// ORDER BY
// ======================
String orderSql;
if (StringUtils.isBlank(orderField)) {
orderSql = "(SELECT NULL)";
} else {
orderSql = "t." + orderField;
}
// ======================
// SQLServer 2008 区间查询
// ======================
String sql =
"SELECT " + outerSelectSql + " FROM (" +
" SELECT ROW_NUMBER() OVER (ORDER BY " + orderSql + ") AS row_num, " +
selectSql +
" FROM " + viewName + " t " +
" WHERE " + whereSql +
") sub WHERE sub.row_num BETWEEN ? AND ?";
params.add(start);
params.add(end);
validateFinalSql(sql);
return jdbcTemplate.queryForList(sql, params.toArray());
}
public void validateFinalSql(String sql) {
if (StringUtils.isBlank(sql)) {
return;
@@ -7,7 +7,7 @@ server:
threads:
max: 800 # 最大线程数
min-spare: 100 # 初始化的线程数
port: 8085
port: 80
spring:
servlet:
@@ -1,4 +1,10 @@
url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR
#url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR
#username=sa
#password=zhang520..
#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3BoxOCR
username=sa
password=zhang520..
password=123456@aaa
driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

+9 -2
View File
@@ -3,10 +3,17 @@ import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
import { loadTianDiTu } from '@/utils/tdl-loader'
import { useUserStore } from './store'
const userStore = useUserStore()
// const userStore = useUserStore()
onLaunch(() => {
userStore.requireLogin()
// userStore.requireLogin()
})
onLoad(async (options) => {
// options 会包含 orgid 和 bid
const { orgid, bid } = options
console.log(options)
})
onMounted(() => {
@@ -69,7 +69,7 @@
"vueVersion": "3",
"h5": {
"router": {
"base": "/g3cdjtd-app/"
"base": "/g3ocr-app/"
}
}
}
@@ -1,4 +1,4 @@
<script setup lang="ts">
<!-- <script setup lang="ts">
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
import { dialog, isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
import { aliyunpluploadApi, delSubRecordApi, loaddataAttachApi } from '@/request/api'
@@ -159,4 +159,17 @@ function onRemove(index: number, item: UploadFileItem) {
.file {
padding: 36rpx;
}
</style> -->
<script lang="ts" setup>
</script>
<template>
<view class=""></view>
</template>
<style lang="scss" scoped>
</style>
@@ -56,36 +56,11 @@ async function onSubmit() {
icon: 'success',
})
uni.switchTab({
uni.navigateTo({
// url: '/pages-sub/upload/index',
url: '/pages/home/index',
})
}
// if (!res?.data) {
// toast.fail('登录失败,请检查账号密码')
// return
// }
// userStore.userInfo = {
// ...userStore.userInfo,
// orgid: formModel.orgid,
// userid: formModel.userid,
// token: res.data,
// }
// userStore.userInfo = {
// ...userStore.userInfo,
// username,
// }
// uni.showToast({
// title: '登录成功',
// icon: 'success',
// })
// uni.switchTab({
// url: '/pages/home/index',
// })
}
catch (err: any) {
// 表单验证错误
@@ -2,7 +2,8 @@
import type { FieldValidateError, FormExpose, FormRules } from 'sard-uniapp'
import { getWindowInfo, toast } from 'sard-uniapp'
import { reactive, ref } from 'vue'
import { loaddataApi, saveobjtApi } from '@/request/api'
import { loadDataApi, updatePasswordApi } from '@/request/api'
import { useUserStore } from '@/store'
const userStore = useUserStore()
@@ -33,46 +34,12 @@ async function onSubmit() {
submitLoading.value = true
const searchcondition = `b_id = '${userStore.userInfo.userid}' AND ISNULL(b_password, '') = ISNULL('${formModel.oldPassword}', '')`
const searchRes = await loaddataApi({
viewname: 'v_mini_loginid',
searchcondition: `${searchcondition}`,
orderfield: 'b_id',
soption: '',
const saveobjtRes = await updatePasswordApi({
oldPassword: formModel.oldPassword,
newPassword: formModel.newPassword,
})
if (!searchRes.data || searchRes.data.length === 0) {
toast.fail('旧密码错误,请重新输入')
return
}
const updateObj = {
b_id: userStore.userInfo.userid,
b_password: formModel.newPassword,
}
const tables = []
const insertValues = []
const updateValues = []
const delValues = []
const keys = []
tables.push('b_truck')
insertValues.push('[]')
updateValues.push(JSON.stringify([updateObj]))
delValues.push('[]')
keys.push('b_id')
const saveobjtRes = await saveobjtApi({
tables,
insertValues,
delValues,
updateValues,
keys,
})
if (saveobjtRes.data === 'OK') {
if (saveobjtRes.success) {
toast.success('密码修改成功,请重新登录')
setTimeout(() => {
uni.navigateTo({
@@ -210,7 +210,7 @@ async function handleConfirm() {
b_container_no: form.value.containerNo,
b_container_img: form.value.containerImageUrl,
b_ocr_no: form.value.ocrNo,
b_ocr_score: form.value.ocrScore,
b_ocr_score: form.value.ocrScore || 0.00,
}
const fileInserts = []
@@ -76,7 +76,7 @@ function deleteAliyunFiles() {
/>
</view>
<view v-if="userStore.isLogined" class="grid-container">
<sar-grid :columns="2" gap="24rpx">
<sar-grid :columns="1" gap="24rpx">
<sar-grid-item
@click="toLinkUrl('/pages-sub/upload/index')"
>
@@ -86,10 +86,6 @@ function deleteAliyunFiles() {
</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
@@ -72,3 +72,23 @@ export function saveObjectApi(params: SaveObjectParams[]) {
export function getUniqueIdApi() {
return httpPost<number>('/data/getUniqueId')
}
interface LoadDataParams {
viewName: string
searchCondition: string
orderField: string
searchColumns: string[]
}
export function loadDataApi(params: LoadDataParams) {
return httpPost<any[]>('/data/loadData', params)
}
interface UpdatePasswordParams {
oldPassword: string
newPassword: string
}
export function updatePasswordApi(params: UpdatePasswordParams) {
return httpPost(`/auth/updatePassword`, params)
}
@@ -18,7 +18,7 @@ function request<T = any>(option: UniApp.RequestOptions): Promise<IResData<T>> &
// H5:用 request 请求,带时间戳保证最新
const timestamp = Date.now()
uni.request({
url: `/g3cdjtd-app/static/db.json?t=${timestamp}`,
url: `/g3ocr-app/static/db.json?t=${timestamp}`,
method: 'GET',
success: r => res(r.data as Record<string, string>),
fail: err => rej(err),
@@ -1,3 +1,4 @@
{
"g3hd": "http://127.0.0.1:8085/api"
// "g3hd": "http://ser369.g3soft.cn:80/api",
"g3hd": "http://192.168.1.137:80/api"
}
-110
View File
@@ -1,110 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>集装箱录入</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100">
<!-- 顶部 -->
<div class="bg-white shadow-sm px-4 py-3 text-lg font-semibold">
集装箱录入
</div>
<div class="p-4 space-y-4">
<!-- Step 1 -->
<div class="bg-white rounded-2xl shadow p-4 space-y-4">
<!-- 标题 -->
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-full bg-blue-500 text-white text-sm flex items-center justify-center">
1
</div>
<div class="font-medium text-gray-800">
集装箱识别
</div>
</div>
<!-- 输入框 -->
<div>
<label class="text-sm text-gray-500">集装箱号</label>
<input
type="text"
placeholder="请输入或识别集装箱号"
class="w-full mt-1 border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
</div>
<!-- 拍照 -->
<div class="flex gap-3">
<div class="flex-1 border-2 border-dashed rounded-xl h-28 flex flex-col items-center justify-center text-gray-400">
📷
<div class="text-xs mt-1">拍照识别</div>
</div>
<div class="flex-1 border-2 border-dashed rounded-xl h-28 flex flex-col items-center justify-center text-gray-400">
🖼
<div class="text-xs mt-1">相册上传</div>
</div>
</div>
<!-- OCR结果 -->
<div class="bg-gray-50 rounded-lg p-3 text-sm text-gray-600">
OCR识别结果:<span class="text-gray-400">暂无</span>
</div>
</div>
<!-- Step 2 -->
<div class="bg-white rounded-2xl shadow p-4 space-y-4">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-full bg-green-500 text-white text-sm flex items-center justify-center">
2
</div>
<div class="font-medium text-gray-800">
其他照片录入
</div>
</div>
<!-- 图片列表 -->
<div class="grid grid-cols-3 gap-3">
<!-- 已上传 -->
<div class="relative">
<div class="h-24 bg-gray-200 rounded-lg"></div>
<div class="absolute top-1 right-1 bg-black/50 text-white text-xs px-1 rounded">
✓
</div>
</div>
<!-- 上传按钮 -->
<div class="h-24 border-2 border-dashed rounded-lg flex flex-col items-center justify-center text-gray-400">
➕
<div class="text-xs mt-1">添加</div>
</div>
</div>
<!-- 说明 -->
<div class="text-xs text-gray-400">
可上传箱体、封条、货物等相关照片
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="fixed bottom-0 left-0 right-0 bg-white border-t p-3">
<button class="w-full bg-blue-500 text-white py-3 rounded-xl text-lg">
提交
</button>
</div>
</body>
</html>
-18
View File
@@ -1,18 +0,0 @@
from paddleocr import PaddleOCR
# 初始化 PaddleOCR 实例
ocr = PaddleOCR(
use_doc_orientation_classify=False,
use_doc_unwarping=False,
use_textline_orientation=False,
text_det_limit_side_len=736, # 设置图像边长限制
)
# 对示例图像执行 OCR 推理
result = ocr.predict(
input="./8B457A96D0F016EAB6EB6D24AD70406C.jpg")
# 可视化结果并保存 json 结果
for res in result:
res.print()
res.save_to_img("output")
res.save_to_json("output")
+3 -3
View File
@@ -1,15 +1,15 @@
# Application
# 应用名称(用于标题、日志等)
PUBLIC_APP_NAME=g3hy2026
PUBLIC_APP_NAME=g3ocr
# Server
# 应用部署基础路径(路由和静态资源 base)
PUBLIC_SERVER_PATH=/
PUBLIC_SERVER_PATH=/g3ocr/
# 本地开发服务器端口
PUBLIC_SERVER_PORT=8080
# API
# 后端 API 基础地址
PUBLIC_API_BASE_URL=http://127.0.0.1:8085/api
PUBLIC_API_BASE_URL=http://ser369.g3soft.cn:80/api
PUBLIC_ALIYUN_BUCKET = g3hd
+2
View File
@@ -25,6 +25,8 @@
"nprogress": "^0.2.0",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"qrcode": "^1.5.4",
"qrcode.vue": "^3.8.0",
"uuid": "^13.0.0",
"vue": "^3.5.26",
"vue-router": "^5.0.3",
+207
View File
@@ -56,6 +56,12 @@ importers:
pinia-plugin-persistedstate:
specifier: ^4.7.1
version: 4.7.1(pinia@3.0.4(vue@3.5.30))
qrcode:
specifier: ^1.5.4
version: 1.5.4
qrcode.vue:
specifier: ^3.8.0
version: 3.8.0(vue@3.5.30)
uuid:
specifier: ^13.0.0
version: 13.0.0
@@ -918,6 +924,10 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -1020,6 +1030,10 @@ packages:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001778:
resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==}
@@ -1038,6 +1052,9 @@ packages:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1109,6 +1126,10 @@ packages:
supports-color:
optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
@@ -1124,6 +1145,9 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
@@ -1161,6 +1185,9 @@ packages:
peerDependencies:
vue: ^3.3.0
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emojis-list@3.0.0:
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
engines: {node: '>= 4'}
@@ -1254,6 +1281,10 @@ packages:
file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'}
@@ -1285,6 +1316,10 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -1355,6 +1390,10 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -1417,6 +1456,10 @@ packages:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'}
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
lodash-es@4.17.23:
resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==}
@@ -1580,12 +1623,28 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
package-manager-detector@1.6.0:
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
@@ -1635,6 +1694,10 @@ packages:
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
@@ -1652,6 +1715,16 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
qrcode.vue@3.8.0:
resolution: {integrity: sha512-+XKbSKvQu158zlHsJm+HObGUQM3Q+9Oq6yb2op/6lMM2gwOiiG9uIhujvrSV5UoTjrzR0BcNxCdW2kk7KE4NEg==}
peerDependencies:
vue: ^3.0.0
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
@@ -1676,6 +1749,13 @@ packages:
reduce-configs@1.1.1:
resolution: {integrity: sha512-EYtsVGAQarE8daT54cnaY1PIknF2VB78ug6Zre2rs36EsJfC40EG6hmTU2A2P1ZuXnKAt2KI0fzOGHcX7wzdPw==}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
@@ -1846,6 +1926,9 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
@@ -1876,12 +1959,20 @@ packages:
ssr-window@3.0.0:
resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
@@ -2056,9 +2147,16 @@ packages:
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
wildcard@1.1.2:
resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -2071,6 +2169,9 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -2079,6 +2180,14 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
zip-stream@4.1.1:
resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
engines: {node: '>= 10'}
@@ -2995,6 +3104,8 @@ snapshots:
acorn@8.16.0: {}
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -3128,6 +3239,8 @@ snapshots:
es-errors: 1.3.0
function-bind: 1.1.2
camelcase@5.3.1: {}
caniuse-lite@1.0.30001778: {}
chainsaw@0.1.0:
@@ -3148,6 +3261,12 @@ snapshots:
dependencies:
readdirp: 5.0.0
cliui@6.0.0:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -3205,6 +3324,8 @@ snapshots:
dependencies:
ms: 2.1.3
decamelize@1.2.0: {}
deepmerge@4.3.1: {}
defu@6.1.4: {}
@@ -3214,6 +3335,8 @@ snapshots:
detect-libc@2.1.2:
optional: true
dijkstrajs@1.0.3: {}
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
@@ -3275,6 +3398,8 @@ snapshots:
transitivePeerDependencies:
- typescript
emoji-regex@8.0.0: {}
emojis-list@3.0.0: {}
end-of-stream@1.4.5:
@@ -3371,6 +3496,11 @@ snapshots:
file-saver@2.0.5: {}
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
follow-redirects@1.15.11: {}
form-data@4.0.5:
@@ -3396,6 +3526,8 @@ snapshots:
gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -3472,6 +3604,8 @@ snapshots:
is-extglob@2.1.1:
optional: true
is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -3526,6 +3660,10 @@ snapshots:
pkg-types: 2.3.0
quansync: 0.2.11
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
lodash-es@4.17.23: {}
lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.23)(lodash@4.17.23):
@@ -3674,10 +3812,22 @@ snapshots:
'@oxfmt/binding-win32-ia32-msvc': 0.40.0
'@oxfmt/binding-win32-x64-msvc': 0.40.0
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
p-try@2.2.0: {}
package-manager-detector@1.6.0: {}
pako@1.0.11: {}
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
pathe@2.0.3: {}
@@ -3713,6 +3863,8 @@ snapshots:
exsolve: 1.0.8
pathe: 2.0.3
pngjs@5.0.0: {}
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
@@ -3727,6 +3879,16 @@ snapshots:
proxy-from-env@1.1.0: {}
qrcode.vue@3.8.0(vue@3.5.30):
dependencies:
vue: 3.5.30
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
quansync@0.2.11: {}
readable-stream@2.3.8:
@@ -3756,6 +3918,10 @@ snapshots:
reduce-configs@1.1.1: {}
require-directory@2.1.1: {}
require-main-filename@2.0.0: {}
resize-observer-polyfill@1.5.1: {}
rfdc@1.4.1: {}
@@ -3889,6 +4055,8 @@ snapshots:
semver@6.3.1: {}
set-blocking@2.0.0: {}
setimmediate@1.0.5: {}
slate-history@0.66.0(slate@0.72.8):
@@ -3912,6 +4080,12 @@ snapshots:
ssr-window@3.0.0: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
@@ -3920,6 +4094,10 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-literal@3.1.0:
dependencies:
js-tokens: 9.0.1
@@ -4117,8 +4295,16 @@ snapshots:
webpack-virtual-modules@0.6.2: {}
which-module@2.0.1: {}
wildcard@1.1.2: {}
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrappy@1.0.2: {}
xe-utils@3.9.1: {}
@@ -4127,10 +4313,31 @@ snapshots:
xmlchars@2.2.0: {}
y18n@4.0.3: {}
yallist@3.1.1: {}
yaml@2.8.2: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs@15.4.1:
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
zip-stream@4.1.1:
dependencies:
archiver-utils: 3.0.4
@@ -124,7 +124,6 @@ const gridInnerLabelStyle = computed(() => {
background-color: #f9f9f9;
border-right: 1px solid #ccc;
font-size: 14px;
height: 100%;
display: flex;
position: relative;
min-width: 0;
@@ -26,6 +26,11 @@ const menuList = ref([
icon: "lucide:box",
path: "/container",
},
{
label: "手机二维码",
icon: "lucide:scan-qr-code",
path: "/phone",
},
{
label: "系统管理",
icon: "lucide:settings",
@@ -87,6 +87,40 @@ export function loadPageDataApi(
);
}
/**
* 分页加载表格数据
* @param {Object} params 参数对象
* @param {number} params.start 当前页
* @param {number} params.end 每页条数
* @param {string} params.viewName 视图名称
* @param {string} [params.searchCondition] 搜索条件
* @param {string} [params.orderField] 排序字段
* @param {string[]} [params.searchColumns] 搜索列
* @returns {Promise<{
* success: boolean,
* data: Array<Record<string, any>>,
* code: number,
* message: string
* }>} 返回 Promise 对象
*/
export function loadRangeDataApi(
{ start, end, viewName, searchCondition = "", orderField = "", searchColumns = [] },
config = {},
) {
return httpPost(
"/data/range",
{
start,
end,
viewName,
searchCondition,
orderField,
searchColumns,
},
config,
);
}
export function saveObjectApi(data) {
return httpPost("/data/saveObject", data);
}
@@ -23,6 +23,15 @@ export const routes = [
moduleId: "v_container",
},
},
// ==================== Phone ====================
{
path: "/phone",
component: () => import("@/views/phone/index.vue"),
meta: {
title: "手机二维码",
keepAlive: true,
},
},
// ==================== 系统管理 ====================
{
path: "/system/field",
@@ -13,7 +13,7 @@ const userStore = useUserStore();
const permissionStore = usePermissionStore();
const form = ref({
orgid: userStore.userInfo.orgid,
orgid: 'g3hd',
userid: userStore.userInfo.userid,
password: "",
});
@@ -130,7 +130,7 @@ async function onVcodeSuccess() {
<div class="tip">登录</div>
<div class="split"></div>
<div class="form">
<div class="form-item">
<!-- <div class="form-item">
<div class="label required">机构码</div>
<el-input v-model="form.orgid">
@@ -138,7 +138,7 @@ async function onVcodeSuccess() {
<G3Icon icon="lucide:building" size="18" />
</template>
</el-input>
</div>
</div> -->
<div class="form-item">
<div class="label required">账号</div>
<el-input v-model="form.userid">
@@ -1,7 +1,13 @@
<script lang="js" setup>
import { loadDataBySqlApi } from "@/request/api";
import { loadDataApi, loadDataBySqlApi, loadRangeDataApi } from "@/request/api";
import { useThrottle } from "@/utils/throttle";
import { ElMessage } from "element-plus";
import { saveAs } from "file-saver";
import JSZip from "jszip";
import { getAliyunFileBlob } from "@/utils/file";
import { formatToDate } from "@/utils/time";
const viewName = "v_container";
const visible = defineModel(false);
@@ -22,7 +28,7 @@ const chunkModeForm = ref({
pageSize: 100,
});
const total = ref(0);
const allTotal = ref(0);
const loadingTotal = ref(false);
function handleResetForm() {
@@ -65,12 +71,7 @@ const fetchTotalThrottle = useThrottle(async () => {
await fetchTotal();
}, 300);
async function fetchTotal() {
if (!visible.value) {
return;
}
loadingTotal.value = true;
function buildSearchCondition() {
let searchCondition = [];
if (searchForm.value.startTime) {
@@ -82,9 +83,7 @@ async function fetchTotal() {
}
if (searchForm.value.containerNo) {
searchCondition.push(
`b_container_no LIKE '%${searchForm.value.containerNo}%'`,
);
searchCondition.push(`b_container_no LIKE '%${searchForm.value.containerNo}%'`);
}
if (searchForm.value.username) {
@@ -92,33 +91,320 @@ async function fetchTotal() {
}
// 拼接 WHERE 条件
const whereClause = searchCondition.length
? "WHERE " + searchCondition.join(" AND ")
: "";
const whereClause = searchCondition.length ? "WHERE " + searchCondition.join(" AND ") : "";
return whereClause;
}
async function fetchTotal() {
if (!visible.value) {
return;
}
loadingTotal.value = true;
// 构建 SQL 获取总数
const sql = `SELECT COUNT(*) as total FROM v_container ${whereClause}`;
const sql = `SELECT COUNT(*) as total FROM ${viewName} ${buildSearchCondition()}`;
try {
const res = await loadDataBySqlApi(sql);
if (res.success) {
total.value = res.data[0]?.total || 0;
allTotal.value = res.data[0]?.total || 0;
}
} catch (err) {
console.error("获取总数失败", err);
total.value = 0;
allTotal.value = 0;
} finally {
loadingTotal.value = false;
}
}
function handleExport() {
if (total === 0) {
async function fetchDatas() {
const response = await loadDataApi(
viewName,
buildSearchCondition(),
"b_inputdatetime DESC, b_id DESC",
["b_id", "b_container_no", "b_inputdatetime"],
);
if (response.success) {
return response.data;
} else {
return [];
}
}
function handleDownload() {
if (allTotal.value === 0) {
ElMessage.warning("无有效数据可操作");
return;
}
if (searchForm.value.mode === "ALL") {
downloadAll();
} else if (searchForm.value.mode === "SLICE") {
downloadSlice();
} else if (searchForm.value.mode === "CHUNK") {
downloadChunk();
}
}
async function runWithLimit(tasks, limit = 3) {
const results = [];
const executing = [];
for (const task of tasks) {
const p = task().then((res) => {
executing.splice(executing.indexOf(p), 1);
return res;
});
results.push(p);
executing.push(p);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
async function downloadAll() {
const loading = ElLoading.service({
lock: true,
text: "正在下载并打包文件,请稍候...",
});
try {
// 获取所有记录
const records = await fetchDatas();
if (!records.length) {
ElMessage.warning("无有效数据");
return;
}
const zip = new JSZip();
let finished = 0;
let totalFiles = 0;
// 1. 收集所有下载任务
const allTasks = [];
for (const item of records) {
const fileRes = await loadDataApi("bf_file", `father='${item.b_id}'`, "mx_xh asc");
const files = (fileRes.success && fileRes.data) || [];
totalFiles += files.length;
// 按 年/月/日/集装箱号 生成层级文件夹
const ymdFolderName = formatToDate(item.b_inputdatetime, "YYYY/MM/DD"); // 2026/03/27
const containerFolderName = item.b_container_no;
let folder = zip.folder(ymdFolderName);
folder = folder ? folder.folder(containerFolderName) : zip.folder(containerFolderName);
files.forEach((file, idx) => {
allTasks.push(async () => {
try {
const blob = await getAliyunFileBlob(file.mx_mapfilename);
const fileName = buildFileName(item.b_container_no, idx + 1, file.mx_filename) || "未命名文件";
folder.file(fileName, blob);
} catch (e) {
console.error(item.b_container_no + "下载失败:", file.mx_mapfilename, e);
} finally {
finished++;
loading.setText(`正在下载 ${finished}/${totalFiles} 个文件...`);
}
});
});
}
// 2. 并发执行下载任务,限制同时 3 个
await runWithLimit(allTasks, 3);
loading.setText("正在生成压缩包...");
const content = await zip.generateAsync({ type: "blob" });
// 3. 保存压缩包
saveAs(content, buildZipName("ALL"));
ElMessage.success("下载完成");
} catch (e) {
console.error(e);
} finally {
loading.close();
}
}
// 按区间下载
async function downloadSlice() {
const loading = ElLoading.service({
lock: true,
text: "正在下载并打包文件,请稍候...",
});
try {
let start = Math.max(1, Math.min(sliceModeForm.value.startPage || 1, allTotal.value));
let end = Math.max(1, Math.min(sliceModeForm.value.endPage || 1, allTotal.value));
if (start > end) [start, end] = [end, start];
sliceModeForm.value.startPage = start;
sliceModeForm.value.endPage = end;
const dataRes = await loadRangeDataApi({
start,
end,
viewName,
searchCondition: buildSearchCondition(),
orderField: "b_inputdatetime DESC, b_id DESC",
searchColumns: ["b_id", "b_container_no", "b_inputdatetime"],
});
if (!dataRes.success || !dataRes.data.length) {
ElMessage.warning("无有效数据");
return;
}
const records = dataRes.data;
const zip = new JSZip();
let finished = 0;
const allTasks = [];
records.forEach((item) => {
const ymdFolderName = formatToDate(item.b_inputdatetime, "YYYY/MM/DD");
const containerFolderName = item.b_container_no;
const folderPromise = loadDataApi("bf_file", `father='${item.b_id}'`, "mx_xh asc");
allTasks.push(async () => {
const fileRes = await folderPromise;
if (!fileRes.success) {
console.error("获取附件失败:", item.b_container_no);
return;
}
const folder = zip.folder(ymdFolderName)?.folder(containerFolderName) || zip.folder(containerFolderName);
const files = fileRes.data || [];
const fileTasks = files.map((file, idx) => async () => {
try {
const blob = await getAliyunFileBlob(file.mx_mapfilename);
const fileName = buildFileName(item.b_container_no, idx + 1, file.mx_filename) || "未命名文件";
folder.file(fileName, blob);
} catch (e) {
console.error(item.b_container_no + "下载失败:", file.mx_mapfilename, e);
} finally {
finished++;
loading.setText(`正在下载 ${finished} / ${records.length} 个文件...`);
}
});
await runWithLimit(fileTasks, 3);
});
});
await runWithLimit(allTasks, 3);
loading.setText("正在生成压缩包...");
const content = await zip.generateAsync({ type: "blob" });
saveAs(content, buildZipName("SLICE"));
ElMessage.success("下载完成");
} catch (e) {
console.error(e);
ElMessage.error("下载失败");
} finally {
loading.close();
}
}
// 分页拆分导出
async function downloadChunk() {
const loading = ElLoading.service({
lock: true,
text: "正在下载并打包文件,请稍候...",
});
try {
const records = await fetchDatas();
if (!records?.length) {
ElMessage.warning("无有效数据");
return;
}
const zip = new JSZip();
const pageSize = chunkModeForm.value.pageSize || 10;
const totalPages = Math.ceil(records.length / pageSize);
let finished = 0;
const allPageTasks = [];
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
const startIndex = pageIndex * pageSize;
const endIndex = Math.min((pageIndex + 1) * pageSize, records.length);
const pageRecords = records.slice(startIndex, endIndex);
const pageFolderName = `${startIndex + 1}-${endIndex}`;
const pageFolder = zip.folder(pageFolderName);
pageRecords.forEach((item) => {
allPageTasks.push(async () => {
const fileRes = await loadDataApi("bf_file", `father='${item.b_id}'`, "mx_xh asc");
if (!fileRes.success) {
console.error("获取附件失败:", item.b_container_no);
return;
}
const ymdFolderName = formatToDate(item.b_inputdatetime, "YYYY/MM/DD");
const containerFolderName = item.b_container_no;
const folder = pageFolder.folder(ymdFolderName)?.folder(containerFolderName) || pageFolder.folder(containerFolderName);
const files = fileRes.data || [];
const fileTasks = files.map((file, idx) => async () => {
try {
const blob = await getAliyunFileBlob(file.mx_mapfilename);
const fileName = buildFileName(item.b_container_no, idx + 1, file.mx_filename) || "未命名文件";
folder.file(fileName, blob);
} catch (e) {
console.error(item.b_container_no + "下载失败:", file.mx_mapfilename, e);
} finally {
finished++;
loading.setText(`正在下载 ${finished} 个文件...`);
}
});
await runWithLimit(fileTasks, 3);
});
});
}
await runWithLimit(allPageTasks, 3);
loading.setText("正在生成压缩包...");
const content = await zip.generateAsync({ type: "blob" });
saveAs(content, buildZipName("CHUNK"));
ElMessage.success("下载完成");
} catch (e) {
console.error(e);
ElMessage.error("下载失败");
} finally {
loading.close();
}
}
function buildFileName(containerNo, index, fileName) {
const formattedIndex = String(index).padStart(2, "0");
return `${containerNo}_${formattedIndex}.${fileName.split(".").pop()}`;
}
function buildZipName(mode) {
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, "");
switch (mode) {
case "ALL":
return `全部下载_${dateStr}.zip`;
case "SLICE":
return `下载第${sliceModeForm.value.startPage}-${sliceModeForm.value.endPage}条_${dateStr}.zip`;
case "CHUNK":
return `分页下载_每${chunkModeForm.value.pageSize}条_${dateStr}.zip`;
default:
return `下载数据_${dateStr}.zip`;
}
}
</script>
@@ -137,7 +423,7 @@ function handleExport() {
<div class="total">
共
<span class="num" v-loading="loadingTotal">
{{ total }}
{{ allTotal }}
</span>
条数据
</div>
@@ -195,9 +481,9 @@ function handleExport() {
<div class="label">模式</div>
<div class="content">
<el-radio-group v-model="searchForm.mode">
<el-radio label="ALL">全部导出</el-radio>
<el-radio label="SLICE">按页区间导出</el-radio>
<el-radio label="CHUNK">分页拆分导出</el-radio>
<el-radio value="ALL">全部导出</el-radio>
<el-radio value="SLICE">按区间导出</el-radio>
<el-radio value="CHUNK">分页拆分导出</el-radio>
</el-radio-group>
</div>
@@ -205,16 +491,16 @@ function handleExport() {
v-if="searchForm.mode === 'SLICE'"
style="margin-top: 8px; display: flex; align-items: center"
>
<el-input placeholder="起始页" />
<el-input placeholder="起始页" v-model="sliceModeForm.startPage" />
<el-divider direction="vertical" />
<el-input placeholder="终止页" />
<el-input placeholder="终止页" v-model="sliceModeForm.endPage" />
</div>
<div
v-if="searchForm.mode === 'CHUNK'"
style="margin-top: 8px; display: flex; align-items: center"
>
<el-input placeholder="分页大小" />
<el-input placeholder="分页大小" v-model="chunkModeForm.pageSize" />
</div>
</div>
</div>
@@ -226,7 +512,7 @@ function handleExport() {
</el-icon>
重置
</el-button>
<el-button type="primary">
<el-button type="primary" @click="handleDownload">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:cloud-download" />
</el-icon>
@@ -17,6 +17,8 @@ import { usePermissionStore } from "@/store/permission";
import { ExportVxeTableExcel, exportExcel } from "@/utils/excel";
import HighDownloadDialog from "./high-download-dialog.vue";
const viewName = "v_container";
const tableRef = ref();
const permissionStore = usePermissionStore();
@@ -55,14 +57,10 @@ const fileCategories = [
},
];
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);
return tableData.value.map((item) => item.b_container_img).filter((img) => img);
});
const currentRow = ref({});
@@ -195,12 +193,9 @@ async function deleteRows(rows = []) {
const safeDeleteIds = deleteIds.map((id) => Number(id));
// 使用安全的 SQL 查询
const fileRes = await loadDataApi(
"bf_file",
`father IN (${safeDeleteIds.join(",")})`,
"",
["subid"],
);
const fileRes = await loadDataApi("bf_file", `father IN (${safeDeleteIds.join(",")})`, "", [
"subid",
]);
if (!fileRes.success) {
return;
@@ -276,11 +271,7 @@ async function handleDownloadOne(data) {
});
try {
const fileRes = await loadDataApi(
"bf_file",
`father = ${data.b_id}`,
"mx_xh asc",
);
const fileRes = await loadDataApi("bf_file", `father = ${data.b_id}`, "mx_xh asc");
if (!fileRes.success) {
ElMessage.error("获取附件失败");
@@ -288,11 +279,7 @@ async function handleDownloadOne(data) {
}
const downloadFiles = fileRes.data.map((item, index) => ({
mx_filename: buildFileName(
data.b_container_no,
index + 1,
item.mx_filename,
),
mx_filename: buildFileName(data.b_container_no, index + 1, item.mx_filename),
mx_mapfilename: item.mx_mapfilename,
}));
@@ -347,73 +334,54 @@ async function handleDownloadBatch() {
text: "正在下载并打包文件,请稍候...",
});
const zip = new JSZip();
try {
let total = 0;
const zip = new JSZip();
let finished = 0;
let totalFiles = 0;
// 1. 收集所有下载任务
const allTasks = [];
for (const item of records) {
const fileRes = await loadDataApi(
"bf_file",
`father = '${item.b_id}'`,
"",
);
if (fileRes.success) {
total += (fileRes.data || []).length;
}
}
const fileRes = await loadDataApi("bf_file", `father='${item.b_id}'`, "mx_xh asc");
const files = (fileRes.success && fileRes.data) || [];
totalFiles += files.length;
for (const item of records) {
const folderName = `${item.b_container_no}_${formatToDate(item.b_inputdatetime)}`;
// 按 年/月/日/集装箱号 生成层级文件夹
const ymdFolderName = formatToDate(item.b_inputdatetime, "YYYY/MM/DD"); // 2026/03/27
const containerFolderName = item.b_container_no;
let folder = zip.folder(ymdFolderName);
folder = folder ? folder.folder(containerFolderName) : zip.folder(containerFolderName);
const folder = zip.folder(folderName);
const fileRes = await loadDataApi(
"bf_file",
`father = '${item.b_id}'`,
"mx_xh asc",
);
if (!fileRes.success) {
ElMessage.error("获取附件失败");
continue;
}
const files = fileRes.data || [];
const tasks = files.map((file, index) => async () => {
const fileUrl = file.mx_mapfilename;
try {
const blob = getAliyunFileBlob(fileUrl);
const fileName =
buildFileName(item.b_container_no, index + 1, file.mx_filename) ||
"未命名文件";
folder.file(fileName, blob);
} catch (e) {
ElMessage.error(item.b_container_no + "箱号部分图片下载失败");
console.error("下载失败:", fileUrl);
} finally {
finished++;
loading.setText(`正在下载 ${finished}/${total} 个文件...`);
}
files.forEach((file, idx) => {
allTasks.push(async () => {
try {
const blob = await getAliyunFileBlob(file.mx_mapfilename);
const fileName =
buildFileName(item.b_container_no, idx + 1, file.mx_filename) || "未命名文件";
folder.file(fileName, blob);
} catch (e) {
console.error(item.b_container_no + "下载失败:", file.mx_mapfilename, e);
} finally {
finished++;
loading.setText(`正在下载 ${finished}/${totalFiles} 个文件...`);
}
});
});
await runWithLimit(tasks, 3);
}
// 2. 并发执行下载任务,限制同时 3 个
await runWithLimit(allTasks, 3);
loading.setText("正在生成压缩包...");
const content = await zip.generateAsync({ type: "blob" });
saveAs(content, "批量下载.zip");
// 3. 保存压缩包
saveAs(content, "批量下载");
ElMessage.success("下载完成");
} catch (error) {
console.error(error);
ElMessage.error("打包失败");
} catch (e) {
console.error(e);
} finally {
loading.close();
}
@@ -424,109 +392,6 @@ function buildFileName(containerNo, index, fileName) {
return `${containerNo}_${formattedIndex}.${fileName.split(".").pop()}`;
}
function testExport() {
const options = {
columns: [
{
label: "基础信息",
field: "basicInfo",
width: 120,
children: [
{
label: "姓名",
field: "name",
width: 120,
},
{
label: "年龄",
field: "age",
width: 80,
},
{
label: "头像",
field: "avatar",
width: 100,
type: "image",
imageWidth: 80,
imageHeight: 80,
},
],
},
{
label: "联系方式",
field: "contactInfo",
width: 160,
children: [
{
label: "电话",
field: "phone",
width: 120,
},
{
label: "邮箱",
field: "email",
width: 200,
},
],
},
{
label: "其他信息",
field: "otherInfo",
width: 100,
children: [
{
label: "地址信息",
field: "address",
width: 150,
children: [
{ label: "省份", field: "province", width: 80 },
{ label: "城市", field: "city", width: 80 },
],
},
{ label: "备注", field: "remark", width: 200 },
],
},
],
data: [
{
name: "张三",
age: 25,
phone: "13800138000",
email: "zhangsan@example.com",
province: "浙江",
city: "杭州",
remark: "测试数据1",
avatar:
"data:image/png;base64,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 truncated
},
{
name: "李四",
age: 30,
phone: "13900139000",
email: "lisi@example.com",
province: "江苏",
city: "南京",
remark: "测试数据2",
avatar:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAQAElEQVR4AezdDXojR7kFYHNXAisJ7ISshLASclcCWQnsBLpmxpnxjGVJ3fV/3jxWbMvqqjrvZ6IjexL+78VfBAgQIECAQJyAAhA3coEJECBAgMDLiwLgu4AAAQIECAQKKACBQxeZAAECBLIFSnoFoCi4ESBAgACBMAEFIGzg4hIgQIBAusDn/ArAZwd/J0CAAAECUQIKQNS4hSVAgACBdIHX/ArAq4T3BAgQIEAgSEABCBq2qAQIECCQLvA1vwLw1cJHBAgQIEAgRkABiBm1oAQIECCQLvBtfgXgWw0fEyBAgACBEAEFIGTQYhIgQIBAusDb/ArAWw+fESBAgACBCAEFIGLMQhIgQIBAusD3+RWA70V8ToAAAQIEAgQUgIAhi0iAAAEC6QI/5lcAfjRxDwECBAgQ2F5AAdh+xAISIECAQLrAe/kVgPdU3EeAAAECBDYXUAA2H7B4BAgQIJAu8H5+BeB9F/cSIECAAIGtBRSArccrHAECBAikC9zKrwDcknE/AQIECBDYWEAB2Hi4ohEgQIBAusDt/ArAbRtfIUCAAAEC2wooANuOVjACBAgQSBf4KL8C8JGOrxEgQIAAgU0FFIBNBysWAQIECKQLfJxfAfjYx1cJECBAgMCWAgrAlmMVigABAgTSBe7lVwDuCfk6AQIECBDYUEAB2HCoIhEgQIBAusD9/ArAfSOPIECAAAEC2wkoANuNVCACBAgQSBd4JL8C8IiSxxAgQIAAgc0EFIDNBioOAQIECKQLPJZfAXjMyaMIECBAgMBWAgrAVuMUhgABAgTSBR7NrwA8KuVxBAgQIEBgIwEFYKNhikKAAAEC6QKP51cAHrfySAIECBAgsI2AArDNKAUhQIAAgXSBZ/IrAM9oeSwBAgQIENhEQAHYZJBiECBAgEC6wHP5FYDnvDyaAAECBAhsIaAAbDFGIQgQIEAgXeDZ/ArAs2IeT4AAAQIENhBQADYYoggECBAgkC7wfH4F4HkzVxAgQIAAgeUFFIDlRygAAQIECKQLnMmvAJxRcw0BAgQIEFhcQAFYfICOT4AAAQLpAufyKwDn3FxFgAABAgSWFlAAlh6fwxMgQIBAusDZ/ArAWTnXESBAgACBhQUUgIWH5+gECBAgkC5wPr8CcN7OlQQIECBAYFkBBWDZ0Tk4AQIECKQLXMmvAFzRcy0BAgQIEFhUQAFYdHCOTYAAAQLpAtfyKwDX/FxNgAABAgSWFFAAlhybQxMgQIBAusDV/ArAVUHXEyBAgACBBQUUgAWH5sgECBAgkC5wPb8CcN3QCgQIECBAYDkBBWC5kTkwAQIECKQL1MivANRQtAYBAgQIEFhMQAFYbGCOS4AAAQLpAnXyKwB1HK1CgAABAgSWElAAlhqXwxIgQIBAukCt/ApALUnrECBAgACBhQQUgIWG5agECBAgkC5QL78CUM/SSgQIECBAYBkBBWCZUTkoAQIECKQL1MyvANTUtBYBAgQIEFhEQAFYZFCOSYAAAQLpAnXzKwB1Pa1GgAABAgSWEFAAlhiTQxIgQIBAukDt/ApAbVHrESBAgACBBQQUgAWG5IgECBAgkC5QP78CUN/UigQIECBAYHoBBWD6ETkgAQIECKQLtMivALRQtSYBAgQIEJhcQAGYfECOR4AAAQLpAm3yKwBtXK1KgAABAgSmFlAAph6PwxEgQIBAukCr/ApAK1nrEiBAgACBiQUUgImH42gECBAgkC7QLr8C0M7WygQIECBAYFoBBWDa0TgYAQIECKQLtMyvALTUtTYBAgQIEJhUQAGYdDCORYAAAQLpAm3zKwBtfa1OgAABAgSmFFAAphyLQxEgQIBAukDr/ApAa2HrEyBAgACBCQUUgAmH4kgECBAgkC7QPr8C0N7YDgQIECBAYDoBBWC6kTgQAQIECKQL9MivAPRQtgcBAgQIEJhMQAGYbCCOQ4AAAQLpAn3yKwB9nO1CgAABAgSmElAAphqHwxAgQIBAukCv/ApAL2n7ECBAgACBiQQUgImG4SgECBAgkC7QL78C0M/aTgQIECBAYBoBBWCaUTgIAQIECKQL9MyvAPTUthcBAgQIEJhEQAGYZBCOQYAAAQLpAn3zKwB9ve1GgAABAgSmEFAAphiDQxAgQIBAukDv/ApAb3H7ESBAgACBCQQUgAmG4AgECBAgkC7QP78C0N/cjgQIECBAYLiAAjB8BA5AgAABAukCI/IrACPU7UmAAAECBAYLKACDB2B7AgQIEEgXGJNfARjjblcCBAgQIDBUQAEYym9zAgQIEEgXGJVfARglb18CBAgQIDBQQAEYiG9rAgQIEEgXGJdfARhnb2cCBAgQIDBMQAEYRm9jAgQIEEgXGJlfARipb28CBAgQIDBIQAEYBG9bAgQIEEgXGJtfARjrb3cCBAgQIDBEQAEYwm5TAgQIEEgXGJ1fARg9AfsTIECAAIEBAgrAAHRbEiBAgEC6wPj8CsD4GTgBAQIECBDoLqAAdCe3IQECBAikC8yQXwGYYQrOQIAAAQIEOgsoAJ3BbUeAAAEC6QJz5FcA5piDUxAgQIAAga4CCkBXbpsRIECAQLrALPkVgFkm4RwECBAgQKCjgALQEdtWBAgQIJAuME9+BWCeWTgJAQIECBDoJqAAdKO2EQECBAikC8yUXwGYaRrOQoAAAQIEOgkoAJ2gbUOAAAEC6QJz5VcA5pqH0xAgQIAAgS4CCkAXZpsQIECAQLrAbPkVgNkm4jwECBAgQKCDgALQAdkWBAgQIJAuMF9+BWC+mTgRAQIECBBoLqAANCe2AQECBAikC8yYXwGYcSrORIAAAQIEGgsoAI2BLU+AAAEC6QJz5lcA5pyLUxEgQIAAgaYCCkBTXosTIECAQLrArPkVgFkn41wECBAgQKChgALQENfSBAgQIJAuMG9+BWDe2TgZAQIECBBoJqAANKO1MAECBAikC8ycXwGYeTrORoAAAQIEGgkoAI1gLUuAAAEC6QJz51cA5p6P0xEgQIAAgSYCCkATVosSIECAQLrA7PkVgNkn5HwECBAgQKCBgALQANWSBAgQIJAuMH9+BWD+GTkhAQIECBCoLqAAVCe1IAECBAikC6yQXwFYYUrOSIAAAQIEKgsoAJVBLUeAAAEC6QJr5FcA1piTUxIgQIAAgaoCCkBVTosRIECAQLrAKvkVgFUm5ZwECBAgQKCigAJQEdNSBAgQIJAusE5+BWCdWTkpAQIECBCoJqAAVKO0EAECBAikC6yUXwFYaVrOSoAAAQIEKgkoAJUgLUOAAAEC6QJr5VcA1pqX0xIgQIAAgSoCCkAVRosQIECAQLrAavkVgNUm5rwECBAgQKCCgAJQAdESBAgQIJAusF5+BWC9mTkxAQIECBC4LKAAXCa0AAECBAikC6yYXwFYcWrOTIAAAQIELgooABcBXU6AAAEC6QJr5lcA1pybUxMgQIAAgUsCCsAlPhcTIECAQLrAqvkVgFUn59wECBAgQOCCgAJwAc+lBAgQIJAusG5+BWDd2Tk5AQIECBA4LaAAnKZzIQECBAikC6ycXwFYeXrOToAAAQIETgooACfhXEaAAAEC6QJr51cA1p6f0xMgQIAAgVMCCsApNhcRIECAQLrA6vkVgDYT/Mex7H+Db389sq/yljynZ7P/+xhquf3zeF++x/92vP/zcVv57VmD5MeX2ZfbTvNf+Xv38tkVgMuEFiAQI/DHI2m5lSf9UvJ+OT4vTwblSaEUgnL/cZe3TQXK7MutzNn8X9afsgKw/gwlIDBaoDwplCeEUgZKESifjz6T/fsJlHmbfz/vajspANUoLUSAwCHw+kRQfj1wfOotTCBm/jvMVQHYYYoyEJhLoLwiLL8eKL8aKB/PdTqnaS1QZm7+rZUrrK8AVEC0BAEC7wqUJ4Lya4Hy/t0HuHNrgTL3Tee/x9wUgD3mKAWBWQVenwTKHxyb9YzO1U7A/NvZXl5ZAbhMaAECBO4IlCcBfzjwDtLGX95u/rvMSgHYZZJyEJhboDwJlB8Hz31Kp2slYP6tZC+sqwBcwHMpAQJPCZQngfKTgKcu8uBtBDaZ/zbzeFEA9pmlJARWECh/FqDcVjirM9YXKLMvt/orW/FpAQXgaTIXECBwQaC8CvTfCLgAuPily89/cf83x1cA3nD4hACBDgLlFWC5ddjKFhMKlNmX24RHyzqSApA1b2kJzCLgpwCzTGLMORad/xisVrsqAK1krUuAwEcC5UfBH33d1/YWMP8J5qsATDAERyAQKFCeAPwYOHDwXyIvOf8vZ9/mnQKwzSgFIbCcwE/LndiBawqYf03NE2spACfQXEKAQBUBPwGowrjsIovNf1nnmwdXAG7S+AIBAgQIENhXQAHYd7aSEZhdoPweePYzOl87gaXm345h3MoKwDh7OxMgQIAAgWECCsAwehsTiBfwCjD7W2Ch+e85KAVgz7lKRYAAAQIEPhRQAD7k8UUCBAgQSBfYNb8CsOtk5SJAgAABAh8IKAAf4PgSAQIECKQL7JtfAdh3tpIRIECAAIGbAgrATRpfIECAAIF0gZ3zKwA7T1c2AgQIECBwQ0ABuAHjbgIECBBIF9g7vwKw93ylI0CAAAEC7wooAO+yuJMAAQIE0gV2z68AtJnwz8eyfxh4+/XY29tjAiPnNMPejynt+6gZZjDyDPtOVrK7AgrAXSIPIECAAIE8gf0TKwD7z1hCAgQIECDwg4AC8AOJOwgQIEAgXSAhvwKQMGUZCRAgQIDAdwIKwHcgPiVAgACBdIGM/ApAxpylJECAAAECbwQUgDccPiFAgACBdIGU/ApAyqTlJECAAAEC3wgoAN9g+JAAAQIE0gVy8isAObOWlAABAgQI/C6gAPxO4QMCBAgQSBdIyq8AJE1bVgIECBAg8EVAAfgC4R0BAgQIpAtk5VcAsuYtLQECBAgQ+CSgAHLine truncated
},
],
footer: [
{
name: "总计",
age: 55,
phone: "",
email: "",
province: "",
city: "",
remark: "2条记录",
},
],
};
exportExcel(options);
}
</script>
<template>
@@ -649,9 +514,6 @@ function testExport() {
<el-dropdown-item @click="ExportVxeTableExcel(tableRef)">
导出Execl
</el-dropdown-item>
<el-dropdown-item @click="ExportVxeTableExcel(tableRef)">
高级导出
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -683,14 +545,7 @@ function testExport() {
show-overflow
show-header-overflow
>
<vxe-column
field="seq"
type="seq"
width="50"
align="center"
title=" "
fixed="left"
/>
<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"
@@ -711,12 +566,7 @@ function testExport() {
/>
</template>
<template v-else>
{{
formatValueByFormat(
row[column.col_FieldName],
column.col_DisplayFormat,
)
}}
{{ formatValueByFormat(row[column.col_FieldName], column.col_DisplayFormat) }}
</template>
</template>
</vxe-column>
@@ -0,0 +1,136 @@
<script lang="js" setup>
import QrcodeVue from "qrcode.vue";
import QRCode from "qrcode";
import { loadDataApi } from "@/request/api";
import { useUserStore } from "@/store/user";
const userStore = useUserStore();
// 用户二维码数据
const userData = ref([]);
// 主二维码链接
const mainUrl = "http://ser215.g3soft.cn:8088/g3ocr-app/#/pages-sub/upload/index";
// Element Plus 图片查看器控制
const showPreview = ref(false);
const previewIndex = ref(0);
const srcList = ref([]);
onMounted(() => {
// loadUserDatas();
});
async function loadUserDatas() {
const response = await loadDataApi("b_user", `b_id <> 'g3soft' AND b_canuse = '1'`, "", ["b_id"]);
if (response.success) {
response.data.forEach((item) => {
userData.value.push({
account: item.b_id,
url: `${mainUrl}?orgid=${encodeURIComponent(userStore.userInfo.orgid)}&bid=${encodeURIComponent(item.b_id)}`,
});
});
}
}
// 生成 base64
async function generateQrBase64(urls) {
srcList.value = [];
for (const item of urls) {
const dataUrl = await QRCode.toDataURL(item, { width: 400 });
srcList.value.push(dataUrl);
}
}
// 打开预览
async function openPreview(index) {
const urls = [mainUrl, ...userData.value.map((u) => u.url)];
await generateQrBase64(urls);
previewIndex.value = index;
showPreview.value = true;
}
</script>
<template>
<div class="page-container">
<!-- 主二维码 -->
<div class="qr-section main-qr">
<h2>统一入口</h2>
<qrcode-vue :value="mainUrl" :size="220" :margin="2" @click.native="openPreview(0)" />
</div>
<el-divider />
<!-- 用户二维码 -->
<!-- <div class="qr-section user-qr">
<h2>用户二维码(免登录,需要启用该账户!)</h2>
<div class="user-qr-list">
<div v-for="(user, index) in userData" :key="index" class="user-qr-item">
<div class="account-name">{{ user.account }}</div>
<qrcode-vue
:value="user.url"
:size="200"
@click.native="openPreview(index + 1)"
:margin="2"
/>
</div>
</div>
</div> -->
<!-- Element Plus 图片查看器 -->
<el-image-viewer
v-if="showPreview"
:url-list="srcList"
:initial-index="previewIndex"
show-progress
@close="showPreview = false"
/>
</div>
</template>
<style scoped lang="scss">
@use "@/styles/mixins.scss" as *;
.page-container {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
overflow-y: auto;
}
.qr-section {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
margin-top: 16px;
}
.user-qr {
margin-top: 16px;
}
.qr-section h2 {
margin-bottom: 12px;
font-size: 18px;
font-weight: 600;
}
.user-qr-list {
@include flexGapWrap(10px, 36px);
}
.user-qr-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
}
.account-name {
margin-bottom: 6px;
font-size: 14px;
font-weight: 500;
color: #333;
}
</style>
@@ -75,6 +75,18 @@ async function loadTableDatas() {
searchCondition += `b_inputdatetime <= '${searchForm.value.endTime}' AND `;
}
if (searchForm.value.datas) {
searchCondition += `b_datas LIKE '${searchForm.value.datas}' AND `;
}
if (searchForm.value.option) {
searchCondition += `b_option LIKE '${searchForm.value.option}' AND `;
}
if (searchForm.value.table) {
searchCondition += `b_table LIKE '${searchForm.value.table}' AND `;
}
if (searchCondition.endsWith(" AND ")) {
searchCondition = searchCondition.slice(0, -4);
}
@@ -163,16 +175,16 @@ function handleSearchReset() {
</G3Grid>
<div class="search-extra">
<el-button type="primary">
<el-button type="primary" @click="handleSearch">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:search" @click="handleSearch" />
<G3Icon icon="lucide:search" />
</el-icon>
搜索
</el-button>
<el-button>
<el-button @click="handleSearchReset">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:refresh-ccw" @click="handleSearchReset" />
<G3Icon icon="lucide:refresh-ccw" />
</el-icon>
重置
</el-button>
File diff suppressed because it is too large. Load diff