更新若干功能
This commit is contained in:
1 parent
892ba5886d
commit
79d5ae6b66
45 files changed
+6755
-38
No files matched your search
@@ -0,0 +1,458 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="body" scroll-y :show-scrollbar="false">
|
||||
<view class="form-card">
|
||||
<view class="date-row">
|
||||
<text class="label">日报日期</text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="reportDate"
|
||||
:start="profile.window_start || ''"
|
||||
:end="profile.window_end || today"
|
||||
@change="onDateChange"
|
||||
>
|
||||
<view class="date-chip">{{ reportDate }}</view>
|
||||
</picker>
|
||||
<text class="date-tip">可补写最近 7 天</text>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="label">今日完成</text>
|
||||
<textarea
|
||||
v-model="form.todayDone"
|
||||
class="textarea"
|
||||
maxlength="5000"
|
||||
placeholder="今天完成了哪些工作?可按条列出"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="label">明日计划</text>
|
||||
<textarea
|
||||
v-model="form.tomorrowPlan"
|
||||
class="textarea"
|
||||
maxlength="5000"
|
||||
placeholder="明天计划推进哪些事项?"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="label">问题与建议</text>
|
||||
<textarea
|
||||
v-model="form.issues"
|
||||
class="textarea is-short"
|
||||
maxlength="5000"
|
||||
placeholder="遇到的问题、需要的支持或改进建议(选填)"
|
||||
placeholder-class="placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="label">附件</text>
|
||||
<view class="attachment-list">
|
||||
<view
|
||||
v-for="(file, index) in form.attachments"
|
||||
:key="file.url || index"
|
||||
class="attachment-item"
|
||||
>
|
||||
<text class="attachment-name">{{ file.name || '附件' }}</text>
|
||||
<text class="remove" @tap="removeAttachment(index)">移除</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="form.attachments.length < 9"
|
||||
class="upload-btn"
|
||||
@tap="chooseImages"
|
||||
>
|
||||
{{ uploading ? '上传中...' : '+ 添加附件' }}
|
||||
</view>
|
||||
<text class="upload-tip">支持图片,单个不超过 10MB</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="state-box">
|
||||
<text class="state-text">加载中...</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="btn ghost" :disabled="saving" @tap="save(false)">保存草稿</button>
|
||||
<button class="btn primary" :disabled="saving" @tap="save(true)">提交日报</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import {
|
||||
fetchDailyReportProfile,
|
||||
fetchDailyReportDetail,
|
||||
fetchMyDailyReports,
|
||||
saveDailyReport,
|
||||
uploadDailyReportFile,
|
||||
todayString
|
||||
} from '@/api/oaDailyReport.js'
|
||||
|
||||
const today = todayString()
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const uploading = ref(false)
|
||||
const reportId = ref(0)
|
||||
const reportDate = ref(today)
|
||||
const profile = ref({ window_start: '', window_end: '' })
|
||||
|
||||
const form = reactive({
|
||||
todayDone: '',
|
||||
tomorrowPlan: '',
|
||||
issues: '',
|
||||
attachments: []
|
||||
})
|
||||
|
||||
onLoad(async (options = {}) => {
|
||||
reportDate.value = options.date || today
|
||||
if (options.id) reportId.value = Number(options.id)
|
||||
|
||||
await loadProfile()
|
||||
if (reportId.value) {
|
||||
await loadDetail(reportId.value)
|
||||
} else {
|
||||
await loadByDate(reportDate.value, false)
|
||||
}
|
||||
})
|
||||
|
||||
async function loadProfile() {
|
||||
try {
|
||||
const data = await fetchDailyReportProfile()
|
||||
if (data) profile.value = { ...profile.value, ...data }
|
||||
} catch (e) {
|
||||
// 窗口信息失败时仍允许按今日编辑
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDetail(id) {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await fetchDailyReportDetail(id)
|
||||
applyDetail(data)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '日报加载失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function applyDetail(data) {
|
||||
const report = data?.report
|
||||
if (!report) return
|
||||
reportId.value = report.id
|
||||
reportDate.value = report.report_date
|
||||
form.todayDone = report.today_done || ''
|
||||
form.tomorrowPlan = report.tomorrow_plan || ''
|
||||
form.issues = report.issues || ''
|
||||
form.attachments = Array.isArray(report.attachments)
|
||||
? report.attachments.map((item) => ({ ...item }))
|
||||
: []
|
||||
}
|
||||
|
||||
/** 按日期查找已有日报;载入后可直接编辑 */
|
||||
async function loadByDate(date, notify = true) {
|
||||
const data = await fetchMyDailyReports({ page: 1, pageSize: 1, startDate: date, endDate: date })
|
||||
const item = data?.list?.[0]
|
||||
if (!item) {
|
||||
reportId.value = 0
|
||||
return false
|
||||
}
|
||||
if (notify) {
|
||||
const confirmed = await confirmModal('该日期已有日报', '是否载入已有内容?')
|
||||
if (!confirmed) return false
|
||||
}
|
||||
await loadDetail(item.id)
|
||||
return true
|
||||
}
|
||||
|
||||
async function onDateChange(e) {
|
||||
const next = e.detail.value
|
||||
if (!next || next === reportDate.value) return
|
||||
|
||||
const existing = await fetchMyDailyReports({
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
startDate: next,
|
||||
endDate: next
|
||||
})
|
||||
const item = existing?.list?.[0]
|
||||
if (item) {
|
||||
const confirmed = await confirmModal(
|
||||
'该日期已有日报',
|
||||
'是否载入该日期已有内容?当前未保存的内容将被覆盖。'
|
||||
)
|
||||
if (!confirmed) return
|
||||
reportDate.value = next
|
||||
await loadDetail(item.id)
|
||||
return
|
||||
}
|
||||
|
||||
if (hasContent()) {
|
||||
const confirmed = await confirmModal(
|
||||
'该日期暂无日报',
|
||||
'切换日期会清空当前内容,是否继续?'
|
||||
)
|
||||
if (!confirmed) return
|
||||
}
|
||||
reportDate.value = next
|
||||
reportId.value = 0
|
||||
form.todayDone = ''
|
||||
form.tomorrowPlan = ''
|
||||
form.issues = ''
|
||||
form.attachments = []
|
||||
}
|
||||
|
||||
function hasContent() {
|
||||
return Boolean(
|
||||
form.todayDone.trim() ||
|
||||
form.tomorrowPlan.trim() ||
|
||||
form.issues.trim() ||
|
||||
form.attachments.length
|
||||
)
|
||||
}
|
||||
|
||||
function removeAttachment(index) {
|
||||
form.attachments.splice(index, 1)
|
||||
}
|
||||
|
||||
async function chooseImages() {
|
||||
const remain = Math.max(1, 9 - form.attachments.length)
|
||||
uni.chooseImage({
|
||||
count: remain,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
uploading.value = true
|
||||
const paths = res.tempFilePaths || []
|
||||
const files = res.tempFiles || []
|
||||
for (let i = 0; i < paths.length; i += 1) {
|
||||
try {
|
||||
const data = await uploadDailyReportFile(paths[i])
|
||||
form.attachments.push({
|
||||
id: data.id || 0,
|
||||
url: data.url,
|
||||
name: data.name || '附件',
|
||||
size: files[i]?.size || 0
|
||||
})
|
||||
} catch (e) {
|
||||
// 上传失败已提示,继续处理其余文件
|
||||
}
|
||||
}
|
||||
uploading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function save(submit) {
|
||||
if (!hasContent()) {
|
||||
uni.showToast({ title: '请至少填写一项内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await saveDailyReport({
|
||||
date: reportDate.value,
|
||||
todayDone: form.todayDone,
|
||||
tomorrowPlan: form.tomorrowPlan,
|
||||
issues: form.issues,
|
||||
attachments: form.attachments,
|
||||
submit
|
||||
})
|
||||
uni.showToast({ title: submit ? '日报已提交' : '草稿已保存', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 500)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '保存失败', icon: 'none' })
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function confirmModal(title, content) {
|
||||
return new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title,
|
||||
content,
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
success: (res) => resolve(Boolean(res.confirm)),
|
||||
fail: () => resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/src/styles/page-common.scss';
|
||||
|
||||
.page {
|
||||
height: 100vh;
|
||||
background: $color-bg-page;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 24rpx $page-padding-x 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
@include card;
|
||||
padding: 28rpx 24rpx;
|
||||
}
|
||||
|
||||
.date-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid $color-divider;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text;
|
||||
}
|
||||
|
||||
.date-chip {
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: $radius-sm;
|
||||
background: $color-primary-bg;
|
||||
color: $color-primary;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.date-tip {
|
||||
margin-left: auto;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-top: 14rpx;
|
||||
padding: 20rpx;
|
||||
min-height: 220rpx;
|
||||
border-radius: $radius-md;
|
||||
background: $color-bg-page;
|
||||
font-size: 27rpx;
|
||||
line-height: 1.6;
|
||||
color: $color-text;
|
||||
|
||||
&.is-short {
|
||||
min-height: 150rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: $color-text-muted;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.attachment-list {
|
||||
margin-top: 14rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: $radius-md;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 25rpx;
|
||||
color: $color-text-secondary;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.remove {
|
||||
margin-left: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: $color-danger;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
align-self: flex-start;
|
||||
padding: 14rpx 26rpx;
|
||||
border-radius: $radius-full;
|
||||
border: 1rpx dashed $color-primary-light;
|
||||
color: $color-primary;
|
||||
font-size: 25rpx;
|
||||
background: $color-primary-bg;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 22rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.state-box {
|
||||
padding: 60rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.state-text {
|
||||
font-size: 26rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx $page-padding-x calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: $color-card;
|
||||
box-shadow: 0 -2rpx 12rpx rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 84rpx;
|
||||
line-height: 84rpx;
|
||||
border-radius: $radius-full;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
|
||||
&.ghost {
|
||||
background: $color-bg-page;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background: $color-primary;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user