上传照片

master
lzq 2025-08-22 18:06:32 +08:00
parent 15981273b0
commit 444c7d3684
8 changed files with 733 additions and 386 deletions

View File

@ -0,0 +1,363 @@
<script lang="ts" setup>
import { useFormGroup } from '@idux/cdk'
import times, { FMT } from '@/common/utils/times.ts'
import {
TableColumn,
TableColumnSelectable
} from '@idux/components/table'
import { TablePagination } from '@idux/components/table/src/types'
import TspApi from '@/pages/tsp/tsp-api.ts'
import TspPhotoApi from '@/pages/tsp-photo/tsp-photo-api.ts'
const props = defineProps<{
tspPhotoData: TspPhotoTypes.TspPhotoSearchParam
status: 'edit' | 'view'
}>()
const emit = defineEmits([ 'update:tspPhotoData' ])
const weekday = [ '周一', '周二', '周三', '周四', '周五', '周六', '周日' ]
const days = ref<Array<Array<TspPhotoTypes.DayType | null>>>([])
const tspPhotoForm = useFormGroup<TspPhotoTypes.TspPhotoSearchParam>({
tspId: [ '' ],
pointName: [ '' ],
month: [ new Date() ],
day: [ new Date().getDate() ],
uploadDate: [ times.format(new Date(), FMT.date) ],
})
const selectedRowKeys = ref<string[]>([])
const tableSpin = ref(false)
const datasource = ref<TspTypes.TspData[]>([])
const selectableColumn = reactive<TableColumnSelectable<TspTypes.TspData>>({
type: 'selectable',
align: 'center',
multiple: false,
showIndex: false,
trigger: 'click',
onChange: (selectedKeys) => {
if (selectedKeys.length === 0) {
const value = tspPhotoForm.get('tspId')?.getValue()
if (value != null) {
selectedRowKeys.value = [ value ]
} else {
tspPhotoForm.get('tspId')?.setValue('')
tspPhotoForm.get('pointName')?.setValue('')
}
return
}
const data = datasource.value?.find(item => item.id === (selectedKeys[0] as string))
tspPhotoForm.get('tspId')?.setValue(selectedKeys[0] as string)
tspPhotoForm.get('pointName')?.setValue(data?.pointName)
},
})
const columns: TableColumn<TspTypes.TspData>[] = [
selectableColumn,
{
title: '名称',
dataKey: 'pointName',
customCell: 'pointName'
},
{
title: '所属街道',
dataKey: 'streetName',
},
{
title: '所属小区',
dataKey: 'microdistrict',
},
{
title: '所属物业',
dataKey: 'propertyManagement'
},
]
function searchHandler() {
tableSpin.value = true
return TspApi.paging({
current: pagination.pageIndex ?? 1,
size: pagination.pageSize ?? 10,
}).then(res => {
pagination.pageIndex = res.data.current
pagination.pageSize = res.data.size
pagination.total = res.data.total
datasource.value = res.data.records
return res.data.records
}).finally(() => {
tableSpin.value = false
})
}
const pagination = reactive<TablePagination>({
pageIndex: 1,
pageSize: 10,
total: 0,
size: 'sm',
showTotal: true,
onChange(pageIndex: number, pageSize: number) {
pagination.pageIndex = pageIndex
pagination.pageSize = pageSize
searchHandler()
}
})
const visible = ref(false)
function disabledDate(date: Date) {
return times.parse(date).month > times.now().month
}
watch(visible, (newVal) => {
if (newVal) {
searchHandler()
}
})
function selectDayHandler(day: TspPhotoTypes.DayType) {
const date = `${day.year}-${day.month < 10 ? '0' + day.month : day.month}-${day.day < 10 ? '0' + day.day : day.day}`
const dateObj = times.parse(date, FMT.date)
if (dateObj.diffNow().toMillis() > 0) {
return
}
tspPhotoForm.get('day')?.setValue(day.day)
tspPhotoForm.get('uploadDate')?.setValue(date)
emit('update:tspPhotoData', tspPhotoForm.getValue())
}
const photoUploadStatus = ref<TspPhotoTypes.ObtainStatusResult>({})
function computedStatus(day: number) {
const count = photoUploadStatus.value[day]
return count != null && count > 0 ? 'uploaded'
: count != null && count === 0 ? 'unupload'
: 'unknown'
}
function obtainStatus() {
TspPhotoApi.obtainStatus(tspPhotoForm.getValue()).then(res => {
photoUploadStatus.value = res.data
computedCalendar()
})
}
function computedCalendar() {
const value: Date = tspPhotoForm.get('month')?.getValue() ?? new Date()
const beginOfMonth = times.beginOfMonth(times.parse(value))
const endOfMonth = times.endOfMonth(times.parse(value))
const firstDayIndex = beginOfMonth.weekday - 1
const yu_shu = (endOfMonth.day + firstDayIndex) % 7
const count = yu_shu > 0 ? (7 - yu_shu) + (endOfMonth.day + firstDayIndex) : (endOfMonth.day + firstDayIndex)
const days_: Array<Array<TspPhotoTypes.DayType | null>> = []
let temp: Array<TspPhotoTypes.DayType | null> = []
for (let i = 0; i < count; i++) {
if (i < firstDayIndex) {
temp.push(null)
} else if (i >= firstDayIndex && i < firstDayIndex + endOfMonth.day) {
const day = i - firstDayIndex + 1
temp.push({
year: beginOfMonth.year,
month: beginOfMonth.month,
day: day,
status: computedStatus(day),
photoCount: photoUploadStatus.value[day] ?? 0
})
} else {
temp.push(null)
}
if (temp.length === 7) {
days_.push(temp)
temp = []
}
}
days.value = days_
}
tspPhotoForm.get('tspId')?.watchValue(() => {
obtainStatus()
emit('update:tspPhotoData', tspPhotoForm.getValue())
})
tspPhotoForm.get('month')?.watchValue((month) => {
if (month == null) {
return
}
tspPhotoForm.get('day')?.setValue(1)
tspPhotoForm.get('uploadDate')?.setValue(times.format(times.parse(month), FMT.month) + '-01')
obtainStatus()
emit('update:tspPhotoData', tspPhotoForm.getValue())
})
onMounted(() => {
if (props.status === 'edit') {
searchHandler()
.then((data) => {
if (data.length === 0) {
return
}
selectedRowKeys.value = [ data[0].id ]
tspPhotoForm.get('tspId')?.setValue(data[0].id)
tspPhotoForm.get('pointName')?.setValue(data[0].pointName)
})
} else {
tspPhotoForm.get('tspId')?.setValue(props.tspPhotoData.tspId)
tspPhotoForm.get('pointName')?.setValue(props.tspPhotoData.pointName)
}
})
defineExpose({
refresh() {
obtainStatus()
}
})
</script>
<template>
<div class="calendar-wrapper">
<div class="calendar-title">
<IxForm :control="tspPhotoForm" class="calendar-form" layout="inline">
<IxFormItem>
<IxInput v-if="status === 'view'" control="pointName" readonly/>
<IxPopover v-if="status === 'edit'" v-model:visible="visible" closable header="小区列表" placement="bottom" trigger="click">
<IxInput control="pointName" placeholder="请选择小区"/>
<template #content>
<IxTable v-model:selectedRowKeys="selectedRowKeys" :columns="columns" :dataSource="datasource" :pagination="pagination" :spin="tableSpin" get-key="id"/>
</template>
</IxPopover>
</IxFormItem>
<IxFormItem>
<IxDatePicker :disabled-date="disabledDate" control="month" type="month" @change="computedCalendar"/>
</IxFormItem>
</IxForm>
</div>
<table class="calendar">
<thead>
<tr>
<th v-for="item in weekday" :key="'weekday-' + item" class="weekday">{{ item }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in days" :key="'day-' + index">
<td v-for="(it, i) in item" :key="'day-' + i" :class="{blank:it == null}" class="day">
<div v-if="it != null" :class="{'active-day': it.day === tspPhotoData.day && it.month === (tspPhotoData.month?.getMonth()??-1) + 1 && it.year === tspPhotoData.month?.getFullYear()}" class="day-content" @click="selectDayHandler(it)">
<div class="day-number">
<div>{{ it.day }}</div>
<div>{{ it.day === new Date().getDate() && it.month === new Date().getMonth() + 1 && it.year === new Date().getFullYear() ? '今' : '' }}</div>
</div>
<IxTagGroup v-if="it.status === 'uploaded'" :gap="0" class="day-status">
<IxTag status="success">已上传</IxTag>
<IxTag status="success">{{ it.photoCount }}</IxTag>
</IxTagGroup>
<IxTag v-else-if="it.status === 'unupload'" class="day-status" status="error">未上传</IxTag>
<div v-else class="day-status"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style lang="stylus" scoped>
.calendar-wrapper {
flex 1
padding 1rem
box-sizing border-box
border-right: 1px solid rgb(211, 215, 222);
.calendar-form {
width 100%
height 4rem
:deep(.ix-form-item):first-child {
width 30%
}
:deep(.ix-form-item):nth-child(2) {
width 10%
}
}
.calendar {
width 100%
height calc(100% - 4rem);
margin-top 1rem
box-sizing border-box
table-layout fixed
border-collapse: collapse;
border: 1px solid rgb(211, 215, 222);
thead {
height 4rem
th {
text-align center
}
}
tbody {
height calc(100% - 4rem);
}
.day {
border-top: 1px solid rgb(211, 215, 222);
&:not(.blank) {
border: 1px solid rgb(211, 215, 222);
}
.day-content {
cursor pointer
height 100%
width 100%
display flex
flex-direction column
justify-content space-around
padding 1rem
&:hover {
background-color: rgba(59, 130, 246, 0.1);
}
&.active-day {
background-color: rgba(59, 130, 246, 0.1);
}
.day-number {
font-size 3rem
font-weight bold
display: flex
justify-content: space-between;
& > div:last-child {
color #165DFF
}
}
.day-status {
width 100%;
height 20px;
:deep(.ix-space-item) {
flex 1
& > span {
width 100%
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,252 @@
<script lang="ts" setup>
import { uploadBaseUrl } from '@/common'
import { UploadFile } from '@idux/components'
import AppApi from '@/common/app/app-api.ts'
import Toast from '@/components/toast'
import TspPhotoApi from '@/pages/tsp-photo/tsp-photo-api.ts'
import { ref } from 'vue'
import times, { FMT } from '@/common/utils/times.ts'
const props = defineProps<{
tspId?: string
pointName?: string
uploadDate?: string
status: 'edit' | 'view'
}>()
const emit = defineEmits([ 'refresh' ])
const photoListTitle = computed(() => {
return props.uploadDate == null || props.pointName == null ? '图片列表'
: props.pointName + times.format(times.parse(props.uploadDate, FMT.date), FMT.date_zh) + '上传的图片'
})
const files = ref<UploadFile[]>([])
const fileUrls = ref<{ [key: string]: string }>({})
async function requestData(file: UploadFile) {
if (file.raw == null) return Promise.reject()
let res = await AppApi.obtainPresignedUrl(file.name)
const url = '/' + res.data.bucketName + '/' + res.data.objectName
file.thumbUrl = AppApi.fileUrl(url)
const key = file.key as string
fileUrls.value[key] = url
delete res.data.bucketName
delete res.data.objectName
return Promise.resolve(res.data)
}
function fileStatusChange(file: UploadFile) {
if (file.status === 'success') {
Toast.success('上传成功')
} else if (file.status === 'error') {
Toast.error('上传失败')
}
}
function delPhoto(index: number) {
const key = files.value[index].key as string
delete fileUrls.value[key]
files.value.splice(index, 1)
}
watch(
() => props.uploadDate,
(val) => {
if (val == null) {
files.value = []
return
}
TspPhotoApi.listPhoto({
tspId: props.tspId,
uploadDate: props.uploadDate,
}).then(res => {
let data = res.data?.photos ?? []
let temp: UploadFile[] = []
for (let datum of data) {
temp.push({
key: datum,
name: datum,
status: 'success',
thumbUrl: AppApi.fileUrl(datum),
})
}
files.value = temp
})
})
function submitPhoto() {
TspPhotoApi.save({
tspId: props.tspId,
uploadDate: props.uploadDate,
photos: files.value.map(it => fileUrls.value[it.key as string]).filter(it => it != null),
}).then(() => {
Toast.success('提交成功')
emit('refresh')
})
}
</script>
<template>
<div class="photo-list-container">
<div class="photo-title">
{{ photoListTitle }}
</div>
<div v-if="status === 'view'" class="photo-list">
<IxImage v-for="(it,i) in files" :key="'photo--'+i" :src="it.status==='success' ? it.thumbUrl ?? '' : ''">
<template #previewIcon>
<div class="photo-preview">
<IxIcon name="plus-circle"/>
</div>
</template>
</IxImage>
</div>
<!--<div v-else-if="status === 'edit' && uploadDate != null">
<div style="height: 100px;width: 100px;background-color: #00B42A" v-for="(it,i) in files" :key="'photo&#45;&#45;'+i"></div>
</div>-->
<IxUpload
v-else-if="status === 'edit' && uploadDate != null"
v-model:files="files"
:action="uploadBaseUrl"
:progress="{strokeColor: {
'0%': '#108EE9',
'100%': '#87D068',
},
strokeWidth: 3,
}"
:request-data="requestData"
dragable
multiple
@file-status-change="fileStatusChange">
<div class="drag-panel">
<IxIcon class="drag-panel-icon" name="upload"/>
<p>点击或拖拽上传</p>
</div>
<template #list>
<div v-if="files.length > 0" class="photo-list">
<IxImage v-for="(it,i) in files" :key="'photo--'+i" :src="it.status==='success' ? it.thumbUrl ?? '' : ''">
<template #previewIcon>
<div class="photo-preview">
<IxIcon name="plus-circle"/>
<IxIcon name="delete" @click.stop="delPhoto(i)"/>
</div>
</template>
</IxImage>
</div>
<IxEmpty v-else/>
</template>
</IxUpload>
<IxEmpty v-else description="请选择小区和日期"/>
<div v-if="status === 'edit' && uploadDate != null" class="photo-bottom">
<IxPopconfirm placement="top" title="是否提交?" @ok="submitPhoto">
<IxButton :disabled="files.length === 0" mode="primary">提交修改</IxButton>
<template #content>
<div>小区{{ pointName }}</div>
<div>日期{{ uploadDate }}</div>
<div>图片数量{{ files.length }}</div>
</template>
</IxPopconfirm>
</div>
</div>
</template>
<style lang="stylus" scoped>
.photo-list-container {
width 30%
height 100%
display flex
flex-direction column
.photo-title {
font-size 2rem
font-weight bold
padding 1rem
box-sizing border-box
border-bottom 1px solid rgb(211, 215, 222);
text-wrap: auto;
word-break: break-word;
overflow-wrap: normal;
line-height: 1.6;
}
:deep(.ix-upload) {
flex 1
display: flex
flex-direction: column;
height: 0;
.ix-upload-selector {
height 6rem
width 100%;
margin-bottom 1rem
.drag-panel {
height 100%
width 100%;
display: flex;
align-items: center;
justify-content: center;
gap 1rem
&-icon {
font-size: 24px;
}
p {
font-size: 14px;
margin: 8px 0 0 0;
}
&:hover {
color: #1C6EFF;
}
}
}
}
.photo-list {
flex 1
width 100%;
padding 1rem
overflow auto
display grid
grid-template-columns repeat(2, 1fr)
grid-auto-rows: 8rem
gap 1rem
align-items stretch
& > div {
height: 8rem;
box-sizing: border-box;
}
.photo-preview {
display: flex
color: white;
font-size: 2rem;
gap: 1rem;
}
}
.photo-bottom {
display flex
justify-content space-evenly
align-items center
height 10rem
gap 5%
padding 1rem
& > button {
width 10rem
}
}
}
</style>

View File

@ -1,258 +1,20 @@
<script lang="ts" setup>
import times, { FMT } from '@/common/utils/times.ts'
import { ref } from 'vue'
import {
TableColumn,
TableColumnSelectable
} from '@idux/components/table'
import { TablePagination } from '@idux/components/table/src/types'
import TspApi from '@/pages/tsp/tsp-api.ts'
import { useFormGroup, } from '@idux/cdk'
import TspPhotoApi from '@/pages/tsp-photo/tsp-photo-api.ts'
import PhotoCalendar from '@/pages/tsp-photo/PhotoCalendar.vue'
import PhotoList from '@/pages/tsp-photo/PhotoList.vue'
const weekday = [ '周一', '周二', '周三', '周四', '周五', '周六', '周日' ]
const tspPhotoData = ref<TspPhotoTypes.TspPhotoSearchParam>({})
const photoCalendar = ref<InstanceType<typeof PhotoCalendar> | null>(null)
interface DayType {
year: number
month: number
day: number
function refresh() {
photoCalendar.value?.refresh()
}
const visible = ref(false)
const selectedRowKeys = ref<string[]>([])
const tspPhotoForm = useFormGroup<TspPhotoTypes.TspPhotoSearchParam>({
tspId: [ undefined ],
pointName: [ undefined ],
month: [ new Date() ],
day: [ 0 ],
uploadDate: [ times.format(new Date(), FMT.date) ],
})
const days = ref<Array<Array<DayType | null>>>([])
function renderCalendar(value: Date = new Date()) {
const beginOfMonth = times.beginOfMonth(times.parse(value))
const endOfMonth = times.endOfMonth(times.parse(value))
const firstDayIndex = beginOfMonth.weekday - 1
const count = (7 - (endOfMonth.day + firstDayIndex) % 7) + (endOfMonth.day + firstDayIndex)
const days_: Array<Array<DayType | null>> = []
let temp: Array<DayType | null> = []
for (let i = 0; i < count; i++) {
if (i < firstDayIndex) {
temp.push(null)
} else if (i >= firstDayIndex && i < firstDayIndex + endOfMonth.day) {
temp.push({
year: beginOfMonth.year,
month: beginOfMonth.month,
day: i - firstDayIndex + 1
})
} else {
temp.push(null)
}
if (temp.length === 7) {
days_.push(temp)
temp = []
}
}
days.value = days_
}
function disabledDate(date: Date) {
return times.parse(date).month > times.now().month
}
const datasource = ref<TspTypes.TspData[]>()
const selectableColumn = reactive<TableColumnSelectable<TspTypes.TspData>>({
type: 'selectable',
align: 'center',
multiple: false,
showIndex: false,
trigger: 'click',
onChange: (selectedKeys) => {
if (selectedKeys.length === 0) {
const value = tspPhotoForm.get('tspId')?.getValue()
if (value != null) {
selectedRowKeys.value = [ value ]
} else {
tspPhotoForm.get('tspId')?.setValue('')
tspPhotoForm.get('pointName')?.setValue('')
}
return
}
const data = datasource.value?.find(item => item.id === (selectedKeys[0] as string))
tspPhotoForm.get('tspId')?.setValue(selectedKeys[0] as string)
tspPhotoForm.get('pointName')?.setValue(data?.pointName)
},
})
const tableSpin = ref(false)
const pagination = reactive<TablePagination>({
pageIndex: 1,
pageSize: 10,
total: 0,
size: 'sm',
showTotal: true,
onChange(pageIndex: number, pageSize: number) {
pagination.pageIndex = pageIndex
pagination.pageSize = pageSize
searchHandler()
}
})
function searchHandler() {
tableSpin.value = true
TspApi.paging({
current: pagination.pageIndex ?? 1,
size: pagination.pageSize ?? 10,
})
.then(res => {
pagination.pageIndex = res.data.current
pagination.pageSize = res.data.size
pagination.total = res.data.total
datasource.value = res.data.records
})
.finally(() => {
tableSpin.value = false
})
}
const photoUploadStatus = ref<TspPhotoTypes.ObtainStatusResult>({})
const columns: TableColumn<TspTypes.TspData>[] = [
selectableColumn,
{
title: '名称',
dataKey: 'pointName',
customCell: 'pointName'
},
{
title: '所属街道',
dataKey: 'streetName',
},
{
title: '所属小区',
dataKey: 'microdistrict',
},
{
title: '所属物业',
dataKey: 'propertyManagement'
},
]
watch(visible, (newVal) => {
if (newVal) {
searchHandler()
}
})
const computedTitle = () => {
const uploadDate = tspPhotoForm.get('uploadDate')?.getValue()
const pointName = tspPhotoForm.get('pointName')?.getValue()
return uploadDate == null || pointName == null ? '图片列表' : pointName + ' ' + times.format(times.parse(uploadDate, FMT.date), FMT.date_zh) + '上传的图片'
}
const photoListTitle = ref<string>('图片列表')
const photoList = ref<string[]>()
tspPhotoForm.get('tspId')?.watchValue(() => {
TspPhotoApi.obtainStatus(tspPhotoForm.getValue()).then(res => {
photoUploadStatus.value = res.data
})
})
tspPhotoForm.get('month')?.watchValue((month) => {
tspPhotoForm.get('day')?.setValue(0)
if (month == null) {
return
}
tspPhotoForm.get('uploadDate')?.setValue(times.format(times.parse(month), FMT.date))
TspPhotoApi.obtainStatus(tspPhotoForm.getValue()).then(res => {
photoUploadStatus.value = res.data
})
})
function onClickDate(day: DayType) {
tspPhotoForm.get('day')?.setValue(day.day)
tspPhotoForm.get('uploadDate')?.setValue(`${day.year}-${day.month < 10 ? '0' + day.month : day.month}-${day.day < 10 ? '0' + day.day : day.day}`)
photoListTitle.value = computedTitle()
TspPhotoApi.listPhoto(tspPhotoForm.getValue())
.then(res => {
photoList.value = res.data?.photos ?? []
})
}
const files = ref([])
onMounted(() => {
renderCalendar()
})
</script>
<template>
<div class="tsp-photo-wrapper">
<div class="calendar-wrapper">
<div class="calendar-title">
<IxForm :control="tspPhotoForm" class="calendar-form" layout="inline">
<IxFormItem>
<IxPopover v-model:visible="visible" closable header="小区列表" placement="bottom" trigger="click">
<IxInput control="pointName" placeholder="请选择小区"/>
<template #content>
<IxTable v-model:selectedRowKeys="selectedRowKeys" :columns="columns" :dataSource="datasource" :pagination="pagination" :spin="tableSpin" get-key="id"/>
</template>
</IxPopover>
</IxFormItem>
<IxFormItem>
<IxDatePicker :disabled-date="disabledDate" control="month" type="month" @change="renderCalendar"/>
</IxFormItem>
</IxForm>
</div>
<table class="calendar">
<thead>
<tr>
<th v-for="item in weekday" :key="'weekday-' + item" class="weekday">{{ item }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in days" :key="'day-' + index">
<td v-for="(it, i) in item" :key="'day-' + i" :class="{blank:it == null}" class="day">
<div v-if="it != null" :class="{'active-day': it.day === tspPhotoForm.get('day')?.getValue()}" class="day-content" @click="onClickDate(it)">
<div class="day-number">{{ it.day }}</div>
<IxTagGroup v-if="photoUploadStatus[it.day+''] != null && photoUploadStatus[it.day+''] > 0" :gap="0" class="day-status">
<IxTag status="success">已上传</IxTag>
<IxTag status="success">{{ photoUploadStatus[it.day + ''] }}</IxTag>
</IxTagGroup>
<IxTag v-else-if="photoUploadStatus[it.day+''] != null && photoUploadStatus[it.day+''] == 0" class="day-status" status="error">未上传</IxTag>
<div v-else class="day-status" status="error"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="photo-wrapper">
<div class="photo-title">
{{ photoListTitle }}
</div>
<div class="photo">
<IxImage v-for="(it,i) in photoList" :key="'photo--'+i" :src="it"/>
</div>
<div v-if="tspPhotoForm.get('day')?.getValue() !== 0" class="photo-bottom">
<IxUpload v-model:files="files" action="https://run.mocky.io/v3/7564bc4f-780e-43f7-bc58-467959ae3354" dragable>
<div class="drag-panel">
<IxIcon class="drag-panel-icon" name="upload"></IxIcon>
<p>拖拽上传</p>
</div>
<template #list>
<IxUploadFiles type="text"/>
</template>
</IxUpload>
<IxButton>提交</IxButton>
</div>
</div>
<PhotoCalendar ref="photoCalendar" v-model:tspPhotoData="tspPhotoData" status="edit"/>
<PhotoList :pointName="tspPhotoData.pointName" :tspId="tspPhotoData.tspId"
:uploadDate="tspPhotoData.uploadDate" status="edit" @refresh="refresh"/>
</div>
</template>
@ -261,131 +23,5 @@ onMounted(() => {
height: 100%
width: 100%
display flex
.calendar-wrapper {
flex 1
padding 1rem
box-sizing border-box
border-right: 1px solid rgb(211, 215, 222);
.calendar-form {
width 100%
height 4rem
:deep(.ix-form-item):first-child {
width 30%
}
:deep(.ix-form-item):nth-child(2) {
width 10%
}
}
.calendar {
width 100%
height calc(100% - 4rem);
margin-top 1rem
box-sizing border-box
table-layout fixed
border-collapse: collapse;
border: 1px solid rgb(211, 215, 222);
thead {
height 4rem
}
tbody {
height calc(100% - 4rem);
}
.day {
border-top: 1px solid rgb(211, 215, 222);
&:not(.blank) {
border: 1px solid rgb(211, 215, 222);
}
.day-content {
cursor pointer
height 100%
width 100%
display flex
flex-direction column
justify-content space-around
padding 1rem
&:hover {
background-color: rgba(59, 130, 246, 0.1);
}
&.active-day {
background-color: rgba(59, 130, 246, 0.1);
}
.day-number {
font-size 3rem
font-weight bold
}
.day-status {
width 100%;
height 20px;
:deep(.ix-space-item) {
flex 1
& > span {
width 100%
}
}
}
}
}
}
}
.photo-wrapper {
width 30%
height 100%
display flex
flex-direction column
.photo-title {
font-size 2rem
font-weight bold
padding 1rem
box-sizing border-box
border-bottom 1px solid rgb(211, 215, 222);
text-wrap: auto;
word-break: break-word;
overflow-wrap: normal;
overflow: hidden;
line-height: 1.6;
}
.photo {
flex 1
width 100%;
padding 1rem
overflow-y auto
display grid
grid-template-columns repeat(2, 1fr)
grid-auto-rows: 8rem
gap 1rem
align-items stretch
& > div {
height: 8rem;
box-sizing: border-box;
}
}
}
}
</style>

View File

@ -1,8 +1,11 @@
import { get, } from '@/common/utils/http-util.ts'
import {
get,
post,
} from '@/common/utils/http-util.ts'
export default {
save(data?: TspPhotoTypes.TspPhotoSaveParam) {
return get<null>('/tsp_photo/save', data)
return post<null>('/tsp_photo/save', data)
},
listPhoto(data?: TspPhotoTypes.TspPhotoSearchParam) {
return get<TspPhotoTypes.TspPhotoSearchResult>('/tsp_photo/list_photo', data)

View File

@ -1,6 +1,17 @@
export {}
declare global {
namespace TspPhotoTypes {
interface DayType {
year: number
month: number
day: number
status: 'uploaded' | 'unupload' | 'unknown'
photoCount: number
}
interface TspPhotoSearchParam {
tspId?: string
pointName?: string

View File

@ -0,0 +1,78 @@
<script lang="ts" setup>
import PhotoCalendar from '@/pages/tsp-photo/PhotoCalendar.vue'
import PhotoList from '@/pages/tsp-photo/PhotoList.vue'
import { nanoid } from 'nanoid'
const id = nanoid()
const show = ref(false)
const containerElement = ref<HTMLElement | null>(null)
const tspPhotoData = ref<TspPhotoTypes.TspPhotoSearchParam>({})
const header = ref<string>('图片列表')
function closeHandler() {
containerElement.value!.style!.width = '0'
containerElement.value!.style!.height = '0'
tspPhotoData.value = {}
show.value = false
}
defineExpose({
open(tspId: string, pointName: string) {
tspPhotoData.value.tspId = tspId
tspPhotoData.value.pointName = pointName
show.value = true
containerElement.value!.style!.width = '100%'
containerElement.value!.style!.height = '100%'
}
})
</script>
<template>
<div :id="id" ref="containerElement" class="ix_modal-container">
<IxModal v-model:visible="show" :container="containerElement!"
:header="header" :mask="false"
:on-before-close="closeHandler"
type="default" width="100%">
<div class="photo-panel">
<PhotoCalendar ref="photoCalendar" v-model:tspPhotoData="tspPhotoData" status="view"/>
<PhotoList :pointName="tspPhotoData.pointName" :tspId="tspPhotoData.tspId"
:uploadDate="tspPhotoData.uploadDate" status="view"/>
</div>
<template #footer>
<IxButton mode="primary" @click="closeHandler"></IxButton>
</template>
</IxModal>
</div>
</template>
<style lang="stylus" scoped>
.ix_modal-container {
position absolute
top 0
left 0
:deep(.ix-modal) {
width 100%
height 100%
.ix-modal-body {
width 100%
overflow hidden
border-top: 1px dashed #E1E5EB;
border-bottom: 1px dashed #E1E5EB;
.photo-panel {
width 100%
height 100%
display flex
}
}
.ix-modal-footer {
justify-content center
}
}
}
</style>

View File

@ -6,11 +6,11 @@ import { TablePagination } from '@idux/components/table/src/types'
import CreateTsp from '@/pages/tsp/create-tsp.vue'
import TspApi from '@/pages/tsp/tsp-api.ts'
import { ref } from 'vue'
import VideoPanel from '@/pages/tsp/VideoPanel.vue'
import Toast from '@/components/toast'
import PhotoPanel from '@/pages/tsp/PhotoPanel.vue'
const createTsp = ref<InstanceType<typeof CreateTsp> | null>(null)
const videoPanel = ref<InstanceType<typeof VideoPanel> | null>(null)
// const videoPanel = ref<InstanceType<typeof VideoPanel> | null>(null)
const photoPanel = ref<InstanceType<typeof PhotoPanel> | null>(null)
const dataSource: SelectData[] = [
{key: '', label: '全部状态'},
{key: 'ZhengChang', label: '正常运营'},
@ -92,12 +92,15 @@ function searchHandler() {
})
}
function playVideo(record: TspTypes.TspData) {
/* function playVideo(record: TspTypes.TspData) {
if (record.videoUrl == null) {
Toast.error('该收纳点未配置视频监控')
return
}
videoPanel.value?.open(record.videoUrl, record.pointName)
// videoPanel.value?.open(record.videoUrl, record.pointName)
} */
function playPhoto(record: TspTypes.TspData) {
photoPanel.value?.open(record.id, record.pointName)
}
onMounted(() => {
@ -117,7 +120,7 @@ onMounted(() => {
<div>
<div>收纳点总数</div>
<div>{{ statisticsResult.totalCount }}</div>
<div><i class="iconfont icon-arrow-up"></i>较上月增长 {{ statisticsResult.totalCountGrowthRate }}%</div>
<div><!--<i class="iconfont icon-arrow-up"></i>较上月增长 {{ statisticsResult.totalCountGrowthRate }}%--></div>
</div>
<Iconfont name="map-pin" wrapper/>
</IxCard>
@ -125,7 +128,7 @@ onMounted(() => {
<div>
<div>正常运营数量</div>
<div>{{ statisticsResult.onlineCount }}</div>
<div><i class="iconfont icon-arrow-up"></i>较上月增长 {{ statisticsResult.onlineCountGrowthRate }}%</div>
<div><!--<i class="iconfont icon-arrow-up"></i>较上月增长 {{ statisticsResult.onlineCountGrowthRate }}%--></div>
</div>
<Iconfont name="check-circle-fill" wrapper/>
</IxCard>
@ -133,7 +136,7 @@ onMounted(() => {
<div>
<div>非正常运营数量</div>
<div>{{ statisticsResult.offlineCount }}</div>
<div><i class="iconfont icon-arrow-up"></i>较上月增长 {{ statisticsResult.offlineCountGrowthRate }}%</div>
<div><!--<i class="iconfont icon-arrow-up"></i>较上月增长 {{ statisticsResult.offlineCountGrowthRate }}%--></div>
</div>
<Iconfont name="exclamationcircle-f" wrapper/>
</IxCard>
@ -166,7 +169,8 @@ onMounted(() => {
<span>{{ record.pointName }}</span>
</template>
<template #action="{record}">
<IxButton class="video-btn" icon="eye" mode="text" @click="playVideo(record)"></IxButton>
<!--<IxButton class="video-btn" icon="eye" mode="text" @click="playVideo(record)"></IxButton>-->
<IxButton class="video-btn" icon="eye" mode="text" @click="playPhoto(record)"></IxButton>
</template>
<template #status="{record}">
<IxTag v-if="record.status === 'ZhengChang'" status="success">{{ record.statusTxt }}</IxTag>
@ -175,7 +179,8 @@ onMounted(() => {
</IxTable>
</IxCard>
<CreateTsp ref="createTsp"/>
<VideoPanel ref="videoPanel"/>
<!--<VideoPanel ref="videoPanel"/>-->
<PhotoPanel ref="photoPanel"/>
</div>
</template>

View File

@ -151,7 +151,6 @@ defineExpose({
</template>
</IxModal>
</div>
</template>
<style lang="stylus" scoped>