上传图片组件:
<template>
<div>
<el-upload
v-model:file-list="fileList"
:action="uploadUrl"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:limit="1"
:http-request="uploadFormSuccess"
:before-upload="beforeUpload"
:class="[
fileList.length > 0 ? 'showSelect el-dialog__body' : 'hideSelect el-dialog__body',
props.disableRemove ? 'disable-remove' : 'allow-remove'
]"
>
<el-icon>
<Plus/>
</el-icon>
</el-upload>
<el-dialog
v-model="dialogVisible"
center
append-to-body
align-center
width="'100%'"
>
<img :src="dialogImageUrl" />
</el-dialog>
</div>
</template>
<script setup>
import {ref} from 'vue'
import {ElMessage, dayjs, ElLoading} from "element-plus";
import request from "../../utils/request";
import {globalConfigs} from "../../utils/globalConfigs";
import {imageTypeList, pdfList} from "../../utils/fileTypeList";
import {v4 as uuidv4} from "uuid";
import {uploadWithToken} from "../../api/login";
import {bucketUpload} from "../../utils/fileUpload";
const props = defineProps({
//编辑时传入的图片链接
oldUrl: {
type: String,
default: ''
},
//拼接新文件名的前缀
newfileTitle: {
type: String,
default: ''
},
disableRemove: {
type: Boolean,
default: false
}
})
//预览用的图片链接
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const progressPercent = ref(0)
//上传的文件
const fileList = ref([])
//文件原名称
const oldFileName = ref('')
// 修改 uploadFlag 为 ref,以便能够正确追踪状态
const uploadFlag = ref(false)
let token = ''
if (![null, undefined, ''].includes(props.oldUrl)) {
fileList.value.push({
url: props.oldUrl
})
}
const emits = defineEmits(['success'])
const uploadUrl = globalConfigs.uploadUrl()
const beforeUpload = async (file) => {
if (uploadFlag.value) {
return false
}
uploadFlag.value = true
oldFileName.value = file.name
if (!imageTypeList.hasOwnProperty(oldFileName.value.split(".")[oldFileName.value.split(".").length - 1])) {
ElMessage({type: "error", message: "仅支持上传图片!"})
uploadFlag.value = false
return false;
}
//uuid随机生成
let uuid = uuidv4()
let query = {
uuid
}
let res = await uploadWithToken(query)
if(!res){
uploadFlag.value = false
return false
}
token = res
return true
}
const handlePictureCardPreview = (uploadFile) => {
dialogImageUrl.value = uploadFile.url || uploadFile.response?.url
dialogVisible.value = true
}
function changeRadioValue(value) {
if (![null, undefined, ''].includes(value) && fileList.value.length < 1) fileList.value.push({url: value})
}
watch(() => props.oldUrl, value => changeRadioValue(value))
const handleRemove = () => {
fileList.value = []
emits('success', '')
}
const beforeRemove = ()=>{
if(props.disableRemove){
return false
}
}
const uploadFormSuccess = async (options) => {
const { file, onProgress, onSuccess, onError } = options
bucketUpload(file,
(res) => {
let successRes = {
url:res.aliyunAddress,
filename:res.name,
oldFilename:oldFileName.value,
}
emits('success',successRes)
uploadFlag.value = false
onSuccess(res)
},
(err) => {
onError(err) // 让 el-upload 知道失败
ElMessage.error('上传失败')
uploadFlag.value = false
},
)
};
</script>
<style scoped>
:deep(.showSelect .el-upload--picture-card) {
display: none;
}
:deep(.showSelect .el-upload-list__item-actions) {
display: inline-flex;
}
:deep(.hideSelect .el-upload--picture-card) {
display: inline-flex;
}
:deep(.hideSelect .el-upload-list__item-actions) {
display: none;
}
.fullscreen-dialog {
margin: 0 !important; /* 移除默认的上下外边距 */
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
}
:deep(.el-dialog__body){
padding: 30px 20px;
color: #606266;
font-size: 14px;
word-break: break-all;
}
:deep(.el-upload-list__item .el-upload-list__item-actions .el-upload-list__item-delete){
display: none;
}
:deep(.disable-remove .el-upload-list__item .el-upload-list__item-actions .el-upload-list__item-delete) {
display: none;
}
:deep(.allow-remove .el-upload-list__item .el-upload-list__item-actions .el-upload-list__item-delete) {
display: inline-flex;
}
</style>
上传文件组件:
<template>
<div class="upload-container">
<el-upload
:action="uploadUrl"
ref="upload"
:limit="1"
:http-request="uploadFormSuccess"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
>
<!-- 按钮上传-->
<el-button
icon="Plus"
type="primary"
style="width: 200px"
>{{buttonName}}</el-button>
</el-upload>
<span v-if="oldFile && showOldFileName" class="file-name">{{ oldFile }}</span>
</div>
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid'
import { ElMessage, genFileId} from "element-plus";
import {getCurrentInstance, ref} from 'vue';
import {globalConfigs} from "../../utils/globalConfigs";
import {imageTypeList, pdfList, videoTypeList} from "../../utils/fileTypeList";
import {uploadWithToken} from "../../api/login";
import {bucketUpload} from "../../utils/fileUpload";
const props = defineProps({
oldFilename:{
type:String,
default:''
},
newFileTitle:{
type:String,
default:''
},
onlyVideo:{
type:Boolean,
default:false
},
onlyPdf:{
type:Boolean,
default:false
},
onlyPic:{
type:Boolean,
default:false
},
// 按钮旁是否显示文件名
showOldFileName:{
type:Boolean,
default:true
},
buttonName:{
type:String,
default:'上传'
},
needExcced:{
type:Boolean,
default:true
}
})
const visible = ref(false)
const uploadUrl = globalConfigs.uploadUrl()
const emits = defineEmits(['success'])
//oldFile为当前原名称
const oldFile = ref(props.oldFilename)
const progressPercent = ref(0)
const upload = ref()
const { proxy } = getCurrentInstance();
let token = ''
const handleExceed = (files) => {
if(props.needExcced){
upload.value.clearFiles()
const file = files[0]
file.uid = genFileId()
upload.value.handleStart(file)
}
}
const beforeUpload = async (file) => {
upload.value.clearFiles()
if(props.onlyVideo){
if (!videoTypeList.hasOwnProperty(file.name.split('.')[file.name.split('.').length - 1])) {
ElMessage.error('仅支持上传视频')
return false
}
}
if(props.onlyPic){
if (!imageTypeList.hasOwnProperty(file.name.split('.')[file.name.split('.').length - 1])) {
ElMessage.error('仅支持上传图片')
return false
}
}
//uuid随机生成
let uuid = uuidv4()
let query = {
uuid
}
let res = await uploadWithToken(query)
if(!res){
return false
}
token = res
return true
}
const uploadFormSuccess = async (options) => {
const { file, onProgress, onSuccess, onError } = options
oldFile.value = file.name
bucketUpload(file,
(res) => {
let successRes = {
url:res.aliyunAddress,
oldFilename:oldFile.value,
filename:res.name,
}
emits('success',successRes)
onSuccess(res) // 让 el-upload 知道成功
proxy.$modal.msgSuccess("上传成功");
},
(err) => {
onError(err) // 让 el-upload 知道失败
proxy.$modal.msgError("上传失败");
},
)
};
function changeRadioValue(value){
oldFile.value = value
}
watch(() => props.oldFilename, value => changeRadioValue(value))
</script>
<style scoped>
.upload-container {
display: flex;
align-items: center;
gap: 10px;
}
.file-name {
color: #606266;
}
</style>
上传文件列表:
<template>
<div>
<div class="top">
<div style="width: 250px">请输入页码后再上传</div>
<el-input-number v-model="picPages" style="width: 350px;margin-right: 30px" />
<upload-button ref="uploadButton" @success="getUrl" :only-pic="true" :filename-use-old-file="true" :show-old-file-name="false" :need-excced="false"></upload-button>
</div>
<el-table
v-loading="loading"
:data="dataList"
>
<el-table-column label="图片" align="center" prop="fileUrl">
<template #default="scope">
<template v-if="scope.row.fileUrl">
<el-image
style="width: 100px; height: 100px"
:src="scope.row.fileUrl"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="srcList(scope.row.fileUrl)"
:z-index="9999"
preview-teleported
/>
</template>
<template v-else>
{{scope.row.fileUrl}}
</template>
</template>
</el-table-column>
<el-table-column label="页码" align="center" prop="pages" />
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
fixed="right"
:min-width="120"
>
<template #default="scope">
<el-button
link
type="primary"
@click="handleEditPage(scope.row)"
:icon="Edit"
>修改页码</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row)"
:icon="Delete"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog
v-model="innerVisible"
width="300"
title="修改页码"
append-to-body
>
<el-input-number v-model="innerForm.pages" style="width: 150px" />
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {getCurrentInstance} from "vue";
import {Delete,Edit} from '@element-plus/icons-vue';
import {reactive, toRefs} from "vue";
import {
addOfficialKnowledgeFile,
deleteOfficialKnowledgeFile,
listOfficialKnowledgeFile, putOfficialKnowledgeFile
} from "../../api/official/knowledge";
import UploadButton from "../uploadButton/uploadButton.vue";
const {proxy} = getCurrentInstance()
//弹窗
const innerVisible = ref(false)
const innerForm = ref({})
//表格
const state = reactive({
loading: false,
total: 0,
dataList: [],
queryParams: {
pageNum: 1,
pageSize: 100,
},
});
const picPages = ref(1)
const addIndex = ref(0)
const emits = defineEmits(['getFileList'])
// 将响应式数据解构出来
const { loading, total, dataList, queryParams} = toRefs(state);
const form = ref({})
//弹窗
function cancel() {
innerForm.value = {}
innerVisible.value = false
}
async function submitForm() {
const pk = queryParams.value.pkKnowledge
if ([null, undefined, ''].includes(pk)) {
// 新增主表记录 修改页码
for(let i=0;i<dataList.value.length;i++){
if(dataList.value[i].index === innerForm.value.index){
dataList.value[i].pages = innerForm.value.pages
cancel()
return
}
}
} else {
// 修改主表记录 修改页码
await putOfficialKnowledgeFile(innerForm.value)
getList()
cancel()
}
}
//表格
const srcList = (url) =>{
let list = []
list.push(url)
return list
}
//上传获取url及文件原名 新名
const getUrl = async (res) => {
const pk = queryParams.value.pkKnowledge
form.value = {}
form.value.fileUrl = res.url
form.value.fileName = res.filename
form.value.pages = picPages.value
if ([null, undefined, ''].includes(pk)) {
form.value.index = addIndex.value
dataList.value.push(form.value)
addIndex.value++
picPages.value = 1
emits('getFileList', dataList.value)
} else {
form.value.pkKnowledge = pk
await addOfficialKnowledgeFile(form.value)
picPages.value = 1
getList()
}
}
const getList = async (query) => {
if(query) queryParams.value.pkKnowledge = query.pkKnowledge
state.loading = true;
let response = await listOfficialKnowledgeFile(queryParams.value)
state.dataList = response.rows;
state.total = response.total;
state.loading = false;
};
function handleEditPage(row) {
innerVisible.value = true
innerForm.value = JSON.parse(JSON.stringify(row))
}
function handleDelete(row){
const pk = queryParams.value.pkKnowledge
if([null,undefined,''].includes(pk)){
// dataList去除此条记录
dataList.value.splice(dataList.value.indexOf(row),1)
emits('getFileList',dataList.value)
}else{
proxy.$modal.confirm('是否确认页码为"' + row.pages + '"的数据项?,删除后将无法恢复!').then(function () {
return deleteOfficialKnowledgeFile(row.pkKnowledgeFile);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => { });
}
}
defineExpose({
getList
})
</script>
<style scoped>
:deep(.el-table tr){
height: 50px;
}
.top{
width: 650px;
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
}
</style>
上传阿里云js:
import request from "@/utils/request";
import OSS from "ali-oss";
import {dayjs} from "element-plus";
//生成uuid,防止同名文件被覆盖
function getUUID() {
return dayjs().format("YYYYMMDD") + Math.floor(Math.random() * 100000000)
}
/**
* 阿里云oss sdk文件上传
* @param {*} file 文件流
* @param {*} successCallback 成功回调
* @param {*} errCallBack 失败回调
* @param {*} bucketName 阿里云桶名(可以指定多个桶名)
* @param {*} dir 上传文件夹路径 譬如images
*/
export function bucketUpload(
file,
successCallback = new Function(),
errCallBack = new Function(),
dir = ""
) {
let fileName = file.name;
// 先获取上传要的资料签名
request({
method: "get",
url: "/auth/token/getOssToken", // 返回new OSS需要的参数
})
.then((res) => {
let obj = res || {};
let config = {};
config.host = "oss-cn-shanghai.aliyuncs.com";
// 实例化一个上传客户端
const client = new OSS({
// yourRegion填写Bucket所在地域。比如oss-cn-hangzhou。
region: "oss-cn-shanghai",
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
accessKeyId: obj.accessKeyId,
accessKeySecret: obj.accessKeySecret,
// 从STS服务获取的安全令牌(SecurityToken)。
stsToken: obj.securityToken,
// 填写Bucket名称。
bucket: "",
// 添加以下配置项以使用 HTTPS
secure: true,
});
try {
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
// 您可以通过自定义文件名(例如exampleobject.txt)或文件完整路径(例如exampledir/exampleobject.txt)的形式实现将数据上传到当前Bucket或Bucket中的指定目录。
// data对象可以自定义为file对象、Blob数据或者OSS Buffer。
// 为保证唯一性,通过uuid将文件名替换
let uuid = getUUID() + fileName.substring(fileName.lastIndexOf("."));
if (dir.substring(dir.length - 1, 1) !== "/") {
dir += "/";
}
const result = client.put(dir + uuid, file);
result.then(async (res) => {
let size =
file.size > 1000000
? parseFloat(file.size / 1000000).toFixed(2) + "M"
: parseFloat(file.size / 1000).toFixed(2) + "KB";
//获取私有地址
let privateUrl = await request({
method: "get",
url: "/auth/token/getOssPri",
params: {
fileName: res.name,
},
});
successCallback({
name: res.name,
attachment: fileName,
aliyunAddress: privateUrl.data,
size: size,
host: config.host,
});
})
.catch((err) => {
errCallBack(err);
});
} catch (e) {
console.log(e);
}
})
.catch((err) => {
errCallBack(err);
});
}
翻书动画:
<template>
<div class="turn-container">
<div class="turn-banner">
<div class="turn-content">
<div id="flipbook">
<el-image v-for="(item, index) in images" :key="index" fit="fill" :src="item.url" alt="" srcset="" />
</div>
</div>
<div class="slider-bar">
<div v-for="(item, index) in images" :key="index" class="slider" :class="{ 'slider-current': index + 1 == currentPage }" @click="toPage(index)"></div>
</div>
</div>
</div>
</template>
<script>
import { ref, nextTick, onMounted } from 'vue'
import turn from '@/utils/turn.js'
import $ from 'jquery'
import {getKnowledge} from "@/api/waterSupply/knowledge";
import {useRoute} from "vue-router";
export default {
components: {},
setup() {
const route = useRoute()
const currentPage = ref(1)
const images = ref([])
onMounted(async () => {
await getDetail()
onTurn()
})
const onTurn = () => {
nextTick(() => {
$('#flipbook').turn({
autoCenter: true,
height: 646, //高度
width: 996, //宽度
display: 'double', //单页显示/双页显示 single/double
elevation: 50,
duration: 500, //翻页速度(毫秒), 默认600ms
gradients: true, //翻页时的阴影渐变, 默认true
autoCenter: true, //自动居中, 默认false
acceleration: true, //硬件加速, 默认true, 如果是触摸设备设置为true
page: 1, //设置当前显示第几页
pages: images.value.length, //总页数
when: {
//监听事件
turning: function (e, page, view) {
// console.log(e, page, view)
// 翻页前触发
},
turned: function (e, page) {
// console.log(e, page)
currentPage.value = page
// 翻页后触发
},
},
})
})
}
const toPage = i => {
currentPage.value = i + 1
$('#flipbook').turn('page', currentPage.value) //进度条跳转到对应的页数
}
async function getDetail() {
try {
let res = await getKnowledge(route.query.pk);
if (!res || res.code !== 200) {
return;
}
let knowledgeFiles = res.data.knowledgeFiles;
for(let i=0;i<knowledgeFiles.length;i++){
images.value.push({
url: knowledgeFiles[i].fileUrl,
});
}
} catch (e) {
}
}
return {
images,
toPage,
getDetail,
currentPage,
}
},
}
</script>
<style lang="scss" scoped>
.turn-banner {
width: 100%;
height: 700px;
.turn-content {
display: flex;
margin: 0px auto;
overflow: hidden;
}
}
.slider-bar {
width: 900px;
height: 8px;
border-radius: 5px;
background-color: #ccc;
margin-top: 15px;
display: flex;
overflow: hidden;
.slider {
flex: 1;
cursor: pointer;
}
.slider-current {
background-color: #666;
border-radius: 5px;
}
}
</style>