上传照片
parent
15981273b0
commit
444c7d3684
|
|
@ -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>
|
||||
|
|
@ -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--'+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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -151,7 +151,6 @@ defineExpose({
|
|||
</template>
|
||||
</IxModal>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
|
|
|||
Loading…
Reference in New Issue