From 1d552a3bad95caae4af15322df28e6240b797693 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Thu, 13 Aug 2026 15:32:06 +0800
Subject: [PATCH] feat: app视频封面图
---
uniapps/work-app/src/pages/work/index.vue | 876 +++++++++++++++++++++++++++++++++++++++------------------
1 files changed, 595 insertions(+), 281 deletions(-)
diff --git a/uniapps/work-app/src/pages/work/index.vue b/uniapps/work-app/src/pages/work/index.vue
index a0f981e..f9dea90 100644
--- a/uniapps/work-app/src/pages/work/index.vue
+++ b/uniapps/work-app/src/pages/work/index.vue
@@ -1,334 +1,648 @@
<template>
- <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }">
+ <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }">
+ <div class="searchTop">
+ <up-search
+ placeholder="请输入关键字搜索"
+ :animation="true"
+ v-model="listParams.keyword"
+ :show-action="false"
+ @search="handleSearch"
+ @clear="handleClear"
+ ></up-search>
+ </div>
+ <div class="listBox">
+ <div class="tabs-container">
+ <up-tabs :scrollable="true" :list="tabList" @click="handleClick"></up-tabs>
+ </div>
+ <scroll-view class="eventBox" scroll-y :lower-threshold="80" @scrolltolower="loadMore">
+ <div class="eventList">
+ <div class="eventItem" v-for="(item, index) in dataList" :key="index">
+ <image v-if="item.isImage" :src="item.thumbnail" mode="aspectFill" @click="detailHandle(item)" />
+ <div
+ v-else-if="item.isVideo"
+ class="videoBox"
+ :style="item.thumbnailUrl ? { backgroundImage: `url(${item.thumbnailUrl})`, backgroundSize: 'cover', backgroundPosition: 'center' } : {}"
+ @click="detailHandle(item)"
+ >
+ <div class="playIcon"></div>
+ </div>
+ <div class="informationDisplay">
+ <div class="itemTitle">{{ item.eventName }}</div>
+ <div class="itemContent">{{ formatDate(item.createTime) }}</div>
+ </div>
+ </div>
+ </div>
- <div class="searchTop">
- <up-search placeholder="请输入关键字搜索" :animation="true" v-model="listParams.keyword" :show-action="false" @search="handleSearch" @clear="handleClear"></up-search>
- </div>
- <div class="listBox">
- <div class="tabs-container">
- <up-tabs :list="tabList" @click="handleClick"></up-tabs>
- </div>
-<!-- @scrolltolower="loadMore" -->
- <scroll-view class="eventBox" scroll-y :lower-threshold="80" >
- <div class="eventList">
- <div class="eventItem" v-for="(item,index) in dataList" :key="index">
- <img :src="item.eventImageUrl" alt="" @click="detailHandle(item)" />
- <div class="informationDisplay">
- <!-- <div class="itemTitle">{{item.event_name}}</div>-->
- <div class="itemContent">{{formatDate(item.createTime) }}</div>
- </div>
- </div>
- </div>
+ <!-- 暂无数据提示 -->
+ <div class="noData" v-if="!loading && dataList.length === 0">
+ <text>暂无数据</text>
+ </div>
- <!-- 暂无数据提示 -->
- <div class="noData" v-if="!loading && dataList.length === 0">
- <text>暂无数据</text>
- </div>
-
- <!-- 加载提示 -->
- <div class="loadingMore">
- <text v-if="loading">加载中...</text>
-
- </div>
- </scroll-view>
- </div>
-
- </div>
-
+ <!-- 加载提示 -->
+ <div class="loadingMore">
+ <text v-if="loading">加载中...</text>
+ <text v-else-if="!hasMore">没有更多了</text>
+ </div>
+ </scroll-view>
+ </div>
+ <canvas
+ canvas-id="ai-image-canvas"
+ id="ai-image-canvas"
+ class="ai-image-canvas"
+ :width="canvasSize.width"
+ :height="canvasSize.height"
+ :style="{ width: canvasSize.width + 'px', height: canvasSize.height + 'px' }"
+ ></canvas>
+ </div>
</template>
<script setup>
-
+import { getCurrentInstance } from 'vue'
import { useUserStore } from '@/store/index.js'
-import {getGdList,getstatusCount} from '/src/api/work/index.js'
-import dayjs from 'dayjs';
-import { getStatusBarHeight } from '@/utils/common';
+import { getGdList, getstatusCount } from '@/api/work/index.js'
+import dayjs from 'dayjs'
+import { getStatusBarHeight } from '@/utils/common'
+
+const canvasOwner = getCurrentInstance()?.proxy
const userStore = useUserStore()
const userInfo = userStore.userInfo
const dataList = ref([])
-const currentTab=ref('all')
+const currentTab = ref('all')
const loading = ref(false)
const hasMore = ref(true)
+const PREVIEW_IMG_CACHE_KEY_PREFIX = 'work-detail-ai-img-'
+const imageTaskMap = new Map()
+let imageRenderQueue = Promise.resolve()
const tabList = ref([
- {
- name: '全部工单',
- key: 'all',
- badge: {
- value: 0,
- showZero: true
- },
-
- },
- {
- name: '我的工单',
- key: 'myTickets',
- badge: {
- value: 0,
- showZero: true
- }
- }
-
-
+ {
+ name: '全部工单',
+ key: 'all',
+ badge: {
+ value: 0,
+ showZero: true,
+ },
+ },
+ {
+ name: '我的工单',
+ key: 'myTickets',
+ badge: {
+ value: 0,
+ showZero: true,
+ },
+ },
+ {
+ name: '处置中',
+ key: 'disposing',
+ badge: {
+ value: 0,
+ showZero: true,
+ },
+ },
+ {
+ name: '已处置',
+ key: 'handled',
+ badge: {
+ value: 0,
+ showZero: true,
+ },
+ },
+ {
+ name: '已完成',
+ key: 'completed',
+ badge: {
+ value: 0,
+ showZero: true,
+ },
+ },
])
-const formatDate = (dateString) => {
- return dayjs(dateString).format('MM/DD HH:mm');
-};
-const listParams = ref({
-
- current: 1,
- size: 12,
- source: 1,
- department:'',
- keyword:''
-
-})
-const getDataList = () => {
- if (loading.value || !hasMore.value) return
-
- loading.value = true
- const params = {
- current: listParams.value.current,
- size: listParams.value.size,
- keyword:listParams.value.keyword,
- onlyMine:currentTab.value=== 'myTickets' ? 1 : 0
- }
- getGdList(params).then(res => {
- const response = res.data.data
- dataList.value = [
- {
- "id": "2018864760937254914",
- "eventImageUrl": "https://wrj.shuixiongit.com/minio/cloud-bucket/78611c83-8fb3-47c9-a18c-0349137bb30c/DJI_202601171733_001_78611c83-8fb3-47c9-a18c-0349137bb30c/DJI_20260117173534_0004_V.jpeg",
- "createTime": "2026-02-04 09:50:42"
- },
- {
- "id": "2018864819590402049",
- "eventImageUrl": "https://wrj.shuixiongit.com/minio/cloud-bucket/78611c83-8fb3-47c9-a18c-0349137bb30c/DJI_202601171733_001_78611c83-8fb3-47c9-a18c-0349137bb30c/DJI_20260117173534_0004_V.jpeg",
- "createTime": "2026-02-04 09:50:56"
- }
- ]
- // 根据当前页码决定是替换还是追加数据
- // if (listParams.value.current === 1) {
- // dataList.value = response
- // } else {
- // dataList.value = [...dataList.value, ...response]
- // }
-
- // // 判断是否还有更多数据
- // if (response.length < listParams.value.size || response.current >= response.pages) {
- // hasMore.value = false
- // } else {
- // hasMore.value = true
- // }
- }).finally(() => {
- loading.value = false
- })
+// tab key 与事件状态码映射
+const tabStatusMap = {
+ disposing: 1,
+ handled: 3,
+ completed: 4,
}
-const getstatusCountData=()=>{
- const params={
- keyword:listParams.value.keyword,
- }
- getstatusCount(params).then(res=>{
- const response = res.data.data
- const { totalCount,myCount } = response
- tabList.value.forEach(tab=>{
+const formatDate = dateString => {
+ return dayjs(dateString).format('MM/DD HH:mm')
+}
+const listParams = ref({
+ current: 1,
+ size: 12,
+ source: 1,
+ department: '',
+ keyword: '',
+ eventStatus: '',
+})
+let requestSeq = 0 // 请求序列号,用于丢弃快速切换tab时的过期响应
+let imageGeneration = 0 // 图片任务批次,用于取消已离开列表的绘制任务
+const MAX_CANVAS_EDGE = 2048
+const canvasSize = ref({
+ width: MAX_CANVAS_EDGE,
+ height: MAX_CANVAS_EDGE,
+})
- if(tab.key === 'all'){
- tab.badge.value = (totalCount || 0).toString()
- tab.badge.showZero = true
+// 解析算法框数据
+const parseAiFrame = aiFrameSource => {
+ if (!aiFrameSource) return []
+ if (Array.isArray(aiFrameSource)) return aiFrameSource
- }else if(tab.key === 'myTickets'){
- tab.badge.value = (myCount || 0).toString()
- tab.badge.showZero = true
- }
+ try {
+ return JSON.parse(aiFrameSource)
+ } catch (error) {
+ console.log(error)
+ return []
+ }
+}
+
+// 获取图片信息
+const getImageInfo = src =>
+ new Promise(resolve => {
+ uni.getImageInfo({
+ src,
+ success: resolve,
+ fail: error => {
+ console.log(error)
+ resolve(null)
+ },
})
+ })
+// 将画布导出为临时图片
+const canvasToTempFilePath = (width, height) =>
+ new Promise(resolve => {
+ uni.canvasToTempFilePath(
+ {
+ canvasId: 'ai-image-canvas',
+ width,
+ height,
+ destWidth: width,
+ destHeight: height,
+ fileType: 'jpg',
+ quality: 0.92,
+ success: res => resolve(res.tempFilePath),
+ fail: error => {
+ console.log(error)
+ resolve('')
+ },
+ },
+ canvasOwner
+ )
+ })
+
+// 根据图片信息绘制算法框
+const drawAiImage = async (imageInfo, aiFrame, scaleX = 1, scaleY = 1) => {
+ if (!imageInfo?.path || !imageInfo.width || !imageInfo.height || !aiFrame.length) return ''
+ const canvasScale = Math.min(1, MAX_CANVAS_EDGE / imageInfo.width, MAX_CANVAS_EDGE / imageInfo.height)
+ const width = Math.round(imageInfo.width * canvasScale)
+ const height = Math.round(imageInfo.height * canvasScale)
+ const boxScaleX = scaleX * canvasScale
+ const boxScaleY = scaleY * canvasScale
+
+ const ctx = uni.createCanvasContext('ai-image-canvas', canvasOwner)
+ ctx.drawImage(imageInfo.path, 0, 0, width, height)
+
+ aiFrame.forEach(item => {
+ const { x_cen, y_cen, width: boxWidth, height: boxHeight } = item.bbox || {}
+ if ([x_cen, y_cen, boxWidth, boxHeight].some(value => typeof value !== 'number')) return
+
+ const scaledWidth = boxWidth * boxScaleX
+ const scaledHeight = boxHeight * boxScaleY
+ const x = x_cen * boxScaleX - scaledWidth / 2
+ const y = y_cen * boxScaleY - scaledHeight / 2
+ const label = item.class_name || ''
+ const fontSize = Math.max(18, Math.round(width / 80))
+ const labelHeight = fontSize + 10
+ const labelY = y - labelHeight >= 0 ? y - labelHeight : y
+ const lineWidth = Math.max(3, Math.round(width / 640))
+
+ ctx.setStrokeStyle('#FF3B30')
+ ctx.setLineWidth(lineWidth)
+ ctx.strokeRect(x, y, scaledWidth, scaledHeight)
+
+ if (label) {
+ ctx.setFontSize(fontSize)
+ const labelWidth = label.length * fontSize + 16
+ ctx.setFillStyle('#FF3B30')
+ ctx.fillRect(x, labelY, labelWidth, labelHeight)
+ ctx.setFillStyle('#FFFFFF')
+ ctx.setTextBaseline('middle')
+ ctx.fillText(label, x + 8, labelY + labelHeight / 2)
+ }
+ })
+
+ return new Promise(resolve => {
+ ctx.draw(false, async () => {
+ const tempFilePath = await canvasToTempFilePath(width, height)
+ resolve(tempFilePath)
+ })
})
}
-const handleClick = (item) => {
- currentTab.value = item.key
-
- listParams.value.current = 1
- hasMore.value = true
- dataList.value = []
- getDataList()
+// 生成缩略图地址
+const getThumbnailUrl = url => {
+ if (!url) return ''
+ return url.replace(/(\.[^./?]+)(\?.*)?$/, '_thumbnail$1$2')
}
+
+// 前置整理列表媒体字段
+const normalizeWorkItem = item => {
+ const isImage = [1, 2].includes(item.attachmentType)
+ const aiFrame = item.attachmentType === 2 ? parseAiFrame(item.geojson) : []
+ return {
+ ...item,
+ isImage,
+ isVideo: item.attachmentType === 3,
+ thumbnail: item.eventImageUrl,
+ previewUrl: item.eventImageUrl,
+ thumbnailSource: isImage ? getThumbnailUrl(item.eventImageUrl) : '',
+ aiFrame,
+ imageReady: !aiFrame.length,
+ }
+}
+
+// 串行绘制单条 AI 原图和缩略图
+const enqueueAiImages = (item, originalImageInfo, thumbnailImageInfo, generation) => {
+ const task = imageRenderQueue.then(async () => {
+ if (generation !== imageGeneration) return
+ const previewUrl = (await drawAiImage(originalImageInfo, item.aiFrame)) || item.eventImageUrl
+ if (generation !== imageGeneration) return
+ item.previewUrl = previewUrl
+ if (!thumbnailImageInfo) {
+ item.thumbnail = previewUrl
+ return
+ }
+ const scaleX = thumbnailImageInfo.width / originalImageInfo.width
+ const scaleY = thumbnailImageInfo.height / originalImageInfo.height
+ item.thumbnail = (await drawAiImage(thumbnailImageInfo, item.aiFrame, scaleX, scaleY)) || previewUrl
+ })
+ imageRenderQueue = task.catch(() => {})
+ return task
+}
+
+// 异步准备单条记录的缩略图和 AI 图片
+const prepareItemImages = async (item, generation) => {
+ if (!item.isImage || !item.eventImageUrl) return
+ const thumbnailImagePromise = getImageInfo(item.thumbnailSource)
+ if (!item.aiFrame.length) {
+ const thumbnailImageInfo = await thumbnailImagePromise
+ if (generation === imageGeneration && thumbnailImageInfo) item.thumbnail = item.thumbnailSource
+ return
+ }
+
+ const [originalImageInfo, thumbnailImageInfo] = await Promise.all([
+ getImageInfo(item.eventImageUrl),
+ thumbnailImagePromise,
+ ])
+ if (generation !== imageGeneration) return
+ if (thumbnailImageInfo) item.thumbnail = item.thumbnailSource
+ if (!originalImageInfo) return
+ return enqueueAiImages(item, originalImageInfo, thumbnailImageInfo, generation)
+}
+
+// 启动当前页图片后台处理任务
+const startImageTasks = (list, generation) => {
+ list.forEach(item => {
+ const task = prepareItemImages(item, generation)
+ .catch(() => {})
+ .finally(() => {
+ item.imageReady = true
+ })
+ if (item.id) imageTaskMap.set(item.id, task)
+ })
+}
+
+// 获取工单列表
+const getDataList = () => {
+ if (!hasMore.value) return
+
+ // 每次请求自增序列号,响应回来时校验是否过期
+ const seq = ++requestSeq
+ const generation = imageGeneration
+ loading.value = true
+ const params = {
+ current: listParams.value.current,
+ size: listParams.value.size,
+ keyword: listParams.value.keyword,
+ onlyMine: currentTab.value === 'myTickets' ? 1 : 0,
+ eventStatus: listParams.value.eventStatus,
+ }
+ getGdList(params)
+ .then(res => {
+ // 已有新请求发出,丢弃过期响应
+ if (seq !== requestSeq) return
+ const resData = res.data.data
+ const response = resData.records
+ const list = response.map(normalizeWorkItem)
+ let currentPageList = []
+ // 根据当前页码决定是替换还是追加数据
+ if (listParams.value.current === 1) {
+ dataList.value = list
+ currentPageList = dataList.value
+ } else {
+ const startIndex = dataList.value.length
+ dataList.value = [...dataList.value, ...list]
+ currentPageList = dataList.value.slice(startIndex)
+ }
+ // 判断是否还有更多数据
+ if (list.length < listParams.value.size || resData.current >= resData.pages) {
+ hasMore.value = false
+ } else {
+ hasMore.value = true
+ }
+ startImageTasks(currentPageList, generation)
+ })
+ .finally(() => {
+ // 仅在最新一次请求结束时关闭loading,避免过早关闭
+ if (seq === requestSeq) loading.value = false
+ })
+}
+// 获取工单状态数量
+const getstatusCountData = () => {
+ const params = {
+ keyword: listParams.value.keyword,
+ }
+ getstatusCount(params).then(res => {
+ const response = res.data.data
+ const { totalCount, myCount, disposingCount, disposedCount, completedCount } = response
+ tabList.value.forEach(tab => {
+ if (tab.key === 'all') {
+ tab.badge.value = (totalCount || 0).toString()
+ tab.badge.showZero = true
+ } else if (tab.key === 'myTickets') {
+ tab.badge.value = (myCount || 0).toString()
+ tab.badge.showZero = true
+ } else if (tab.key === 'disposing') {
+ tab.badge.value = (disposingCount || 0).toString()
+ tab.badge.showZero = true
+ } else if (tab.key === 'handled') {
+ tab.badge.value = (disposedCount || 0).toString()
+ tab.badge.showZero = true
+ } else if (tab.key === 'completed') {
+ tab.badge.value = (completedCount || 0).toString()
+ tab.badge.showZero = true
+ }
+ })
+ })
+}
+
+// 切换工单状态
+const handleClick = item => {
+ imageGeneration++
+ imageTaskMap.clear()
+ currentTab.value = item.key
+ listParams.value.eventStatus = tabStatusMap[item.key] ?? ''
+
+ listParams.value.current = 1
+ hasMore.value = true
+ dataList.value = []
+ getDataList()
+}
+// 加载更多工单
const loadMore = () => {
- if (loading.value || !hasMore.value) return
- listParams.value.current++
- getDataList()
+ if (loading.value || !hasMore.value) return
+ listParams.value.current++
+ getDataList()
}
-const detailHandle = (val) => {
- uni.navigateTo({
- url: `/subPackages/workDetail/index?id=${val.id}`,
- })
+// 缓存预览原图后跳转详情页
+const detailHandle = async val => {
+ const imageTask = imageTaskMap.get(val.id)
+ if (val.aiFrame.length && !val.imageReady && imageTask) {
+ uni.showLoading({ title: '图片处理中', mask: true })
+ try {
+ await imageTask
+ } finally {
+ uni.hideLoading()
+ }
+ }
+ if (val.aiFrame.length) {
+ uni.setStorageSync(`${PREVIEW_IMG_CACHE_KEY_PREFIX}${val.id}`, val.previewUrl)
+ }
+ uni.navigateTo({ url: `/subPackages/workDetail/index?id=${val.id}` })
}
// 搜索功能
const handleSearch = () => {
- listParams.value.current = 1
- hasMore.value = true
- dataList.value = []
- getDataList()
- getstatusCountData()
+ imageGeneration++
+ imageTaskMap.clear()
+ listParams.value.current = 1
+ hasMore.value = true
+ dataList.value = []
+ getDataList()
+ getstatusCountData()
}
// 清除搜索
const handleClear = () => {
- listParams.value.keyword = ''
- listParams.value.current = 1
- hasMore.value = true
- dataList.value = []
- getDataList()
- getstatusCountData()
+ imageGeneration++
+ imageTaskMap.clear()
+ listParams.value.keyword = ''
+ listParams.value.current = 1
+ hasMore.value = true
+ dataList.value = []
+ getDataList()
+ getstatusCountData()
}
const topMargin = getStatusBarHeight()
+// 页面显示时刷新工单列表
onShow(() => {
- listParams.value.current = 1
- hasMore.value = true
- dataList.value = []
- getDataList()
- getstatusCountData()
+ imageGeneration++
+ imageTaskMap.clear()
+ listParams.value.current = 1
+ hasMore.value = true
+ dataList.value = []
+ getDataList()
+ getstatusCountData()
})
</script>
<style scoped lang="scss">
.eventTickets {
- padding: 0 20rpx;
- display: flex;
- flex-direction: column;
- height: 100%;
- box-sizing: border-box;
- width: 100%;
- overflow-x: hidden;
- .searchTop {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding-right: 24rpx;
- width: 100%;
- height: 108rpx;
- :deep(){
- .u-search__content{
- background: #fff !important;
- }
- .u-search__content__input{
- background: #fff !important;
- }
- }
- }
- :deep(){
+ padding: 0 20rpx;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ box-sizing: border-box;
+ width: 100%;
+ overflow-x: hidden;
+ .searchTop {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-right: 24rpx;
+ width: 100%;
+ height: 108rpx;
+ :deep() {
+ .u-search__content {
+ background: #fff !important;
+ }
+ .u-search__content__input {
+ background: #fff !important;
+ }
+ }
+ }
+ :deep() {
+ .u-tabs__wrapper__nav__line {
+ // width: 80rpx !important;
+ background: #1d6fe9 !important;
+ }
+ .u-badge {
+ background-color: #1d6fe9 !important;
+ margin-top: -14px !important;
+ margin-left: -5px !important;
+ }
+ }
+ tabs-container {
+ display: flex;
+ align-items: center;
+ height: 98rpx;
+ width: 100%;
+ overflow-x: auto; // 横向滚动
+ overflow-y: hidden;
+ white-space: nowrap; // 防止换行
+ -webkit-overflow-scrolling: touch; // 移动端平滑滚动
- .u-tabs__wrapper__nav__line{
- // width: 80rpx !important;
- background: #1D6FE9 !important;
- }
- .u-badge {
- background-color: #1d6fe9 !important;
- margin-top: -14px !important;
- margin-left: -5px !important;
- }
- }
+ // 隐藏滚动条(可选)
+ &::-webkit-scrollbar {
+ height: 0;
+ display: none;
+ }
+ }
+ // 子元素不压缩
+ .tabs-container > * {
+ flex-shrink: 0;
+ }
+ .listBox {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ width: 100%;
+ box-sizing: border-box;
+ }
+ .eventBox {
+ padding: 20rpx 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ height: 0;
+ flex-grow: 1;
+ align-content: flex-start;
+ width: 100%;
+ box-sizing: border-box;
+ }
- .tabs-container{
- display: flex;
- justify-content: center;
- height: 98rpx;
- }
- .listBox {
- position: relative;
- display: flex;
- flex-direction: column;
- flex: 1;
- width: 100%;
- box-sizing: border-box;
- }
- .eventBox {
- padding: 20rpx 0;
- overflow-y: auto;
- overflow-x: hidden;
- height: 0;
- flex-grow: 1;
- align-content: flex-start;
- width: 100%;
- box-sizing: border-box;
- }
+ .eventList {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20rpx;
+ width: 100%;
+ box-sizing: border-box;
+ justify-content: space-between;
- .eventList {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- width: 100%;
- box-sizing: border-box;
- justify-content: space-between;
+ .eventItem {
+ width: calc(50% - 10rpx);
+ background-color: #fff;
+ border-radius: 12rpx;
+ overflow: hidden;
+ position: relative;
+ box-sizing: border-box;
+ max-width: 100%;
- .eventItem {
- width: calc(50% - 10rpx);
- background-color: #fff;
- border-radius: 12rpx;
- overflow: hidden;
- position: relative;
- box-sizing: border-box;
- max-width: 100%;
+ image,
+ .videoBox {
+ width: 100%;
+ height: 208rpx;
+ border-radius: 12rpx;
+ overflow: hidden;
+ display: block;
+ }
+ .videoBox {
+ background: linear-gradient(135deg, #eef4ff 0%, #dfe8f8 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding-bottom: 48rpx;
+ box-sizing: border-box;
+ }
+ .playIcon {
+ width: 72rpx;
+ height: 72rpx;
+ border-radius: 50%;
+ background: rgba(29, 111, 233, 0.9);
+ position: relative;
+ box-shadow: 0 8rpx 18rpx rgba(29, 111, 233, 0.24);
- img {
- width: 100%;
- height: 208rpx;
- border-radius: 12rpx;
- overflow: hidden;
- }
- .informationDisplay{
- width: 100%;
- position: absolute;
- bottom: 3px;
- background: rgba(11,11,11,0.35);
- border-radius: 0rpx 0rpx 12rpx 12rpx;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- padding: 10rpx 0rpx 10rpx 0rpx;
- .itemTitle {
- width: 144rpx;
- font-family: Source Han Sans CN, Source Han Sans CN;
- font-weight: 500;
- font-size: 28rpx;
- color: #000000;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
+ &::after {
+ content: '';
+ position: absolute;
+ left: 29rpx;
+ top: 20rpx;
+ width: 0;
+ height: 0;
+ border-top: 16rpx solid transparent;
+ border-bottom: 16rpx solid transparent;
+ border-left: 22rpx solid #fff;
+ }
+ }
+ .informationDisplay {
+ width: 100%;
+ position: absolute;
+ bottom: 3px;
+ background: rgba(11, 11, 11, 0.35);
+ border-radius: 0rpx 0rpx 12rpx 12rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10rpx 14rpx;
+ box-sizing: border-box;
+ .itemTitle {
+ flex: 1;
+ min-width: 0;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 24rpx;
+ color: #ffffff;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
- .itemContent {
- font-family: Source Han Sans CN, Source Han Sans CN;
- font-weight: 400;
- font-size: 24rpx;
- color: #FFFFFF;
- padding-right: 14rpx;
+ .itemContent {
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 400;
+ font-size: 24rpx;
+ color: #ffffff;
+ margin-left: 12rpx;
+ white-space: nowrap;
+ }
+ }
+ }
+ }
- }
- }
- }
- }
+ .loadingMore {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 80rpx;
+ font-size: 28rpx;
+ color: #999;
+ width: 100%;
+ }
- .loadingMore {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 80rpx;
- font-size: 28rpx;
- color: #999;
- width: 100%;
- }
+ .noData {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 300rpx;
+ font-size: 28rpx;
+ color: #999;
+ width: 100%;
+ }
- .noData {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 300rpx;
- font-size: 28rpx;
- color: #999;
- width: 100%;
- }
+ .ai-image-canvas {
+ position: fixed;
+ left: -9999px;
+ top: -9999px;
+ pointer-events: none;
+ }
}
</style>
--
Gitblit v1.9.3