海信医疗-远程超声管理平台-信创国产化
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

964 lines
30 KiB

<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 左侧分类菜单 -->
<el-col :span="6">
<el-form @submit.native.prevent>
<el-form-item prop="name">
<el-input
v-model="queryRightParams.title"
placeholder="输入内容过滤"
clearable
size="small"
prefix-icon="el-icon-search"
@keyup.enter.native="handleRightQuery"
>
<template slot="append">
<el-button
slot="append"
icon="el-icon-refresh-right"
@click="resetRightQuery"
/>
</template>
</el-input>
</el-form-item>
</el-form>
<el-menu :default-active="defaultActive" @select="handleSelect">
<!-- 自定义标签 -->
<el-menu-item
v-for="(item, index) in cateList"
:key="item.id"
:index="item.id"
class="custom-item"
>
<div class="left">
<i :class="getBuiltInIcon(index) || 'el-icon-collection-tag'" />
<span>{{ item.name }}</span>
</div>
<div class="right" v-if="item.type == 'user'">
<el-button
type="text"
icon="el-icon-edit"
@click.stop="handleEditTag(item)"
/>
<el-button
type="text"
icon="el-icon-delete"
@click.stop="handleLeftDelete(item)"
/>
</div>
</el-menu-item>
<el-menu-item>
<div class="left" @click.stop="handleAddTag">
<i class="el-icon-edit-outline" />
<span>自定义标签</span>
</div>
</el-menu-item>
</el-menu>
</el-col>
<!-- 右侧资源列表 -->
<el-col :span="18">
<el-card shadow="never">
<div slot="header" class="clearfix">
<span>{{ queryRightParams.name }}</span>
<el-button
style="float: right; padding: 3px 0"
type="text"
icon="el-icon-upload2"
@click="openUploadTypeDialog"
v-if="!(cate_id == 0 && (is_mine == 0 || is_mine == 2))"
>
上传
</el-button>
</div>
<!-- 资源列表 -->
<el-table
:data="list"
height="calc(100vh - 264px)"
:show-header="false"
>
<el-table-column
label="内容"
prop="thumbnail_path"
align="center"
width="250"
>
<template slot-scope="scope">
<el-image
class="resource-thumb"
fit="contain"
:src="getFullFilePath(scope.row.thumbnail_path)"
@click="handlePlay(scope.row)"
>
<div
slot="error"
class="image-slot"
@click="handlePlay(scope.row)"
>
<i :class="getFileIconClass(scope.row.file_type)" />
</div>
</el-image>
</template>
</el-table-column>
<el-table-column
label="标题"
prop="title"
:show-overflow-tooltip="true"
/>
<el-table-column
label="创建时间"
align="center"
prop="create_time"
width="200"
>
<template slot-scope="scope">
<span>{{ parseTime(scope.row.create_time) }}上传</span>
</template>
</el-table-column>
<el-table-column
label="文件大小"
align="center"
prop="file_size"
width="120"
>
<template slot-scope="scope">
<span>{{ formatfile_size(scope.row.file_size) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
width="240"
class-name="small-padding fixed-width"
>
<template slot-scope="scope">
<el-button
type="text"
icon="el-icon-share"
@click="handleShare(scope.row)"
>
分享
</el-button>
<el-button
v-if="scope.row.is_mine == 1"
type="text"
icon="el-icon-edit"
@click="handleUpdateFileName(scope.row)"
>
修改
</el-button>
<el-button
v-if="scope.row.is_mine == 1"
type="text"
icon="el-icon-delete"
@click="handleRightDelete(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="queryRightParams.total > 0"
:total="queryRightParams.total"
:page.sync="queryRightParams.page"
:limit.sync="queryRightParams.pageSize"
@pagination="getKnowledgeList"
/>
</el-card>
</el-col>
</el-row>
<!-- 自定义标签弹窗 -->
<el-dialog
title="自定义标签名称"
:visible.sync="tagDialogVisible"
width="400px"
destroy-on-close
>
<el-form
:model="tagForm"
:rules="tagRules"
ref="tagFormRef"
label-width="80px"
>
<el-form-item label="标签名称" prop="name">
<el-input
v-model="tagForm.name"
placeholder="请输入最多8个字符"
maxlength="8"
show-word-limit
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitTagForm">确认</el-button>
<el-button @click="cancelTagForm">取消</el-button>
</div>
</el-dialog>
<!-- 修改文件名弹窗 -->
<el-dialog
title="修改"
:visible.sync="fileNameDialogVisible"
width="400px"
destroy-on-close
>
<el-form
:model="fileNameForm"
:rules="fileNameRules"
ref="fileNameFormRef"
label-width="80px"
>
<el-form-item label="标题" prop="title">
<el-input
v-model="fileNameForm.title"
placeholder="请输入标题"
show-word-limit
maxlength="8"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileNameForm">确认</el-button>
<el-button @click="cancelFileNameForm">取消</el-button>
</div>
</el-dialog>
<!-- 选择上传类型弹窗 -->
<el-dialog
title="请选择上传文件类型"
:visible.sync="uploadTypeDialogVisible"
width="400px"
:show-footer="false"
>
<div class="upload-type-box">
<div class="type-item" @click="selectFileType('video')">
<i class="el-icon-video-play" />
<span>视频文件(MP4)</span>
</div>
<div class="type-item" @click="selectFileType('pdf')">
<i class="el-icon-document" />
<span>PDF 文件</span>
</div>
</div>
</el-dialog>
<!-- 隐藏的文件输入(用于触发上传) -->
<input
ref="fileInput"
type="file"
:accept="uploadAccept"
class="hidden-upload-input"
@change="handleFileSelect"
/>
<!-- 上传进度弹窗 -->
<el-dialog
title="文件上传中"
:visible.sync="uploadProgressDialogVisible"
width="400px"
:close-on-click-modal="false"
:show-footer="false"
>
<div class="progress-box">
<el-progress :percentage="uploadPercent" />
<p style="margin-top: 15px">资源上传中请稍候...</p>
</div>
</el-dialog>
<!-- 分享弹窗 - 选择联系人 -->
<CreateGroupDialog
ref="createGroupDialogRef"
title="选择人员"
:min-select-count="1"
@confirm="handleShareToContacts"
/>
</div>
</template>
<script>
import {
postKnowledgeCateList,
postKnowledgeCateCreate,
postKnowledgeCateEdit,
postKnowledgeCateDelete,
postKnowledgeList,
postKnowledgeCreate,
postKnowledgeDetail,
postKnowledgePlay,
postKnowledgeEdit,
postKnowledgeDelete,
postMessagePushToUser,
} from "@/api/knowledge";
import { mapGetters } from "vuex";
import CreateGroupDialog from "@/views/message/components/CreateGroupDialog";
import {
uploadFileWithThumbnail,
parseMinioFilePath,
uploadFile,
} from "@/utils/requestMinio";
import { MessageType } from "@/utils/constants";
export default {
name: "Knowledge",
components: {
CreateGroupDialog,
},
computed: {
...mapGetters(["config", "userInfo"]),
},
data() {
return {
// 知识库左侧-默认选中分类
defaultActive: "2",
// 知识库左侧-系统分类
systemCateList: [],
// 知识库左侧-用户分类
userCateList: [],
cateList: [],
// 知识库左侧-自定义标签
tagDialogVisible: false,
// 知识库左侧-自定义标签-表单数据
tagForm: { id: null, name: "" },
// 知识库左侧-自定义标签-表单验证规则
tagRules: {
name: [
{ required: true, message: "标签名称不能为空", trigger: "blur" },
{ max: 8, message: "最多8个字符", trigger: "blur" },
],
},
// 知识库右侧-查询参数
cate_id: null,
is_mine: null,
queryRightParams: {
title: "",
cate_id: 0,
is_mine: "-1",
page: 1,
pageSize: 10,
total: 0,
},
// 知识库右侧-列表
list: [],
// 知识库右侧-文件名弹窗
fileNameDialogVisible: false,
// 知识库右侧-文件名弹窗-表单数据
fileNameForm: { userId: undefined, title: "" },
// 知识库右侧-文件名弹窗-表单验证规则
fileNameRules: {
title: [{ required: true, message: "标题不能为空", trigger: "blur" }],
},
loading: true,
userList: [],
// ================== 上传相关 ==================
uploadTypeDialogVisible: false, // 类型选择弹窗
currentFileType: "", // 当前选择的文件类型 video/pdf
uploadAccept: "",
uploadProgressDialogVisible: false,
uploadPercent: 0,
currentUploadFile: null, // 当前上传的文件对象
// ================== 分享相关 ==================
shareDialogVisible: false, // 分享弹窗
shareItem: null, // 当前分享的文件
};
},
created() {
this.getKnowledgeCateList();
},
methods: {
// 获取完整文件路径
// index.vue - methods
getFullFilePath(relativePath) {
if (!relativePath) return "";
// 1. 如果是完整 URL,直接返回
if (
relativePath.startsWith("http://") ||
relativePath.startsWith("https://")
) {
return relativePath;
}
// 2. 解析服务器返回的 bucket/object 格式路径(C++ 端逻辑)
const { bucket, object } = parseMinioFilePath(relativePath);
if (!bucket || !object) {
console.warn("无效的 MinIO 路径格式:", relativePath);
return relativePath;
}
// 3. 获取 MinIO 配置(从 Vuex 中获取)
const { netConfig } = this.$store.state.user;
const useSSL =
netConfig.MINIO_SECURE === "1" || netConfig.MINIO_SECURE === true;
const protocol = useSSL ? "https" : "http";
const endpoint = netConfig.MINIO_ENDPOINT || "47.92.6.51:9100";
// 4. 拼接完整 URL(与 C++ 端公式一致:protocol://endpoint/bucket/object)
return `${protocol}://${endpoint}/${bucket}/${object}`;
},
// 知识库左侧-图标
getBuiltInIcon(idx) {
return ["el-icon-coin", "el-icon-video-camera", "el-icon-user"][idx];
},
// 知识库左侧-分类列表
getKnowledgeCateList() {
postKnowledgeCateList().then((res) => {
this.systemCateList = res.data.system_cate_list.map((item) => ({
...item,
id: item.id.toString(),
is_mine: item.id.toString(),
type: "system",
}));
this.userCateList = res.data.user_cate_list.map((item) => ({
...item,
id: item.id.toString(),
is_mine: 0,
cate_id: item.id.toString(),
type: "user",
}));
this.cateList = [...this.systemCateList, ...this.userCateList];
console.log("知识库左侧分类列表", res.data);
this.handleSelect(this.cateList[0].id);
});
},
// 知识库左侧-自定义标签-新增
handleAddTag() {
this.tagForm = { name: "" };
this.tagDialogVisible = true;
},
// 知识库左侧-自定义标签-编辑
handleEditTag(t) {
this.tagForm = { ...t, cate_id: t.id };
this.tagDialogVisible = true;
},
// 知识库左侧-自定义标签-提交
submitTagForm() {
this.$refs["tagFormRef"].validate((valid) => {
if (valid) {
if (this.tagForm.id) {
postKnowledgeCateEdit(this.tagForm).then(() => {
this.$modal.msgSuccess("修改成功");
this.tagDialogVisible = false;
this.getKnowledgeCateList();
});
} else {
postKnowledgeCateCreate(this.tagForm).then(() => {
this.$modal.msgSuccess("新增成功");
this.tagDialogVisible = false;
this.getKnowledgeCateList();
});
}
}
});
},
// 知识库左侧-自定义标签-取消
cancelTagForm() {
this.tagDialogVisible = false;
},
// 知识库右侧-查询
handleSelect(key) {
this.defaultActive = key;
// 获取选中的菜单项名称s
const selectedItem = this.findMenuItem(key);
this.queryRightParams.is_mine = selectedItem.is_mine;
this.queryRightParams.cate_id = selectedItem.cate_id;
if (selectedItem) {
this.queryRightParams.name = selectedItem.name;
}
this.handleRightQuery();
},
// 删除资源
handleLeftDelete(row) {
this.$modal
.confirm(
`是否删除该自定义标签?若删除,该标签下资源将迁移至[个人收藏录像]标签下。`
)
.then(function () {
return postKnowledgeCateDelete({ cate_id: row.id });
})
.then(() => {
this.getKnowledgeCateList();
this.$modal.msgSuccess("删除成功");
})
.catch(() => {});
},
// 查找菜单项
findMenuItem(key) {
// 先在系统分类中查找
const item = this.cateList.find((item) => item.id === key);
if (item) return item;
return null;
},
// 知识库右侧-列表
handleRightQuery() {
this.queryRightParams.page = 1;
this.getKnowledgeList();
},
// 知识库右侧-重置查询条件
resetRightQuery() {
this.resetForm("queryRightForm");
this.queryRightParams.title = null;
this.handleRightQuery();
},
// 知识库右侧-文件图标
getFileIconClass(fileName) {
if (!fileName) return "el-icon-document";
const suffix = fileName.split(".").pop().toLowerCase();
const iconMap = {
txt: "el-icon-tickets",
pdf: "el-icon-document-copy",
mp3: "el-icon-headset",
wav: "el-icon-headset",
avi: "el-icon-video-camera",
mp4: "el-icon-video-camera",
wmv: "el-icon-video-camera",
mov: "el-icon-video-camera",
zip: "el-icon-folder-opened",
rar: "el-icon-folder-opened",
ppt: "el-icon-data-board",
pps: "el-icon-data-board",
xls: "el-icon-s-grid",
xlsx: "el-icon-s-grid",
doc: "el-icon-document",
docx: "el-icon-document",
jpg: "el-icon-picture",
jpeg: "el-icon-picture",
jpe: "el-icon-picture",
bmp: "el-icon-picture",
gif: "el-icon-picture",
png: "el-icon-picture",
tif: "el-icon-picture",
tiff: "el-icon-picture",
};
return iconMap[suffix] || "el-icon-document";
},
// 知识库右侧-格式化文件大小
formatfile_size(bytes) {
if (!bytes) return "0B";
const u = ["B", "KB", "MB", "GB"];
let s = bytes,
i = 0;
while (s >= 1024 && i < 3) {
s /= 1024;
i++;
}
return s.toFixed(2) + u[i];
},
// 知识库右侧-列表
getKnowledgeList() {
this.list = [];
postKnowledgeList(this.queryRightParams).then((res) => {
this.list = res.data.list;
this.queryRightParams.total = Number(res.data.total);
this.cate_id = res.data.cate_id;
this.is_mine = res.data.is_mine;
console.log("知识库右侧-列表", this.queryRightParams);
});
},
// 知识库右侧-预览
handlePlay(row) {
postKnowledgePlay({ knowledge_id: row.id }).then((res) => {
window.open(this.getFullFilePath(row.file_path), "_blank");
});
},
handleUpdateFileName(row) {
this.fileNameForm = { title: row.title, knowledge_id: row.id };
this.fileNameDialogVisible = true;
},
submitFileNameForm() {
this.$refs.fileNameFormRef.validate((valid) => {
if (valid) {
postKnowledgeEdit(this.fileNameForm).then(() => {
this.$modal.msgSuccess("修改成功");
this.fileNameDialogVisible = false;
this.getKnowledgeList();
});
}
});
},
cancelFileNameForm() {
this.fileNameDialogVisible = false;
},
// 知识库右侧-删除
handleRightDelete(row) {
this.$modal
.confirm("确认要删除此资源?")
.then(function () {
return postKnowledgeDelete({ knowledge_id: row.id });
})
.then(() => {
this.getKnowledgeList();
this.$modal.msgSuccess("删除成功");
})
.catch(() => {});
},
handleShare(row) {
this.shareItem = row;
this.$refs.createGroupDialogRef.show();
},
handleShareToContacts(selectedMembers) {
console.log("选中的成员:", selectedMembers, this.userInfo);
if (selectedMembers && selectedMembers.length > 0) {
postMessagePushToUser({
client_id: "utalk-client-" + this.userInfo.id,
message: {
at_users: [],
message_id: 0,
payload: {
content: JSON.stringify({
file_name: this.shareItem.file_name,
file_path: this.shareItem.file_path,
file_size: this.shareItem.file_size,
knowledge_id: this.shareItem.id,
thumbnail_path: this.shareItem.thumbnail_path,
file_time: this.shareItem.file_time,
}),
file_duration: 0,
file_ico: "",
file_name: this.shareItem.file_name,
file_path: this.shareItem.file_path,
file_size: this.shareItem.file_size,
file_type: this.shareItem.file_type,
},
scene: 1,
source_id: this.userInfo.id,
target_id: selectedMembers[0].id,
timestamp: 0,
type: "knowledge_share",
},
target_id: selectedMembers[0].id,
topic: "/user/¯",
}).then((res) => {
this.$modal.msgSuccess(`已分享给 ${selectedMembers[0].name}`);
});
this.shareDialogVisible = false;
this.shareItem = null;
}
},
// ====================== 上传核心逻辑 ======================
// 打开类型选择
openUploadTypeDialog() {
this.uploadTypeDialogVisible = true;
},
// 选择文件类型
selectFileType(type) {
this.uploadTypeDialogVisible = false;
this.currentFileType = type;
this.uploadAccept = type === "video" ? "video/mp4" : "application/pdf";
// 触发文件选择
this.$nextTick(() => {
this.$refs.fileInput.click();
});
},
// 文件选择处理
handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
// 文件名校验
const validateResult = this.validateFileName(file.name);
if (!validateResult.valid) {
this.$modal.msgError(validateResult.message);
event.target.value = "";
return;
}
// 文件大小校验(可选,根据需求启用)
// const sizeResult = this.validateFileSize(file.size);
// if (!sizeResult.valid) {
// this.$modal.msgError(sizeResult.message);
// event.target.value = "";
// return;
// }
this.currentUploadFile = file;
this.uploadProgressDialogVisible = true;
this.uploadPercent = 0;
// 创建初始上传记录(无论成功失败都会保存)
const recordId = this.createTransferRecord(file);
// 执行 MinIO 上传
this.uploadToMinio(file, recordId);
// 清空文件输入
event.target.value = "";
},
// 文件名验证
validateFileName(fileName) {
// 1. 检查文件名是否为空
if (!fileName || fileName.trim() === "") {
return { valid: false, message: "文件名不能为空" };
}
// 2. 检查文件名长度(不含扩展名最多20字符)
const nameWithoutExt = fileName.replace(/\.[^/.]+$/, "");
if (nameWithoutExt.length > 20) {
return { valid: false, message: "文件名(不含扩展名)不能超过20个字符" };
}
// 3. 检查是否以数字开头
if (/^\d/.test(nameWithoutExt)) {
return { valid: false, message: "文件名不能以数字开头" };
}
// 4. 检查是否以特殊字符开头
const specialChars = /^[!@#$%^&*()_+\-=\[\]{}|;:,.<>?~`]/;
if (specialChars.test(nameWithoutExt)) {
return { valid: false, message: "文件名不能以特殊字符开头" };
}
// 5. 检查是否包含特殊字符(Windows文件名限制)
const invalidChars = /[\\/:*?"<>|]/;
if (invalidChars.test(fileName)) {
return { valid: false, message: "文件名不能包含 \\ / : * ? \" < > | 等特殊字符" };
}
return { valid: true, message: "" };
},
// 文件大小验证(根据需求启用)
validateFileSize(size) {
// 例如:限制最大100MB
const maxSize = 100 * 1024 * 1024;
if (size > maxSize) {
return { valid: false, message: `文件大小不能超过100MB,当前文件大小:${(size / (1024 * 1024)).toFixed(2)}MB` };
}
return { valid: true, message: "" };
},
// 上传到 MinIO(支持 PDF 缩略图生成)
async uploadToMinio(file, recordId) {
try {
// 确保 MinIO 客户端已初始化
await this.ensureMinioInitialized();
// 根据 is_mine 选择存储桶
const isMine = this.queryRightParams.is_mine == "0";
const bucket = this.config?.MINIO_BUCKET_KNOWLEDGE_PERSONAL;
// 生成文件路径
const timestamp = Date.now();
const fileName = file.name;
const objectName = `${this.currentFileType}/${this.queryRightParams.cate_id}/${timestamp}/${fileName}`;
console.log(`Uploading to bucket: ${bucket}, object: ${objectName}`);
// 使用统一的上传方法,自动判断文件类型并生成缩略图(PDF 和视频)
const result = await uploadFileWithThumbnail(
bucket,
objectName,
file,
{},
(percent) => {
this.uploadPercent = percent;
}
);
console.log("MinIO 上传成功:", result);
// 上传成功后调用业务接口入库(包含缩略图路径)
await this.saveKnowledgeToDB(
result.objectName,
file,
result.thumbnailPath,
bucket,
recordId
);
// 完成上传
this.uploadPercent = 100;
setTimeout(() => {
this.uploadProgressDialogVisible = false;
this.$modal.msgSuccess("上传成功");
this.getKnowledgeList();
}, 500);
} catch (error) {
console.error("上传失败:", error);
this.uploadProgressDialogVisible = false;
this.$modal.msgError("上传失败: " + error.message);
// 更新上传记录为失败状态
this.updateTransferRecord(recordId, false, "");
}
},
// 确保 MinIO 客户端已初始化
async ensureMinioInitialized() {
const config = this.$store.getters.config;
if (!config || !config.MINIO_ENDPOINT) {
console.log("MinIO config not loaded, fetching...");
await this.$store.dispatch("GetNetConfig");
}
},
// 保存知识库记录到业务数据库
async saveKnowledgeToDB(
filePath,
file,
thumbnailPath = "",
bucket = "",
recordId = null
) {
console.log("saveKnowledgeToDB:", filePath, file, thumbnailPath, bucket);
// 构建完整的文件路径(bucket/object 格式)
const fullFilePath = bucket ? `${bucket}/${filePath}` : filePath;
// 构建缩略图路径
const fullThumbnailPath = thumbnailPath
? `${bucket}/${thumbnailPath}`
: "";
console.log("fullThumbnailPath:", fullThumbnailPath);
const data = {
cate_id: this.queryRightParams.cate_id,
file_name: file.name,
file_path: fullFilePath,
file_size: file.size,
file_type: this.currentFileType,
kid: 21,
thumbnail_path:
fullThumbnailPath ||
"personal-test/video/688/1780036088/1633500241136.png",
title: file.name.replace(/\.[^/.]+$/, ""), // 去除扩展名作为标题
true_file_size: file.size,
};
await postKnowledgeCreate(data);
console.log("知识库记录保存成功");
// 更新上传记录为成功状态,保存完整的可预览路径
if (recordId) {
const minioEndpoint =
this.$store.state.user.netConfig?.MINIO_ENDPOINT_HTTPS?.trim() ||
"http://47.92.6.51:9100/";
const fileUrl = minioEndpoint + fullFilePath;
this.updateTransferRecord(recordId, true, fileUrl);
}
},
// 创建初始上传记录(文件选择时调用,无论成功失败都会保存)
createTransferRecord(file) {
const STORAGE_KEY = "file_transfer_records";
try {
// 获取现有记录
const existingData = localStorage.getItem(STORAGE_KEY);
const records = existingData ? JSON.parse(existingData) : [];
// 生成唯一记录 ID
const recordId =
Date.now() + "_" + Math.random().toString(36).substr(2, 9);
// 创建新记录(transfer_type: 3 表示上传)
const newRecord = {
id: recordId,
file_name: file.name,
file_size: file.size,
time: new Date().toISOString(),
transfer_type: 3, // 3 表示上传操作
is_success: false, // 初始状态为未完成
is_uploading: true, // 正在上传中
file_url: "",
};
// 添加到记录数组
records.unshift(newRecord);
// 保存回 localStorage
localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
console.log("上传记录已创建:", newRecord);
return recordId;
} catch (e) {
console.error("创建上传记录失败", e);
return null;
}
},
// 更新上传记录状态
updateTransferRecord(recordId, isSuccess, fileUrl = "") {
const STORAGE_KEY = "file_transfer_records";
try {
// 获取现有记录
const existingData = localStorage.getItem(STORAGE_KEY);
const records = existingData ? JSON.parse(existingData) : [];
// 查找并更新记录
const recordIndex = records.findIndex(
(record) => record.id === recordId
);
if (recordIndex !== -1) {
records[recordIndex].is_success = isSuccess;
records[recordIndex].is_uploading = false; // 上传完成,不再是上传中
if (fileUrl) {
records[recordIndex].file_url = fileUrl;
}
// 保存回 localStorage
localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
console.log("上传记录已更新:", records[recordIndex]);
}
} catch (e) {
console.error("更新上传记录失败", e);
}
},
},
};
</script>
<style lang="scss" scoped>
// 自定义项
.custom-item {
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
align-items: center;
gap: 8px;
}
}
// 资源缩略图
.resource-thumb {
width: 230px;
height: 80px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.hidden-upload-input {
display: none;
}
.upload-type-box {
display: flex;
justify-content: space-around;
padding: 20px 0;
.type-item {
width: 120px;
height: 120px;
border: 1px dashed #ccc;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
i {
font-size: 40px;
color: #409eff;
margin-bottom: 10px;
}
&:hover {
border-color: #409eff;
background: #f5f7fa;
}
}
}
.progress-box {
padding: 20px;
text-align: center;
}
</style>