feat:更新上传文件组件

This commit is contained in:
2026-03-15 21:52:49 +08:00
parent d7d167f3e2
commit 1fb76fc220
20 changed files with 119 additions and 228 deletions

View File

@@ -1,5 +1,6 @@
import { cloudService } from './index'
import { IFileInfo } from '@/types/file'
import type { AxiosProgressEvent } from 'axios'
export const uploadFileApi = (form: FormData, path: string, md5: string): Promise<any> =>
cloudService({
@@ -12,12 +13,14 @@ export const uploadFileApi = (form: FormData, path: string, md5: string): Promis
data: form
})
export const uploadAListFileApi = (form: FormData): Promise<any> =>
export const uploadAListFileApi = (form: FormData,
progress: (event: AxiosProgressEvent) => void): Promise<string> =>
cloudService({
url: '/home-api/file/alist',
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: progress,
timeout: 0,
method: 'post',
data: form

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="mobile-common-query card-item">
<div class="query-item">
<span>分类</span>
<el-segmented v-model="anniversaryStore.queryInfo.category"
@@ -7,27 +7,11 @@
@change="anniversaryStore.queryAnniversaryList()"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="anniversaryStore.queryInfo.category"
placeholder="请选择纪念日类别" clearable
@change="anniversaryStore.queryAnniversaryList()"
style="width: 180px">
<el-option
v-for="(item, index) in queryCategoryList"
:key="index" :label="item.label" :value="item.value"
/>
</el-select>
<el-button :icon="Refresh" circle @click="refreshClick"/>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useAnniversaryStore } from '@/store/anniversary.ts'
import { Refresh } from '@element-plus/icons-vue'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { ICategory } from '@/types'
const anniversaryStore = useAnniversaryStore()
@@ -39,11 +23,6 @@ onMounted(async () => {
initCategoryList()
})
const refreshClick = async () => {
anniversaryStore.queryInfo.category = ''
await anniversaryStore.queryAnniversaryList()
}
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="mobile-common-query card-item">
<div class="query-item">
<span>分类</span>
<el-segmented v-model="assetStore.queryInfo.category"
@@ -7,27 +7,11 @@
@change="assetStore.refreshInfo()"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="assetStore.queryInfo.category"
placeholder="请选择纪念日类别" clearable
@change="assetStore.refreshInfo()"
style="width: 180px">
<el-option
v-for="(item, index) in queryCategoryList"
:key="index" :label="item.label" :value="item.value"
/>
</el-select>
<el-button :icon="Refresh" circle @click="refreshClick"/>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useAssetStore } from '@/store/asset.ts'
import { Refresh } from '@element-plus/icons-vue'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { ICategory } from '@/types'
const assetStore = useAssetStore()
@@ -39,11 +23,6 @@ onMounted(async () => {
initCategoryList()
})
const refreshClick = async () => {
assetStore.queryInfo.category = ''
await assetStore.queryAssetList()
}
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({

View File

@@ -41,7 +41,6 @@ import * as echarts from 'echarts'
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
import { useBillStore } from '@/store/bill.ts'
import { useAppStore } from '@/store/app.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { formatDate } from 'vue3-common/utils/dateUtil'
const billStore = useBillStore()
@@ -111,7 +110,6 @@ const updateChart = () => {
const billCategoryList = billStore.billCategoryStatsList.slice(0, MAX_CATEGORY_COUNT)
billCategoryChart.setOption(pieChartOptions('rank', '元', billCategoryList))
if (isMobile()) {
billCategoryChart.setOption({
series: [{
radius: ['20%', '50%'],
@@ -121,5 +119,4 @@ const updateChart = () => {
}]
})
}
}
</script>

View File

@@ -30,8 +30,6 @@ import { useRouter } from 'vue-router'
import { useAppStore } from '@/store/app.ts'
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
import { IFoodRecord } from '@/types/food.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { isMobile } from 'vue3-common/utils/layoutUtil'
const foodStore = useFoodStore()
const appStore = useAppStore()
@@ -85,18 +83,10 @@ const getFoodDailyByDate = (date: string) => {
*/
const selectFoodRecordClick = async (foodRecord: IFoodRecord) => {
foodStore.foodRecordApiType = 'UPDATE'
if (isMobile()) {
appStore.isShowMobileBack = true
foodStore.currentFoodRecord = foodRecord
appStore.isShowMobileBack = true
await router.push({ name: 'FoodRecordForm' })
} else {
foodStore.foodRecordDialog = {
title: '编辑记录',
visible: true,
data: deepCopyObject(foodRecord)
}
}
}
</script>

View File

@@ -38,8 +38,6 @@
<script setup lang="ts">
import { defineProps, PropType } from 'vue'
import { formatAmount } from 'vue3-common/utils/numberUtil'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { useLedgerStore } from '@/store/ledger.ts'
import { useRouter } from 'vue-router'
import { ILedger, ILegerItem } from '@/types/ledger.ts'

View File

@@ -13,7 +13,6 @@
import { onMounted, ref } from 'vue'
import { useLedgerStore } from '@/store/ledger.ts'
import { ICategory } from '@/types'
import { statsByField } from '@/utils/home/ledgerUtil.ts'
const ledgerStore = useLedgerStore()

View File

@@ -5,9 +5,8 @@
<script lang="ts" setup>
import { onMounted, watch } from 'vue'
import { Calendar, DateSelectArg, EventClickArg } from '@fullcalendar/core'
import { initCommonCalendar, initMobileCalendar } from '@/utils/home/planUtil.ts'
import { initMobileCalendar } from '@/utils/home/planUtil.ts'
import { usePlanStore } from '@/store/plan.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
let calendar: Calendar | undefined
@@ -27,11 +26,7 @@ watch(() => planStore.isRefresh, () => {
const initCalendar = async () => {
const calendarEl = document.getElementById('planCalendar') as HTMLElement
if (isMobile()) {
calendar = initMobileCalendar(calendarEl, selectDateEvent, eventClickEvent, updateDateEvent)
} else {
calendar = initCommonCalendar(calendarEl, selectDateEvent, eventClickEvent, updateDateEvent)
}
calendar.render()
}

View File

@@ -67,11 +67,9 @@ import { defineProps, PropType } from 'vue'
import { AlarmClock, Clock } from '@element-plus/icons-vue'
import { IPlanInfo } from '@/types/plan.ts'
import { getRemainDays, isBeforeToday } from '@/utils/home/planUtil.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { usePlanStore } from '@/store/plan.ts'
import { useRouter } from 'vue-router'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { isMobile } from 'vue3-common/utils/layoutUtil'
const planStore = usePlanStore()
const router = useRouter()
@@ -85,17 +83,8 @@ const props = defineProps({
const editPlanClick = async () => {
planStore.planApiType = 'UPDATE'
if (isMobile()) {
planStore.currentPlan = props.plan as IPlanInfo
await router.push({ name: 'MobileHomePlanDetailId', params: { id: planStore.currentPlan.id } })
} else {
planStore.planDialog = {
title: '编辑计划',
visible: true,
data: deepCopyObject(props.plan)
}
}
}
const changeCompleteEvent = (value: boolean) => {

View File

@@ -16,8 +16,8 @@
@click="viewImageClick(item, index)"/>
<video v-for="(video, index) in item.videoList"
:key="index" controls style="width: 60%;min-height: 200px;">
<source :src="AListUrl + video" type="video/mp4">
:key="index" controls style="width: 100%;">
<source :src="video" type="video/mp4">
您的浏览器不支持 HTML5 视频
</video>
</el-card>
@@ -31,7 +31,6 @@
<script setup lang="ts">
import { SvgIcon } from 'vue3-common'
import { useTravelStore } from '@/store/travel.ts'
import { AListUrl } from '@/utils'
import { showImagePreview } from 'vant'
import { ITravelRecord } from '@/types/travel.ts'

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="mobile-common-query card-item">
<div class="query-item">
<span>城市</span>
<el-segmented v-model="travelStore.querySpot.category"
@@ -7,20 +7,10 @@
@change="queryTravelSegmented"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="travelStore.queryDateType"
@change="travelStore.refreshInfo()"
style="width: 120px;align-self: center;">
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
:label="item.label" :value="item.value"/>
</el-select>
</div>
</template>
<script setup lang="ts">
import { useTravelStore } from '@/store/travel.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { ref, onMounted } from 'vue'
import { ICategory } from '@/types'
@@ -28,15 +18,6 @@ const travelStore = useTravelStore()
const queryCategoryList = ref([] as ICategory[])
const dateTypeOptions = [{
label: '按月选择',
value: 'month'
},
{
label: '按年选择',
value: 'year'
}]
onMounted(async () => {
await travelStore.queryTravelCategory()
initCategoryList()

View File

@@ -15,8 +15,6 @@
<script setup lang="ts">
import { PropType, defineProps, computed } from 'vue'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { useKpiStore } from '@/store/kpi.ts'
import { useAppStore } from '@/store/app.ts'
import { useRouter } from 'vue-router'
@@ -42,16 +40,8 @@ const editClick = async (kpi: IKpi) => {
kpiStore.kpiApiType = 'UPDATE'
kpiStore.currentKpi = kpi
if (isMobile()) {
appStore.isShowMobileBack = true
await router.push({ name: 'KpiDetailId', params: { id } })
} else {
kpiStore.kpiDialog = {
title: '编辑绩效',
visible: true,
data: deepCopyObject(kpiStore.currentKpi)
}
}
}
</script>

View File

@@ -14,7 +14,6 @@ import { getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/ut
import { IDateType, ImagePathEnum } from '@/types'
import { IDialog, IStatsChart, IHandleApi } from 'vue3-common/types'
import { ElMessage } from 'element-plus'
import { isMobile } from 'vue3-common/utils/layoutUtil'
export const useBillStore = defineStore('bill', {
state: () => ({
@@ -125,10 +124,6 @@ export const useBillStore = defineStore('bill', {
break
}
this.billDialog.visible = false
if (!isMobile()) {
await this.refreshInfo()
}
},
getBillDateList() {
const { billYear, billMonth } = this.billQueryForm

View File

@@ -13,7 +13,6 @@ import type { IDialog } from 'vue3-common/types'
import { IHandleApi } from 'vue3-common/types'
import { ElMessage } from 'element-plus'
import { getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil'
import { isMobile } from 'vue3-common/utils/layoutUtil'
export const useFoodStore = defineStore('food', {
state: () => ({
@@ -157,10 +156,6 @@ export const useFoodStore = defineStore('food', {
break
}
this.foodRecordDialog.visible = false
if (!isMobile()) {
await this.refreshInfo()
}
},
async refreshInfo() {
await this.queryFoodRecipeList()

View File

@@ -12,7 +12,6 @@ import {
queryTravelSpotNameListApi, queryTravelStatsApi,
updateTravelRecordApi, updateTravelSpotApi
} from '@/apis/travel'
import { isMobile } from 'vue3-common/utils/layoutUtil'
export const useTravelStore = defineStore('travel', {
state: () => ({
@@ -151,10 +150,6 @@ export const useTravelStore = defineStore('travel', {
break
}
this.travelRecordDialog.visible = false
if (!isMobile()) {
await this.refreshInfo()
}
},
async refreshInfo() {
await this.queryTravelSpotList()

View File

@@ -25,7 +25,7 @@ export const beforeUploadImage = (rawFile: UploadRawFile): boolean => {
}
export const beforeUploadVideo = (rawFile: UploadRawFile): boolean => {
const maxFileMax = 1024 * 1024 * 200
const maxFileMax = 1024 * 1024 * 500
const fileType = rawFile.type
const fileName = rawFile.name
const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()
@@ -65,7 +65,13 @@ export const httpRequestFile = async (basePath: string,
}
data.append('filePath', filePath)
await uploadAListFileApi(data)
await uploadAListFileApi(data, (event) => {
if (event.progress === 1) {
loading.setText('文件上传完成 正在压缩 请等待')
} else {
loading.setText(`文件上传中 当前进度:${(event.progress * 100).toFixed(2)}% 请等待`)
}
})
ElMessage.success('上传成功')
return imageUrl
} catch (error) {

View File

@@ -52,56 +52,6 @@ export const calendarConfig = {
eventResourceEditable: false
}
/**
* 初始化普通视图日程
* @param element 挂载元素
* @param select 日期选择事件
* @param eventClick 事件点击
* @param updateDateEvent 更新日期事件
*/
export const initCommonCalendar = (element: HTMLElement,
select: Function,
eventClick: Function,
updateDateEvent: Function) => {
const { height, slotMinTime, slotMaxTime, allDaySlot, firstDay, initialDate, buttonText,
locale, dayMaxEventRows, editable, selectable, eventStartEditable } = calendarConfig
const plugins = [dayGridPlugin, interactionPlugin, timeGridPlugin]
const initialView = 'dayGridMonth'
const headerToolbar = {
left: 'timeGridDay,timeGridWeek,dayGridMonth',
center: 'title',
right: 'prev,today,next'
}
return new Calendar(element, {
height,
plugins,
initialView,
slotMinTime,
slotMaxTime,
allDaySlot,
firstDay,
initialDate,
headerToolbar,
buttonText,
locale,
slotLabelFormat: {
hour: '2-digit',
minute: '2-digit',
meridiem: false,
hour12: false
},
dayMaxEventRows,
editable,
selectable,
eventStartEditable,
select,
eventClick,
datesSet: updateDateEvent
})
}
export const initMobileCalendar = (element: HTMLElement,
select: Function,
eventClick: Function,

View File

@@ -24,7 +24,7 @@ export const AListPath = import.meta.env.VITE_API_ALIST_PATH
export const ThumbImageQuery = '?q=70'
export const RawImageQuery = '?w=2560&h=1440&q=100'
export const RawImageQuery = '?w=2560&h=1440&q=90'
/**
* 获取assets静态资源

View File

@@ -32,7 +32,6 @@ import LedgerRank from '@/components/Ledger/LedgerRank.vue'
import { useLedgerStore } from '@/store/ledger.ts'
import { onMounted, ref } from 'vue'
import { statsByField } from '@/utils/home/ledgerUtil.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { useAppStore } from '@/store/app.ts'
import * as echarts from 'echarts'
import { pieChartOptions } from 'vue3-common/utils/eChartsUtil'
@@ -60,7 +59,6 @@ const updateChart = () => {
ledgerCategoryChart.setOption(pieChartOptions('rank', '元', categoryData))
ledgerPayerChart.setOption(pieChartOptions('rank', '元', payerData))
if (isMobile()) {
ledgerCategoryChart.setOption({
series: [{
radius: ['20%', '50%'],
@@ -79,5 +77,4 @@ const updateChart = () => {
}]
})
}
}
</script>

View File

@@ -34,12 +34,12 @@
list-type="picture-card"
:accept="acceptImageTypes"
:limit="3"
:before-upload="beforeUpload"
:http-request="httpRequest"
:on-success="handleSuccess"
:on-exceed="handleExceed"
:before-upload="beforeImageUpload"
:http-request="httpImageRequest"
:on-success="handleImageSuccess"
:on-exceed="handleImageExceed"
:before-remove="beforeRemove"
:on-remove="handleRemove"
:on-remove="handleImageRemove"
>
<el-icon><Plus /></el-icon>
<template #tip>
@@ -49,6 +49,27 @@
</template>
</el-upload>
</el-form-item>
<el-form-item label="视频">
<el-upload
v-model:file-list="videoFileList"
:accept="acceptVideoTypes"
:limit="3"
:before-upload="beforeVideoUpload"
:http-request="httpVideoRequest"
:on-success="handleVideoSuccess"
:on-exceed="handleVideoExceed"
:before-remove="beforeRemove"
:on-remove="handleVideoRemove"
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">
请上传小于500M的视频
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<div class="button-container">
@@ -66,9 +87,9 @@ import { ElMessage, ElMessageBox, FormRules, UploadProps, UploadRawFile, UploadR
import { travelRecordRules } from '@/utils/element/elRules.ts'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { useAppStore } from '@/store/app.ts'
import { acceptImageTypes } from '@/utils/file.ts'
import { acceptImageTypes, acceptVideoTypes } from '@/utils/file.ts'
import { Plus } from '@element-plus/icons-vue'
import { beforeUploadImage, httpRequestFile } from '@/utils/element/elUpload.ts'
import { beforeUploadImage, beforeUploadVideo, httpRequestFile } from '@/utils/element/elUpload.ts'
const rules = reactive<FormRules>(travelRecordRules)
@@ -77,44 +98,77 @@ const travelStore = useTravelStore()
const appStore = useAppStore()
const imageFileList = ref<UploadUserFile[]>([])
const videoFileList = ref<UploadUserFile[]>([])
onMounted(async () => {
appStore.isShowMobileBack = true
initImageFileList()
initVideoFileList()
})
const initImageFileList = () => {
imageFileList.value = []
travelStore.currentTravelRecord.imageList.forEach((image) => {
travelStore.currentTravelRecord.imageList.forEach((image, index) => {
imageFileList.value.push({
name: 'image',
name: `image${index + 1}`,
url: image
})
})
}
const beforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
const initVideoFileList = () => {
videoFileList.value = []
travelStore.currentTravelRecord.videoList.forEach((video, index) => {
videoFileList.value.push({
name: `video${index + 1}`,
url: video
})
})
}
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
return beforeUploadImage(rawFile)
}
const httpRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
const httpImageRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
return await httpRequestFile(travelStore.travelImagePath, options)
}
const handleSuccess = (response: string) => {
const handleImageSuccess = (response: string) => {
travelStore.currentTravelRecord.imageList.push(response)
}
const handleExceed: UploadProps['onExceed'] = () => {
const handleImageExceed: UploadProps['onExceed'] = () => {
ElMessage.warning('最多上传3张图片')
}
const handleRemove: UploadProps['onRemove'] = (uploadFile) => {
const handleImageRemove: UploadProps['onRemove'] = (uploadFile) => {
const index = imageFileList.value.findIndex((item) => item.uid === uploadFile.uid)
travelStore.currentTravelRecord.imageList.splice(index, 1)
}
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
return beforeUploadVideo(rawFile)
}
const httpVideoRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
return await httpRequestFile(travelStore.travelImagePath, options)
}
const handleVideoSuccess = (response: string) => {
travelStore.currentTravelRecord.videoList.push(response)
}
const handleVideoExceed: UploadProps['onExceed'] = () => {
ElMessage.warning('最多上传3个视频')
}
const handleVideoRemove: UploadProps['onRemove'] = (uploadFile) => {
const index = videoFileList.value.findIndex((item) => item.uid === uploadFile.uid)
travelStore.currentTravelRecord.videoList.splice(index, 1)
}
const beforeRemove: UploadProps['beforeRemove'] = () => {
return ElMessageBox.confirm('确认删除该文件?').then(() => true, () => false)
}