This commit is contained in:
2026-07-27 17:19:36 +08:00
parent e50d1b8fda
commit 1c7532e0de
132 changed files with 7066 additions and 1241 deletions

View File

@@ -48,6 +48,11 @@ export default {
&__card {
width: 100%;
background: none;
> .el-card__body {
padding: 0;
}
}
&:first-child {

View File

@@ -13,7 +13,7 @@
<div class="cron-config-area">
<!-- 每天模式 -->
<div v-if="cronMode === 'daily'" class="mode-content">
<el-form :model="dailyConfig" label-width="100px" size="default">
<el-form :model="dailyConfig" label-width="auto" size="default">
<el-form-item label="执行时间">
<el-time-picker
v-model="dailyConfig.time"
@@ -30,7 +30,7 @@
<!-- 每周模式 -->
<div v-if="cronMode === 'weekly'" class="mode-content">
<el-form :model="weeklyConfig" label-width="100px" size="default">
<el-form :model="weeklyConfig" label-width="auto" size="default">
<el-form-item label="星期选择">
<el-checkbox-group v-model="weeklyConfig.weekDays" @change="generateCron" :disabled="disabled">
<el-checkbox label="1">周一</el-checkbox>
@@ -58,7 +58,7 @@
<!-- 每月模式 -->
<div v-if="cronMode === 'monthly'" class="mode-content">
<el-form :model="monthlyConfig" label-width="100px" size="default">
<el-form :model="monthlyConfig" label-width="auto" size="default">
<el-form-item label="日期选择">
<el-select
v-model="monthlyConfig.days"
@@ -93,7 +93,7 @@
<!-- 自定义模式 -->
<div v-if="cronMode === 'custom'" class="mode-content">
<el-form :model="customConfig" label-width="100px" size="default">
<el-form :model="customConfig" label-width="auto" size="default">
<el-form-item label="秒">
<el-input
v-model="customConfig.second"
@@ -161,7 +161,7 @@
</div>
<div class="cron-result">
<el-form label-width="120px">
<el-form label-width="auto">
<el-form-item label="Cron 表达式">
<el-input
v-model="cronExpression"
@@ -567,7 +567,7 @@ export default {
font-family: 'Courier New', monospace;
font-size: 14px;
font-weight: 600;
color: #409eff;
color: var(--el-color-primary);
}
:deep(.el-checkbox) {

View File

@@ -3,10 +3,10 @@
<el-pagination
background
:current-page="page.currentPage"
:page-size="page.pageSize"
:page-size="pageSize"
:page-sizes="pageSizes"
layout="total, sizes, prev, pager, next, jumper"
:total="0"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
@@ -27,9 +27,15 @@ export default {
isEmptyTotal() {
return Number(this.page.total || 0) === 0;
},
pageSize() {
return this.page.pageSize || 10;
},
pageSizes() {
return this.page.pageSizes || [10, 20, 50, 100];
},
total() {
return Number(this.page.total || 0);
},
},
methods: {
handleSizeChange(pageSize) {
@@ -51,7 +57,6 @@ export default {
.empty-pagination {
display: flex;
justify-content: flex-end;
padding: 20px 0 0;
background: #fff;
}
</style>

View File

@@ -8,7 +8,7 @@
:show-close="false"
width="20%"
>
<el-form :model="form" ref="form" label-width="80px">
<el-form :model="form" ref="form" label-width="auto">
<el-form-item v-if="tenantMode" label="租户编号">
<el-input v-model="form.tenantId" placeholder="请输入租户编号"></el-input>
</el-form-item>

View File

@@ -0,0 +1,364 @@
<template>
<div class="vehicle-attachment-upload">
<el-upload
:action="uploadAction"
name="file"
:headers="uploadHeaders"
:file-list="fileList"
:accept="acceptText"
:multiple="multiple"
:limit="limit"
:disabled="readonly"
:show-file-list="true"
:on-success="handleSuccess"
:on-error="handleError"
:on-remove="handleRemove"
:on-preview="handlePreview"
:before-upload="beforeUpload"
>
<el-button v-if="!readonly" type="primary" plain :icon="buttonIcon">
{{ buttonText }}
</el-button>
<template #tip>
<div v-if="showTip && tipText && !readonly" class="el-upload__tip">
{{ tipText }}
</div>
</template>
</el-upload>
<span v-if="readonly && fileList.length === 0" class="vehicle-attachment-upload__empty">
-
</span>
<el-dialog
v-model="previewVisible"
:title="previewFile.name || '附件预览'"
append-to-body
destroy-on-close
width="90%"
top="4vh"
class="vehicle-attachment-upload__viewer-dialog"
>
<open-file-viewer
v-if="previewVisible && previewSource"
:file="previewSource"
:file-name="previewFile.name"
:mime-type="previewFile.mimeType"
width="100%"
height="72vh"
fit="contain"
theme="auto"
locale="zh-CN"
:toolbar="viewerToolbar"
:plugins="viewerPlugins"
@unsupported="handleUnsupported"
@error="handleViewerError"
/>
</el-dialog>
</div>
</template>
<script>
import { OpenFileViewer } from '@open-file-viewer/vue';
import {
fallbackPlugin,
imagePlugin,
officePlugin,
pdfPlugin,
textPlugin,
} from '@open-file-viewer/core';
import '@open-file-viewer/core/style.css';
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
import { baseUrl } from '@/config/env';
import website from '@/config/website';
import { getToken } from '@/utils/auth';
const MIME_MAP = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
bmp: 'image/bmp',
webp: 'image/webp',
pdf: 'application/pdf',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
txt: 'text/plain',
};
const viewerPlugins = [
imagePlugin(),
pdfPlugin({
workerSrc: pdfWorkerSrc,
useFetchData: true,
}),
officePlugin({
pdf: {
workerSrc: pdfWorkerSrc,
useFetchData: true,
},
}),
textPlugin(),
fallbackPlugin(),
];
export default {
name: 'VehicleAttachmentUpload',
components: {
OpenFileViewer,
},
props: {
modelValue: {
type: [Array, String, Object],
default: () => [],
},
action: {
type: String,
default: '',
},
accept: {
type: [String, Array],
default: '',
},
fileTypes: {
type: Array,
default: () => [],
},
multiple: {
type: Boolean,
default: true,
},
limit: {
type: Number,
default: 20,
},
maxSize: {
type: Number,
default: 0,
},
readonly: {
type: Boolean,
default: false,
},
buttonText: {
type: String,
default: '上传附件',
},
buttonIcon: {
type: String,
default: 'el-icon-upload',
},
tip: {
type: String,
default: '',
},
showTip: {
type: Boolean,
default: true,
},
},
emits: ['update:modelValue', 'change', 'success'],
data() {
return {
fileList: [],
previewVisible: false,
previewFile: {},
previewSource: '',
viewerToolbar: {
download: true,
fullscreen: true,
print: true,
rotate: true,
zoom: true,
},
viewerPlugins,
};
},
computed: {
uploadAction() {
return this.action || `${baseUrl}/blade-resource/oss/endpoint/put-file`;
},
uploadHeaders() {
return {
[website.tokenHeader]: getToken(),
};
},
acceptedList() {
const types = this.fileTypes.length ? this.fileTypes : this.accept;
if (Array.isArray(types)) {
return types.map(item => String(item).trim()).filter(Boolean);
}
return String(types || '')
.split(',')
.map(item => item.trim())
.filter(Boolean);
},
acceptText() {
return this.acceptedList.join(',');
},
tipText() {
if (this.tip) return this.tip;
const typeText = this.acceptText || '所有类型';
const sizeText = this.maxSize > 0 ? `,单文件不超过 ${this.maxSize}MB` : '';
return `支持上传 ${typeText}${sizeText}`;
},
},
watch: {
modelValue: {
handler(value) {
this.fileList = this.toUploadList(value);
},
immediate: true,
deep: true,
},
},
methods: {
toUploadList(value) {
const list = this.parseValue(value);
return list.map((item, index) => {
const url = this.getFileUrl(item);
return {
...item,
uid: item.uid || `${url || item.name || 'file'}-${index}`,
name: item.name || this.getFileName(url) || '附件',
url,
status: 'success',
};
});
},
parseValue(value) {
if (!value) return [];
if (Array.isArray(value)) return value.filter(Boolean);
if (typeof value === 'object') return [value];
try {
const list = JSON.parse(value);
return Array.isArray(list) ? list : [];
} catch (error) {
return String(value)
.split(',')
.map(item => item.trim())
.filter(Boolean)
.map(item => ({
name: this.getFileName(item),
url: item,
}));
}
},
getFileUrl(file) {
return file.url || file.link || file.src || file.domain || '';
},
getFileName(url) {
if (!url) return '';
const path = String(url).split('?')[0];
return decodeURIComponent(path.substring(path.lastIndexOf('/') + 1));
},
getExtension(file) {
const name = String(file.name || this.getFileUrl(file) || '').split('?')[0];
const index = name.lastIndexOf('.');
return index >= 0 ? name.substring(index + 1).toLowerCase() : '';
},
beforeUpload(file) {
if (!this.validFileType(file)) {
this.$message.warning('文件类型不符合上传要求');
return false;
}
if (this.maxSize > 0 && file.size / 1024 / 1024 > this.maxSize) {
this.$message.warning(`单文件大小不能超过 ${this.maxSize}MB`);
return false;
}
return true;
},
validFileType(file) {
if (!this.acceptedList.length) return true;
const fileName = String(file.name || '').toLowerCase();
const mimeType = String(file.type || '').toLowerCase();
return this.acceptedList.some(type => {
const value = String(type).toLowerCase();
if (value === '*/*') return true;
if (value.startsWith('.')) return fileName.endsWith(value);
if (value.endsWith('/*')) return mimeType.startsWith(value.slice(0, -1));
if (value.includes('/')) return mimeType === value;
return fileName.endsWith(`.${value}`);
});
},
handleSuccess(response, file, files) {
if (!response || response.success === false || (response.code && response.code !== 200)) {
this.$message.error((response && response.msg) || '上传失败');
return;
}
this.emitUploadFiles(this.multiple ? files : [file]);
this.$emit('success', this.normalizeUploadFile(file));
this.$message.success('上传成功');
},
handleError() {
this.$message.error('上传失败');
},
handleRemove(file, files) {
if (this.readonly) return false;
this.emitUploadFiles(files);
return true;
},
emitUploadFiles(files) {
const list = files
.filter(file => file.status === 'success' || this.getFileUrl(file))
.map(this.normalizeUploadFile)
.filter(item => item.url);
this.$emit('update:modelValue', list);
this.$emit('change', list);
},
normalizeUploadFile(file) {
const data = (file.response && file.response.data) || file.data || file;
const url = data.link || data.url || data.domain || file.url || '';
const name = data.name || data.originalName || file.name || this.getFileName(url) || '附件';
const extension = this.getExtension({ name, url });
return {
...data,
uid: file.uid || data.uid,
name,
url,
link: data.link || url,
size: data.size || data.attachSize || file.size || '',
extension,
mimeType: data.mimeType || data.contentType || file.raw?.type || MIME_MAP[extension] || '',
};
},
handlePreview(file) {
const previewFile = this.normalizeUploadFile(file);
if (!previewFile.url) {
this.$message.warning('附件地址为空,无法预览');
return;
}
this.previewFile = previewFile;
this.previewSource = previewFile.url;
this.previewVisible = true;
},
handleUnsupported() {
this.$message.warning('当前文件暂不支持在线预览');
},
handleViewerError() {
this.$message.error('附件预览失败');
},
},
};
</script>
<style lang="scss" scoped>
.vehicle-attachment-upload {
width: 100%;
:deep(.el-upload-list__item-name) {
cursor: pointer;
}
&__empty {
color: #909399;
}
}
:global(.vehicle-attachment-upload__viewer-dialog .el-dialog__body) {
padding: 12px 16px 16px;
}
</style>