This commit is contained in:
oneao committed 2026-03-23 21:12:09 +08:00
1 parent 3e172a1d53
commit 401a6719f6
12 files changed
+487 -277

No files matched your search

-1
View File
@@ -12,7 +12,6 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Directory: typeof import('./src/components/wang-editor/modules/directory.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
@@ -349,7 +349,11 @@ 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;
@@ -494,7 +498,9 @@ 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;
@@ -507,7 +513,10 @@ 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");
}
@@ -517,7 +526,9 @@ 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);
@@ -558,7 +569,7 @@ async function deleteFiles(files) {
try {
for (const item of files) {
if (item.subid) {
await deleteFileApi("bf_file", item.subid);
await deleteFileApi(item.subid);
}
}
@@ -587,12 +598,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) {
@@ -679,7 +690,9 @@ 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
@@ -719,6 +732,9 @@ function handleTreeNodeClick(data) {
style="margin-left: 8px"
@click="handleDownloadZip"
>
<el-icon class="el-icon--left">
<G3Icon icon="lucide:download" />
</el-icon>
打包下载
</el-button>
@@ -729,6 +745,9 @@ function handleTreeNodeClick(data) {
:disabled="checkedFiles.length === 0 || loading"
@click="handleBatchDelete()"
>
<el-icon class="el-icon--left">
<G3Icon icon="lucide:trash-2" />
</el-icon>
批量删除
</el-button>
</div>
@@ -738,7 +757,7 @@ function handleTreeNodeClick(data) {
<span class="g3-mode">
模式:{{ displayMode === 0 ? "卡片视图" : "列表视图" }}
<el-icon class="el-icon--right">
<ArrowDown />
<G3Icon icon="lucide:chevron-down" />
</el-icon>
</span>
<template #dropdown>
@@ -767,13 +786,17 @@ function handleTreeNodeClick(data) {
>
<div v-if="fileList.length === 0" class="g3-tip">
<el-icon size="64">
<UploadFilled />
<G3Icon icon="lucide:cloud-upload" />
</el-icon>
<div>支持 拖拽文件、粘贴文件、多文件上传</div>
</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>
@@ -785,9 +808,15 @@ 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" />
@@ -803,8 +832,15 @@ 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">
| {{ formatTime(file.mx_updatedatetime || file.mx_inputdatetime) }}
<template
v-if="file.mx_inputdatetime || file.mx_updatedatetime"
>
|
{{
formatTime(
file.mx_updatedatetime || file.mx_inputdatetime,
)
}}
</template>
</div>
</div>
@@ -812,26 +848,33 @@ function handleTreeNodeClick(data) {
<el-button
v-if="!props.readonly"
type="danger"
:icon="Delete"
size="small"
circle
@click.stop="handleDeleteOne(file)"
/>
>
<G3Icon icon="lucide:trash-2" />
</el-button>
<el-button
type="primary"
:icon="Download"
size="small"
circle
@click.stop="handleDownloadFile(file)"
/>
<el-dropdown v-if="!props.readonly" placement="top" trigger="click">
>
<G3Icon icon="lucide:download" />
</el-button>
<el-dropdown
v-if="!props.readonly"
placement="top"
trigger="click"
>
<el-button
type="info"
:icon="More"
size="small"
circle
style="margin-left: 12px"
/>
>
<G3Icon icon="lucide:ellipsis" />
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleShowUpdateDialog(file)">
@@ -855,9 +898,6 @@ function handleTreeNodeClick(data) {
height="100%"
show-overflow
show-header-overflow
:cell-config="{
height: 26,
}"
:sort-config="{
trigger: 'cell',
}"
@@ -872,14 +912,28 @@ 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>
@@ -904,7 +958,14 @@ function handleTreeNodeClick(data) {
</vxe-column>
<vxe-column width="180" title="操作">
<template #default="{ row }">
<div style="display: flex; align-items: center; justify-content: center">
<div
style="
display: flex;
align-items: center;
justify-content: center;
height: 28px;
"
>
<el-button
v-if="!props.readonly"
type="danger"
@@ -913,14 +974,26 @@ 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>
@@ -937,10 +1010,16 @@ function handleTreeNodeClick(data) {
</div>
<template v-if="!props.readonly" #footer>
<el-button type="primary" :icon="HelpFilled" :disabled="loading" @click="handleClickUpload">
<el-button type="primary" :disabled="loading" @click="handleClickUpload">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:mouse-pointer" />
</el-icon>
选择
</el-button>
<el-button type="primary" :icon="UploadFilled" :disabled="loading" @click="handleSave">
<el-button type="primary" :disabled="loading" @click="handleSave">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:upload" />
</el-icon>
上传
</el-button>
</template>
@@ -953,7 +1032,9 @@ function handleTreeNodeClick(data) {
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>
@@ -966,10 +1047,16 @@ 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')">
@@ -999,7 +1086,11 @@ 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>
@@ -83,58 +83,82 @@ function switchTab(tab, index) {
moveScroll(index);
}
const HOME_PATH = "/";
// 关闭标签
async function closeTab(tab) {
if (tab.fullPath === HOME_PATH) return; // ❗首页不能关
const activePath = await tabStore.closeTab(tab.fullPath);
if (activePath && route.fullPath !== activePath) {
router.push(activePath);
} else if (!activePath) {
router.push("/");
router.push(HOME_PATH);
}
}
// 关闭左侧
function closeLeft(data) {
const currentIndex = tabStore.tabs.findIndex(
(item) => item.fullPath === data.fullPath,
);
if (currentIndex === -1) return;
// 关闭左侧所有标签
tabStore.tabs.slice(0, currentIndex).forEach((item) => {
const index = tabStore.tabs.findIndex((t) => t.fullPath === item.fullPath);
if (index !== -1) tabStore.tabs.splice(index, 1);
if (item.fullPath === route.fullPath) {
router.push(data.fullPath);
}
});
tabStore.tabs
.slice(0, currentIndex)
.filter((item) => item.fullPath !== HOME_PATH) // ❗过滤首页
.forEach((item) => {
const index = tabStore.tabs.findIndex(
(t) => t.fullPath === item.fullPath,
);
if (index !== -1) tabStore.tabs.splice(index, 1);
if (item.fullPath === route.fullPath) {
router.push(data.fullPath);
}
});
}
// 关闭右侧
function closeRight(data) {
const currentIndex = tabStore.tabs.findIndex(
(item) => item.fullPath === data.fullPath,
);
if (currentIndex === -1) return;
tabStore.tabs.slice(currentIndex + 1).forEach((item) => {
const index = tabStore.tabs.findIndex((t) => t.fullPath === item.fullPath);
if (index !== -1) tabStore.tabs.splice(index, 1);
if (item.fullPath === route.fullPath) {
router.push(data.fullPath);
}
});
tabStore.tabs
.slice(currentIndex + 1)
.filter((item) => item.fullPath !== HOME_PATH) // ❗过滤首页
.forEach((item) => {
const index = tabStore.tabs.findIndex(
(t) => t.fullPath === item.fullPath,
);
if (index !== -1) tabStore.tabs.splice(index, 1);
if (item.fullPath === route.fullPath) {
router.push(data.fullPath);
}
});
}
// 关闭其他
function closeOther(tab) {
tabStore.tabs = tabStore.tabs.filter((t) => t.fullPath === tab.fullPath);
tabStore.tabs = tabStore.tabs.filter(
(t) => t.fullPath === tab.fullPath || t.fullPath === HOME_PATH, // ❗保留首页
);
router.push(tab.fullPath);
}
// 关闭全部
function closeAll() {
tabStore.tabs = [];
router.push("/");
}
tabStore.tabs = tabStore.tabs.filter(
(t) => t.fullPath === HOME_PATH, // ❗只保留首页
);
router.push(HOME_PATH);
}
function toHome() {
router.push("/");
}
@@ -63,13 +63,22 @@ function logout() {
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="router.push('/user-center')"
>个人中心</el-dropdown-item
>
<el-dropdown-item @click="router.push('/user-center')">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:user-round-pen" />
</el-icon>
个人中心
</el-dropdown-item>
<el-dropdown-item @click="showUpdatePassword = !showUpdatePassword">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:lock-keyhole-open" />
</el-icon>
修改密码
</el-dropdown-item>
<el-dropdown-item divided @click="logout">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:arrow-right-from-line" />
</el-icon>
退出登录
</el-dropdown-item>
</el-dropdown-menu>
@@ -154,10 +154,10 @@ export function insertFileApi(params) {
return httpPost("/file/insert", params);
}
export function deleteFileApi({ id, tableName }) {
export function deleteFileApi(subid) {
return httpPost("/file/delete", {
id,
tableName,
subid,
tableName: "bf_file",
bucketName: import.meta.env.PUBLIC_ALIYUN_BUCKET,
});
}
+163 -180
View File
@@ -1,42 +1,42 @@
import { getAliyunKeyApi } from "@/request/api";
import { getAliyunKeyApi, insertFileApi } from "@/request/api";
/**
* 选择并压缩图片
*/
export function uploadImage(options = {}) {
const { quality = 0.9, maxSize = 512000 } = options
const { quality = 0.9, maxSize = 512000 } = options;
return new Promise((resolve, reject) => {
if (typeof document === 'undefined') {
reject(new Error('Require browser environment'))
return
if (typeof document === "undefined") {
reject(new Error("Require browser environment"));
return;
}
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.style.display = 'none'
const input = document.createElement("input");
input.type = "file";
input.accept = "image/*";
input.style.display = "none";
input.onchange = async (e) => {
const file = e.target.files?.[0]
const file = e.target.files?.[0];
if (!file) {
reject(new Error('No file selected'))
return
reject(new Error("No file selected"));
return;
}
try {
if (!file.type.startsWith('image/')) {
throw new Error('Invalid image file')
if (!file.type.startsWith("image/")) {
throw new Error("Invalid image file");
}
// 创建一个临时URL来加载图片
const url = URL.createObjectURL(file)
const url = URL.createObjectURL(file);
// 调用 compressImage 进行压缩
const compressResult = await compressImage(url, quality, maxSize)
const compressResult = await compressImage(url, quality, maxSize);
// 释放临时URL
URL.revokeObjectURL(url)
URL.revokeObjectURL(url);
resolve({
blob: compressResult.blob,
@@ -44,19 +44,17 @@ export function uploadImage(options = {}) {
fileName: file.name,
fileExt: getFileExtension(file.name),
fileSize: file.size,
})
});
} catch (err) {
reject(err);
} finally {
document.body.removeChild(input);
}
catch (err) {
reject(err)
}
finally {
document.body.removeChild(input)
}
}
};
document.body.appendChild(input)
input.click()
})
document.body.appendChild(input);
input.click();
});
}
/**
@@ -64,26 +62,26 @@ export function uploadImage(options = {}) {
*/
export function uploadVideo() {
return new Promise((resolve, reject) => {
if (typeof document === 'undefined') {
reject(new Error('Require browser environment'))
return
if (typeof document === "undefined") {
reject(new Error("Require browser environment"));
return;
}
const input = document.createElement('input')
input.type = 'file'
input.accept = 'video/*'
input.style.display = 'none'
const input = document.createElement("input");
input.type = "file";
input.accept = "video/*";
input.style.display = "none";
input.onchange = (e) => {
const file = e.target.files?.[0]
const file = e.target.files?.[0];
if (!file) {
reject(new Error('No file selected'))
return
reject(new Error("No file selected"));
return;
}
if (!file.type.startsWith('video/')) {
reject(new Error('Invalid video file'))
return
if (!file.type.startsWith("video/")) {
reject(new Error("Invalid video file"));
return;
}
resolve({
@@ -92,121 +90,114 @@ export function uploadVideo() {
fileName: file.name,
fileExt: getFileExtension(file.name),
fileSize: file.size,
})
});
document.body.removeChild(input)
}
document.body.removeChild(input);
};
document.body.appendChild(input)
input.click()
})
document.body.appendChild(input);
input.click();
});
}
// 压缩图片
export async function compressImage(url, quality = 0.9, maxSize = 512000) {
return new Promise((resolve, reject) => {
const img = new Image()
img.src = url
const img = new Image();
img.src = url;
img.onload = async () => {
try {
// 获取原图大小
const imgSize = await fetch(url)
.then(res => res.blob())
.then(blob => blob.size)
.then((res) => res.blob())
.then((blob) => blob.size);
if (imgSize <= maxSize) {
return resolve({ url, size: imgSize, blob: await fetch(url).then(res => res.blob()) })
return resolve({ url, size: imgSize, blob: await fetch(url).then((res) => res.blob()) });
}
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx)
return reject(new Error('无法获取canvas上下文'))
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) return reject(new Error("无法获取canvas上下文"));
let targetWidth = img.width
let targetHeight = img.height
let targetWidth = img.width;
let targetHeight = img.height;
// 最大边长限制
const maxEdge = 2000
const maxEdge = 2000;
if (targetWidth > maxEdge || targetHeight > maxEdge) {
if (targetWidth > targetHeight) {
targetHeight = (maxEdge * targetHeight) / targetWidth
targetWidth = maxEdge
}
else {
targetWidth = (maxEdge * targetWidth) / targetHeight
targetHeight = maxEdge
targetHeight = (maxEdge * targetHeight) / targetWidth;
targetWidth = maxEdge;
} else {
targetWidth = (maxEdge * targetWidth) / targetHeight;
targetHeight = maxEdge;
}
}
canvas.width = targetWidth
canvas.height = targetHeight
canvas.width = targetWidth;
canvas.height = targetHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0, targetWidth, targetHeight)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
const getBlob = q =>
const getBlob = (q) =>
new Promise((resolve, reject) => {
canvas.toBlob(
blob => (blob ? resolve(blob) : reject(new Error('Blob转换失败'))),
'image/jpeg',
(blob) => (blob ? resolve(blob) : reject(new Error("Blob转换失败"))),
"image/jpeg",
q,
)
})
);
});
let blob = await getBlob(quality)
let blob = await getBlob(quality);
// 调整质量直到接近最大文件大小
let minQ = 0.5
let maxQ = quality
let attempts = 0
let minQ = 0.5;
let maxQ = quality;
let attempts = 0;
while (blob.size > maxSize && maxQ - minQ > 0.01 && attempts < 5) {
const midQ = (minQ + maxQ) / 2
blob = await getBlob(midQ)
const midQ = (minQ + maxQ) / 2;
blob = await getBlob(midQ);
if (blob.size > maxSize) {
maxQ = midQ
maxQ = midQ;
} else {
minQ = midQ;
}
else {
minQ = midQ
}
attempts++
attempts++;
}
resolve({ blob, size: blob.size, url: URL.createObjectURL(blob) })
resolve({ blob, size: blob.size, url: URL.createObjectURL(blob) });
} catch (err) {
reject(err);
}
catch (err) {
reject(err)
}
}
};
img.onerror = () => reject(new Error('图片加载失败'))
})
img.onerror = () => reject(new Error("图片加载失败"));
});
}
export function formatFileSize(size) {
if (size < 1024)
return `${size} B`
else if (size < 1024 * 1024)
return `${(size / 1024).toFixed(2)} KB`
else if (size < 1024 * 1024 * 1024)
return `${(size / (1024 * 1024)).toFixed(2)} MB`
else return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`
if (size < 1024) return `${size} B`;
else if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`;
else if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(2)} MB`;
else return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
// 获取文件扩展名并进行分类
export function getFileExtension(fileName) {
// 获取文件扩展名
const fileExt = fileName.includes('.')
? fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase() // 获取扩展名并转为小写
: ''
const fileExt = fileName.includes(".")
? fileName.slice(fileName.lastIndexOf(".") + 1).toLowerCase() // 获取扩展名并转为小写
: "";
// 如果是office类文件,将其归类到 "office"
if (['docx', 'docm', 'xls', 'xlsx'].includes(fileExt)) {
return 'office'
if (["docx", "docm", "xls", "xlsx"].includes(fileExt)) {
return "office";
}
// 返回扩展名
return fileExt
return fileExt;
}
// 上传到阿里云
@@ -216,29 +207,27 @@ export function uploadFile(blob, fileName, fileExt, fileSize, xh, moduleid, fath
getAliyunKeyApi()
.then(async (res) => {
try {
const aliyunKey = res.data
const formData = new FormData()
const aliyunKey = res.data;
const formData = new FormData();
let uniqueName = `${Date.now()}_${Math.random()
.toString(36)
.slice(2)}`
let uniqueName = `${Date.now()}_${Math.random().toString(36).slice(2)}`;
if (fileExt) {
uniqueName += `.${fileExt}`
uniqueName += `.${fileExt}`;
}
formData.append('key', `${aliyunKey.dir}${uniqueName}`)
formData.append('OSSAccessKeyId', aliyunKey.accessid)
formData.append('policy', aliyunKey.policy)
formData.append('Signature', aliyunKey.signature)
formData.append('file', blob, uniqueName)
formData.append("key", `${aliyunKey.dir}${uniqueName}`);
formData.append("OSSAccessKeyId", aliyunKey.accessid);
formData.append("policy", aliyunKey.policy);
formData.append("Signature", aliyunKey.signature);
formData.append("file", blob, uniqueName);
const xhr = new XMLHttpRequest()
xhr.open('POST', aliyunKey.host, true)
const xhr = new XMLHttpRequest();
xhr.open("POST", aliyunKey.host, true);
xhr.onload = async () => {
if (xhr.status === 200 || xhr.status === 204) {
const allFilePath = `${aliyunKey.host}/${aliyunKey.dir}${uniqueName}`
const allFilePath = `${aliyunKey.host}/${aliyunKey.dir}${uniqueName}`;
const insertRes = await insertFileApi({
father,
@@ -248,8 +237,8 @@ export function uploadFile(blob, fileName, fileExt, fileSize, xh, moduleid, fath
mx_mapfilename: allFilePath,
mx_filesize: fileSize,
mx_fileext: fileExt,
mx_cate_id: cateid || '',
})
mx_cate_id: cateid || "",
});
if (insertRes.success) {
resolve({
@@ -260,33 +249,30 @@ export function uploadFile(blob, fileName, fileExt, fileSize, xh, moduleid, fath
mx_mapfilename: allFilePath,
mx_filesize: fileSize,
mx_fileext: fileExt,
mx_cate_id: cateid || '',
})
}
else {
reject(new Error(insertRes.data.message))
mx_cate_id: cateid || "",
});
} else {
reject(new Error(insertRes.data.message));
}
} else {
ElMessage.error("上传失败,请稍后再试");
reject(new Error(`OSS 上传失败: ${xhr.responseText}`));
}
else {
ElMessage.error('上传失败,请稍后再试')
reject(new Error(`OSS 上传失败: ${xhr.responseText}`))
}
}
};
xhr.onerror = () => {
ElMessage.error('上传失败,请稍后再试')
reject(new Error('OSS 上传异常'))
}
ElMessage.error("上传失败,请稍后再试");
reject(new Error("OSS 上传异常"));
};
xhr.send(formData)
}
catch (err) {
ElMessage.error('上传失败,请稍后再试')
reject(err)
xhr.send(formData);
} catch (err) {
ElMessage.error("上传失败,请稍后再试");
reject(err);
}
})
.catch(reject)
})
.catch(reject);
});
}
export function getAliyunFileBlob(url) {
@@ -294,73 +280,70 @@ export function getAliyunFileBlob(url) {
// 创建一个独立的异步函数来处理 fetch 请求
async function fetchFile() {
try {
const response = await fetch(url, { mode: 'cors' })
const response = await fetch(url, { mode: "cors" });
// 检查响应状态
if (!response.ok) {
ElMessage.error(`下载失败:${response.status}`)
reject(new Error(`下载失败:${response.status}`))
return
ElMessage.error(`下载失败:${response.status}`);
reject(new Error(`下载失败:${response.status}`));
return;
}
// 获取文件内容
const blob = await response.blob()
const blob = await response.blob();
// 返回blob
resolve(blob)
}
catch (error) {
resolve(blob);
} catch (error) {
// 捕获网络请求失败或其他错误
ElMessage.error('下载文件时发生错误')
reject(error)
ElMessage.error("下载文件时发生错误");
reject(error);
}
}
// 调用异步函数
fetchFile()
})
fetchFile();
});
}
export function downloadZip(
files,
fileName = '文件合集',
callback,
) {
const zip = new JSZip()
const addedFiles = new Set() // 用来存储已添加的文件名
export function downloadZip(files, fileName = "文件合集", callback) {
const zip = new JSZip();
const addedFiles = new Set(); // 用来存储已添加的文件名
let result = files.map((item) => {
let promise = getAliyunFileBlob(item.mx_mapfilename).then((res) => {
// 处理文件名重复的情况
let uniqueFileName = getUniqueFileName(item.mx_filename, addedFiles)
zip.file(uniqueFileName, res, { binary: true })
addedFiles.add(uniqueFileName) // 添加文件名到已添加的集合中
})
return promise
})
let uniqueFileName = getUniqueFileName(item.mx_filename, addedFiles);
zip.file(uniqueFileName, res, { binary: true });
addedFiles.add(uniqueFileName); // 添加文件名到已添加的集合中
});
return promise;
});
Promise.all(result).then(() => {
zip.generateAsync({ type: 'blob' }).then((res) => {
callback && callback()
saveAs(res, `${fileName}.zip`)
Promise.all(result)
.then(() => {
zip.generateAsync({ type: "blob" }).then((res) => {
callback && callback();
saveAs(res, `${fileName}.zip`);
});
})
}).catch(() => {
callback && callback()
})
.catch(() => {
callback && callback();
});
}
// 处理文件名重复的函数
function getUniqueFileName(fileName, addedFiles) {
let uniqueFileName = fileName
let counter = 1
let uniqueFileName = fileName;
let counter = 1;
// 如果文件名已经存在,则在文件名后加上 (1), (2), ...
while (addedFiles.has(uniqueFileName)) {
const fileNameWithoutExtension = uniqueFileName.replace(/\.[^/.]+$/, '')
const extension = uniqueFileName.slice(uniqueFileName.lastIndexOf('.'))
uniqueFileName = `${fileNameWithoutExtension} (${counter})${extension}`
counter++
const fileNameWithoutExtension = uniqueFileName.replace(/\.[^/.]+$/, "");
const extension = uniqueFileName.slice(uniqueFileName.lastIndexOf("."));
uniqueFileName = `${fileNameWithoutExtension} (${counter})${extension}`;
counter++;
}
return uniqueFileName
return uniqueFileName;
}
@@ -344,6 +344,8 @@ async function handleTreeLoad(node, resolve) {
}
async function handleTreeNodeClick(nodeData) {
console.log("===node===")
console.log(nodeData)
if (!nodeData || nodeData.b_id === "root" || !nodeData.b_moduleid) return;
currentNode.value = nodeData;
@@ -17,6 +17,8 @@ const loading = reactive({
const emits = defineEmits(["change", "closed"]);
const showFile = ref(false);
const showVisible = defineModel(false);
const form = ref({
@@ -136,18 +138,41 @@ 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>
<el-button @click="showVisible = false">取消</el-button>
<el-button type="primary" :loading="loading.save" @click="handleSave">确认</el-button>
<div class="g3-footer">
<el-button type="primary" @click="showFile = true">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:paperclip" />
</el-icon>
上传附件
</el-button>
<div>
<el-button @click="showVisible = false">取消</el-button>
<el-button type="primary" :loading="loading.save" @click="handleSave"
>确认</el-button
>
</div>
</div>
</template>
<G3File v-model="showFile" :father="form.b_id" module-id="oa_notices" />
</el-dialog>
</template>
@@ -171,4 +196,10 @@ function onClosed() {
min-width: 70px;
white-space: nowrap;
}
.g3-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
@@ -191,7 +191,12 @@ async function deleteRows(rows = []) {
<template>
<div class="g3-flex-container">
<div class="g3-header">
<el-button type="primary" @click="showCategory = true"> 分类 </el-button>
<el-button type="primary" @click="showCategory = true">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:settings" />
</el-icon>
分类设置
</el-button>
</div>
<div class="g3-container">
<el-splitter>
@@ -218,8 +223,18 @@ async function deleteRows(rows = []) {
<div class="g3-container-right">
<div style="width: 100%">
<div style="display: flex; height: 32px; margin-bottom: 8px">
<el-button type="primary" @click="handleAdd"> 新增 </el-button>
<el-button type="danger" @click="handleDeleteRows"> 批量删除 </el-button>
<el-button type="primary" @click="handleAdd">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:plus" />
</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>
<div class="g3-table">
@@ -279,11 +294,20 @@ async function deleteRows(rows = []) {
</template>
</vxe-column>
<vxe-column width="200" align="center" fixed="right" title="操作">
<vxe-column
width="200"
align="center"
fixed="right"
title="操作"
>
<template #default="{ row }">
<el-button type="primary" link @click="handleEditRow(row)"> 编辑 </el-button>
<el-button type="primary" link @click="handleEditRow(row)">
编辑
</el-button>
<el-button type="danger" link @click="handleDeleteRow(row)"> 删除 </el-button>
<el-button type="danger" link @click="handleDeleteRow(row)">
删除
</el-button>
</template>
</vxe-column>
</vxe-table>
@@ -18,7 +18,14 @@ const loading = reactive({
let oldRowData = {};
const textareaFields = ["b_sql", "b_resultsql", "b_polyline", "b_polyline2", "b_graph_x", "b_img"];
const textareaFields = [
"b_sql",
"b_resultsql",
"b_polyline",
"b_polyline2",
"b_graph_x",
"b_img",
];
const validRules = ref({
b_id: [
@@ -149,7 +156,9 @@ async function deleteRows(rows = []) {
return;
}
const delectedIds = rows.filter((row) => !$table.isInsertByRow(row)).map((row) => row.b_id);
const delectedIds = rows
.filter((row) => !$table.isInsertByRow(row))
.map((row) => row.b_id);
const tipMessage = `确定要删除选中的用户提醒吗?`;
@@ -248,7 +257,12 @@ async function saveRow(row) {
];
} else {
// 更新
const { isChanged, result } = diffObject(row, oldRowData, ["b_id"], ["_X_ROW_KEY"]);
const { isChanged, result } = diffObject(
row,
oldRowData,
["b_id"],
["_X_ROW_KEY"],
);
if (!isChanged) {
ElMessage.info("没有数据变更");
@@ -297,7 +311,7 @@ onMounted(async () => {
await getsubtable(tableData, tableDataoriginal, ``);
});
async function handleRefresh(){
async function handleRefresh() {
await getsubtable(tableData, tableDataoriginal, ``);
}
</script>
@@ -306,11 +320,25 @@ async function handleRefresh(){
<div class="g3-flex-container">
<div class="g3-header">
<div class="g3-header-left">
<el-button type="primary" @click="handleAdd"> 新建 </el-button>
<el-button type="danger" @click="handleDeleteRows"> 删除 </el-button>
<el-button type="primary" @click="handleAdd">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:plus" />
</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 class="g3-header-right">
<div class="g3-svg-query" style="margin-right: 10px" @click="handleRefresh">
<div
class="g3-svg-query"
style="margin-right: 10px"
@click="handleRefresh"
>
<G3Icon icon="lucide:refresh-ccw" />
</div>
<el-dropdown>
@@ -398,7 +426,9 @@ async function handleRefresh(){
>
保存
</el-button>
<el-button link @click="cancelEditRow(row)" @click.stop> 取消 </el-button>
<el-button link @click="cancelEditRow(row)" @click.stop>
取消
</el-button>
</template>
<template v-else>
<el-button
@@ -411,7 +441,12 @@ async function handleRefresh(){
编辑
</el-button>
<el-button :disabled="loading.save" type="danger" link @click="handleDeleteRow(row)">
<el-button
:disabled="loading.save"
type="danger"
link
@click="handleDeleteRow(row)"
>
删除
</el-button>
</template>
@@ -425,17 +425,26 @@ function handleShowBatchCopyDialog() {
</el-icon>
批量删除
</el-button>
<el-button type="primary" @click="showPasswordRow = !showPasswordRow"
>查看密码</el-button
>
<el-button type="primary" @click="handleShowBatchCopyDialog"
>批量复制权限</el-button
>
<el-button type="primary" @click="showPasswordRow = !showPasswordRow">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:eye" />
</el-icon>
查看密码
</el-button>
<el-button type="primary" @click="handleShowBatchCopyDialog">
<el-icon class="el-icon--left">
<G3Icon icon="lucide:copy" />
</el-icon>
批量复制权限
</el-button>
<el-button
type="primary"
@click="handleShowCopyDialog"
:disabled="!isSelectedRow"
>
<el-icon class="el-icon--left">
<G3Icon icon="lucide:copy" />
</el-icon>
复制权限
</el-button>
<el-button
@@ -443,6 +452,9 @@ function handleShowBatchCopyDialog() {
:disabled="!isSelectedRow"
@click="showEmployee = true"
>
<el-icon class="el-icon--left">
<G3Icon icon="lucide:book-user" />
</el-icon>
员工资料
</el-button>
</div>