1610 lines
48 KiB
Vue
1610 lines
48 KiB
Vue
<template>
|
||
<view class="page-container">
|
||
<NavBarModal navBarTitle="请假">
|
||
<template #left>
|
||
<view class="back-btn" @tap="handleBack">
|
||
<up-icon name="arrow-left" size="20" color="#fff" />
|
||
</view>
|
||
</template>
|
||
</NavBarModal>
|
||
|
||
<view class="content-wrapper" :style="contentStyle">
|
||
<!-- Tab切换 -->
|
||
<view class="tabs-container">
|
||
<view
|
||
class="tab-item"
|
||
:class="{ active: activeTab === 'form' }"
|
||
@tap="handleTabChange('form')"
|
||
>
|
||
<text class="tab-text">请假填写</text>
|
||
</view>
|
||
<view
|
||
class="tab-item"
|
||
:class="{ active: activeTab === 'record' }"
|
||
@tap="handleTabChange('record')"
|
||
>
|
||
<text class="tab-text">请假记录</text>
|
||
</view>
|
||
<view
|
||
class="tab-item"
|
||
:class="{ active: activeTab === 'approval' }"
|
||
@tap="handleTabChange('approval')"
|
||
>
|
||
<text class="tab-text">请假审批</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 请假填写 -->
|
||
<scroll-view v-if="activeTab === 'form'" class="scroll-container" scroll-y>
|
||
<up-form :model="formData" :rules="rules" ref="formRef" labelWidth="100">
|
||
<!-- 请假类型 -->
|
||
<up-form-item label="请假类型" prop="leaveTypeId" required :borderBottom="true">
|
||
<view class="form-picker-wrapper" @tap="handleOpenLeaveTypePicker">
|
||
<text
|
||
class="picker-text"
|
||
:class="{ placeholder: !formData.leaveTypeId }"
|
||
>
|
||
{{ formData.leaveTypeName || '请选择请假类型' }}
|
||
</text>
|
||
<up-icon name="arrow-right" size="16" color="#999" />
|
||
</view>
|
||
</up-form-item>
|
||
|
||
<!-- 请假日期 -->
|
||
<up-form-item label="请假日期" prop="leaveDate" required :borderBottom="true">
|
||
<view class="form-picker-wrapper" @tap="handleOpenDatePicker">
|
||
<text class="picker-text" :class="{ placeholder: !leaveDateText }">
|
||
{{ leaveDateText || '请选择请假日期范围' }}
|
||
</text>
|
||
<up-icon name="arrow-right" size="16" color="#999" />
|
||
</view>
|
||
</up-form-item>
|
||
|
||
<!-- 请假事由 -->
|
||
<up-form-item label="请假事由" prop="reason" :borderBottom="true">
|
||
<up-textarea
|
||
count
|
||
:maxlength="140"
|
||
:autoHeight="true"
|
||
v-model="formData.reason"
|
||
placeholder="根据xx安排,参加xx"
|
||
/>
|
||
</up-form-item>
|
||
|
||
<!-- 请假时长 -->
|
||
<up-form-item label="请假时长" prop="duration" :borderBottom="true">
|
||
<up-input v-model="formData.duration" type="number" placeholder="0" />
|
||
</up-form-item>
|
||
|
||
<!-- 请假前需履职天数 -->
|
||
<up-form-item label="请假前需履职天数" prop="beforeDays" :borderBottom="true">
|
||
<up-input
|
||
v-model="formData.beforeDays"
|
||
type="number"
|
||
placeholder="0"
|
||
:disabled="true"
|
||
/>
|
||
</up-form-item>
|
||
|
||
<!-- 请假后需履职天数 -->
|
||
<up-form-item label="请假后需履职天数" prop="afterDays" :borderBottom="true">
|
||
<up-input
|
||
v-model="formData.afterDays"
|
||
type="number"
|
||
placeholder="0"
|
||
:disabled="true"
|
||
/>
|
||
</up-form-item>
|
||
|
||
<!-- 审批人 -->
|
||
<up-form-item label="审批人" prop="approverId" :borderBottom="true">
|
||
<view class="form-picker-wrapper" @tap="handleOpenApproverPicker">
|
||
<text
|
||
class="picker-text"
|
||
:class="{ placeholder: !formData.approverId }"
|
||
>
|
||
{{ approverText || '请选择审批人' }}
|
||
</text>
|
||
<up-icon name="arrow-right" size="16" color="#999" />
|
||
</view>
|
||
</up-form-item>
|
||
|
||
<!-- 上传附件 -->
|
||
<up-form-item label="上传附件" prop="attachments" :borderBottom="true">
|
||
<up-upload
|
||
accept="file"
|
||
:fileList="fileList"
|
||
@afterRead="handleAfterRead"
|
||
@delete="handleDelete"
|
||
name="attachments"
|
||
multiple
|
||
:maxCount="9"
|
||
/>
|
||
</up-form-item>
|
||
</up-form>
|
||
|
||
<!-- 提交按钮 -->
|
||
<view class="submit-section">
|
||
<up-button
|
||
:text="submitButtonText"
|
||
type="primary"
|
||
:loading="submitting"
|
||
@tap="handleSubmit"
|
||
/>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 请假记录/请假审批 -->
|
||
<view v-if="activeTab === 'record' || activeTab === 'approval'" class="list-wrapper">
|
||
<!-- 查询条件 -->
|
||
<view class="filter-section">
|
||
<view class="filter-row">
|
||
<view class="filter-item">
|
||
<CommonPicker
|
||
:options="yearOptions"
|
||
:modelValue="queryParams.year"
|
||
placeholder="选择年份"
|
||
@change="handleYearChange"
|
||
/>
|
||
</view>
|
||
<view class="filter-item">
|
||
<CommonPicker
|
||
:options="monthOptions"
|
||
:modelValue="queryParams.month"
|
||
placeholder="全部月"
|
||
@change="handleMonthChange"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 记录列表 -->
|
||
<scroll-view
|
||
class="list-container"
|
||
scroll-y
|
||
@scrolltolower="onHandleScrollToLower"
|
||
v-if="recordList.length > 0"
|
||
>
|
||
<view
|
||
v-for="(item, index) in recordList"
|
||
:key="item.id || index"
|
||
class="record-item"
|
||
@tap="handleRecordClick(item)"
|
||
>
|
||
<view class="record-header">
|
||
<view class="record-user">
|
||
<up-icon name="account" size="18" color="#07c160" />
|
||
<text class="record-title">{{ item.title || '请假记录' }}</text>
|
||
</view>
|
||
<text class="record-date">{{ item.createTime || '--' }}</text>
|
||
</view>
|
||
|
||
<view class="record-type">
|
||
<text>请假类型:{{ item.typeText || '暂无类型' }}</text>
|
||
</view>
|
||
|
||
<view class="record-body">
|
||
<view class="record-range">
|
||
<up-icon name="calendar" size="16" color="#999" />
|
||
<text class="record-range-text">
|
||
{{ item.dateRange || '暂无日期' }}
|
||
</text>
|
||
</view>
|
||
<text class="record-days" v-if="item.leaveDays || item.leaveCnt">
|
||
{{ item.leaveDays || item.leaveCnt }}天
|
||
</text>
|
||
</view>
|
||
|
||
<view class="record-status">
|
||
<view class="status-left">
|
||
<text
|
||
class="status-tag"
|
||
:class="`status-${item.statusType || 'default'}`"
|
||
>
|
||
{{ item.statusText || '待审批' }}
|
||
</text>
|
||
<text
|
||
v-if="item.statusType === 'success'"
|
||
class="status-tag status-success status-outline"
|
||
>
|
||
结束
|
||
</text>
|
||
</view>
|
||
|
||
<view class="record-actions">
|
||
<!-- 请假审批tab:显示同意和拒绝按钮 -->
|
||
<template v-if="activeTab === 'approval'">
|
||
<up-button
|
||
text="同意"
|
||
size="small"
|
||
type="primary"
|
||
@tap.stop="handleApprove(item)"
|
||
/>
|
||
<up-button
|
||
text="拒绝"
|
||
size="small"
|
||
type="error"
|
||
@tap.stop="handleReject(item)"
|
||
/>
|
||
</template>
|
||
<!-- 请假记录tab:显示修改、撤销或结束按钮 -->
|
||
<template v-else>
|
||
<template v-if="item.canEdit">
|
||
<up-button
|
||
text="修改"
|
||
size="small"
|
||
type="primary"
|
||
plain
|
||
@tap.stop="handleModify(item)"
|
||
/>
|
||
<up-button
|
||
text="撤销"
|
||
size="small"
|
||
type="warning"
|
||
plain
|
||
@tap.stop="handleRevoke(item)"
|
||
/>
|
||
</template>
|
||
<up-button
|
||
v-else
|
||
text="结束"
|
||
size="small"
|
||
type="success"
|
||
plain
|
||
@tap.stop="handleFinish(item)"
|
||
/>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="loading-text">
|
||
{{ !hasMore ? '没有更多数据了~' : '正在加载...' }}
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 空状态 -->
|
||
<view v-if="recordList.length === 0 && !loading" class="empty-state">
|
||
<ReviewEmptyState text="数据为空" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 请假类型选择器 -->
|
||
<up-picker
|
||
:show="showLeaveTypePicker"
|
||
:columns="leaveTypeColumns"
|
||
closeOnClickOverlay
|
||
@cancel="showLeaveTypePicker = false"
|
||
@confirm="handleLeaveTypeConfirm"
|
||
@close="showLeaveTypePicker = false"
|
||
/>
|
||
|
||
<!-- 开始日期选择器 -->
|
||
<up-datetime-picker
|
||
:show="showStartDatePicker"
|
||
mode="date"
|
||
v-model="startDatePickerValue"
|
||
@cancel="showStartDatePicker = false"
|
||
@confirm="handleStartDateConfirm"
|
||
/>
|
||
|
||
<!-- 结束日期选择器 -->
|
||
<up-datetime-picker
|
||
:show="showEndDatePicker"
|
||
mode="date"
|
||
v-model="endDatePickerValue"
|
||
:minDate="computedEndDateMinDate"
|
||
@cancel="showEndDatePicker = false"
|
||
@confirm="handleEndDateConfirm"
|
||
/>
|
||
|
||
<!-- 审批人选择器 -->
|
||
<up-picker
|
||
:show="showApproverPicker"
|
||
:columns="approverColumns"
|
||
closeOnClickOverlay
|
||
@cancel="showApproverPicker = false"
|
||
@confirm="handleApproverConfirm"
|
||
@close="showApproverPicker = false"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { debounce } from 'lodash-es'
|
||
import dayjs from 'dayjs'
|
||
import NavBarModal from '@/components/NavBarModal/index.vue'
|
||
import ReviewEmptyState from '@/components/ReviewEmptyState/index.vue'
|
||
import CommonPicker from '@/components/CommonPicker/index.vue'
|
||
import { getContentStyle } from '@/utils/safeArea'
|
||
import { useMemberStore } from '@/stores'
|
||
import {
|
||
getLeaveTypeListApi,
|
||
getApproverListApi,
|
||
submitLeaveRequestApi,
|
||
getMyLeaveRecordListApi,
|
||
getLeaveApprovalListApi,
|
||
getLeaveDetailApi,
|
||
getPersonPerformanceDaysApi,
|
||
checkHasUnfinishedLeaveApi,
|
||
endLeaveRequestApi,
|
||
approveLeaveRequestApi,
|
||
rejectLeaveRequestApi,
|
||
} from '@/services/leader/leave-request'
|
||
|
||
/**
|
||
* 请假页面
|
||
* 业务背景:请假申请、请假记录查看、请假审批
|
||
* 设计决策:
|
||
* 1. 支持三个Tab:请假填写、请假记录、请假审批
|
||
* 2. 请假记录和请假审批使用相同的查询条件(年份、月份)
|
||
* 3. 支持滚动触底分页加载
|
||
*/
|
||
|
||
const contentStyle = computed(() => {
|
||
return getContentStyle({
|
||
includeNavBar: true,
|
||
includeStatusBar: true,
|
||
includeBottomSafeArea: true,
|
||
})
|
||
})
|
||
|
||
// 当前激活的Tab
|
||
const activeTab = ref('form') // 'form' | 'record' | 'approval'
|
||
const isEditing = ref(false)
|
||
const editingRecord = ref(null)
|
||
|
||
// 表单相关
|
||
const formRef = ref(null)
|
||
const formData = ref({
|
||
leaveTypeId: '',
|
||
leaveTypeName: '',
|
||
startDate: null,
|
||
endDate: null,
|
||
reason: '',
|
||
duration: '0',
|
||
beforeDays: '0',
|
||
afterDays: '0',
|
||
approverId: '',
|
||
approverName: '',
|
||
attachments: [],
|
||
})
|
||
|
||
// 表单验证规则
|
||
const rules = ref({
|
||
leaveTypeId: [
|
||
{
|
||
type: 'number',
|
||
required: true,
|
||
message: '请选择请假类型',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
leaveDate: [
|
||
{
|
||
validator: (rule, value, callback) => {
|
||
if (!formData.value.startDate || !formData.value.endDate) {
|
||
callback(new Error('请选择请假日期范围'))
|
||
} else {
|
||
callback()
|
||
}
|
||
},
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
})
|
||
|
||
// 提交状态
|
||
const submitting = ref(false)
|
||
|
||
// 文件列表
|
||
const fileList = ref([])
|
||
|
||
// 请假类型相关
|
||
const leaveTypeList = ref([])
|
||
const showLeaveTypePicker = ref(false)
|
||
const leaveTypeColumns = computed(() => {
|
||
return [
|
||
leaveTypeList.value.map((item) => ({
|
||
text: item.dictLabel,
|
||
value: item.dictCode || item.dictValue,
|
||
})),
|
||
]
|
||
})
|
||
|
||
// 日期相关
|
||
const showStartDatePicker = ref(false)
|
||
const showEndDatePicker = ref(false)
|
||
const startDatePickerValue = ref(Date.now())
|
||
const endDatePickerValue = ref(Date.now())
|
||
|
||
/**
|
||
* 计算结束日期选择器的最小日期
|
||
* 业务背景:确保结束日期不早于开始日期
|
||
* 设计决策:如果已选择开始日期,则结束日期的最小值为开始日期;否则为当前日期
|
||
*/
|
||
const computedEndDateMinDate = computed(() => {
|
||
if (formData.value.startDate) {
|
||
return typeof formData.value.startDate === 'number'
|
||
? formData.value.startDate
|
||
: new Date(formData.value.startDate).getTime()
|
||
}
|
||
return Date.now()
|
||
})
|
||
|
||
const leaveDateText = computed(() => {
|
||
if (!formData.value.startDate || !formData.value.endDate) return ''
|
||
const start = dayjs(formData.value.startDate).format('YYYY-MM-DD')
|
||
const end = dayjs(formData.value.endDate).format('YYYY-MM-DD')
|
||
return `${start} 至 ${end}`
|
||
})
|
||
|
||
// 审批人相关
|
||
const approverList = ref([])
|
||
const showApproverPicker = ref(false)
|
||
const approverColumns = computed(() => {
|
||
return [
|
||
approverList.value.map((item) => ({
|
||
text: item.nickName || item.name,
|
||
value: item.userId || item.id,
|
||
})),
|
||
]
|
||
})
|
||
const approverText = computed(() => {
|
||
if (!formData.value.approverId) return ''
|
||
return formData.value.approverName || ''
|
||
})
|
||
|
||
// 记录列表相关
|
||
const recordList = ref([])
|
||
const total = ref(0)
|
||
const loading = ref(false)
|
||
|
||
// 查询参数
|
||
const queryParams = ref({
|
||
pageNum: 1,
|
||
year: new Date().getFullYear().toString(),
|
||
month: '',
|
||
})
|
||
|
||
/**
|
||
* 计算请假记录查询的时间范围
|
||
* 业务背景:后端列表接口要求按起止日期筛选,不接受 pageSize
|
||
* 设计决策:保留年/月选择体验,将其转换为 startTime、endTime
|
||
*/
|
||
const computedRecordRange = computed(() => {
|
||
const year = queryParams.value.year
|
||
const month = queryParams.value.month
|
||
|
||
if (month) {
|
||
const start = dayjs(`${year}-${month}-01`)
|
||
return {
|
||
startTime: start.format('YYYY-MM-DD'),
|
||
endTime: start.endOf('month').format('YYYY-MM-DD'),
|
||
}
|
||
}
|
||
|
||
return {
|
||
startTime: dayjs(`${year}-01-01`).format('YYYY-MM-DD'),
|
||
endTime: dayjs(`${year}-12-31`).format('YYYY-MM-DD'),
|
||
}
|
||
})
|
||
|
||
// 年份选项(当前年份前后5年)
|
||
const yearOptions = computed(() => {
|
||
const currentYear = new Date().getFullYear()
|
||
const years = []
|
||
for (let i = currentYear - 5; i <= currentYear + 5; i++) {
|
||
years.push({
|
||
label: `${i}年`,
|
||
value: i.toString(),
|
||
})
|
||
}
|
||
return years
|
||
})
|
||
|
||
// 月份选项
|
||
const monthOptions = ref([
|
||
{ label: '全部月', value: '' },
|
||
{ label: '1月', value: '1' },
|
||
{ label: '2月', value: '2' },
|
||
{ label: '3月', value: '3' },
|
||
{ label: '4月', value: '4' },
|
||
{ label: '5月', value: '5' },
|
||
{ label: '6月', value: '6' },
|
||
{ label: '7月', value: '7' },
|
||
{ label: '8月', value: '8' },
|
||
{ label: '9月', value: '9' },
|
||
{ label: '10月', value: '10' },
|
||
{ label: '11月', value: '11' },
|
||
{ label: '12月', value: '12' },
|
||
])
|
||
|
||
/**
|
||
* 计算提交按钮文案
|
||
* 业务背景:编辑模式需要提示“保存修改”,新增则为“提交请假”
|
||
*/
|
||
const submitButtonText = computed(() => {
|
||
return isEditing.value ? '保存修改' : '提交请假'
|
||
})
|
||
|
||
/**
|
||
* 计算是否还有更多数据
|
||
*/
|
||
const hasMore = computed(() => {
|
||
return recordList.value.length < total.value
|
||
})
|
||
|
||
/**
|
||
* 处理Tab切换
|
||
* @param {String} tab - Tab类型
|
||
*/
|
||
const handleTabChange = (tab) => {
|
||
activeTab.value = tab
|
||
if (tab === 'record' || tab === 'approval') {
|
||
// 重置分页和列表
|
||
queryParams.value.pageNum = 1
|
||
recordList.value = []
|
||
total.value = 0
|
||
loadRecordList()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 打开请假类型选择器
|
||
*/
|
||
const handleOpenLeaveTypePicker = () => {
|
||
if (leaveTypeList.value.length === 0) {
|
||
loadLeaveTypeList()
|
||
}
|
||
showLeaveTypePicker.value = true
|
||
}
|
||
|
||
/**
|
||
* 处理请假类型确认
|
||
* 业务背景:保存请假类型ID和名称
|
||
*/
|
||
const handleLeaveTypeConfirm = (e) => {
|
||
const selectedValue = e.value[0]?.value ?? e.value[0]
|
||
const selectedItem = leaveTypeList.value.find(
|
||
(item) => (item.dictCode || item.dictValue) === selectedValue,
|
||
)
|
||
formData.value.leaveTypeId = selectedValue
|
||
formData.value.leaveTypeName = selectedItem?.dictLabel || e.value[0]?.text || ''
|
||
showLeaveTypePicker.value = false
|
||
}
|
||
|
||
/**
|
||
* 打开日期选择器
|
||
* 业务背景:打开日期范围选择器,先选择开始日期,再选择结束日期
|
||
* 设计决策:分两步选择,确保结束日期不早于开始日期
|
||
*/
|
||
const handleOpenDatePicker = () => {
|
||
if (formData.value.startDate) {
|
||
startDatePickerValue.value = formData.value.startDate
|
||
} else {
|
||
startDatePickerValue.value = Date.now()
|
||
}
|
||
showStartDatePicker.value = true
|
||
}
|
||
|
||
/**
|
||
* 处理开始日期确认
|
||
* 业务背景:选择开始日期后,自动打开结束日期选择器
|
||
*/
|
||
const handleStartDateConfirm = (e) => {
|
||
formData.value.startDate = e.value
|
||
startDatePickerValue.value = e.value
|
||
showStartDatePicker.value = false
|
||
|
||
// 如果结束日期早于开始日期,重置结束日期
|
||
if (formData.value.endDate && formData.value.endDate < e.value) {
|
||
formData.value.endDate = null
|
||
}
|
||
|
||
// 打开结束日期选择器
|
||
if (formData.value.endDate) {
|
||
endDatePickerValue.value = formData.value.endDate
|
||
} else {
|
||
// 默认结束日期为开始日期
|
||
endDatePickerValue.value = e.value
|
||
}
|
||
showEndDatePicker.value = true
|
||
}
|
||
|
||
/**
|
||
* 处理结束日期确认
|
||
* 业务背景:选择结束日期后,调用接口获取请假详情数据并回显
|
||
*/
|
||
const handleEndDateConfirm = async (e) => {
|
||
formData.value.endDate = e.value
|
||
endDatePickerValue.value = e.value
|
||
showEndDatePicker.value = false
|
||
|
||
// 调用接口获取请假详情
|
||
await loadLeaveDetail()
|
||
}
|
||
|
||
/**
|
||
* 打开审批人选择器
|
||
*/
|
||
const handleOpenApproverPicker = () => {
|
||
if (approverList.value.length === 0) {
|
||
loadApproverList()
|
||
}
|
||
showApproverPicker.value = true
|
||
}
|
||
|
||
/**
|
||
* 处理审批人确认
|
||
* 业务背景:保存审批人ID和名称
|
||
*/
|
||
const handleApproverConfirm = (e) => {
|
||
const selectedValue = e.value[0]?.value ?? e.value[0]
|
||
const selectedItem = approverList.value.find(
|
||
(item) => (item.userId || item.id) === selectedValue,
|
||
)
|
||
formData.value.approverId = selectedValue
|
||
formData.value.approverName = selectedItem?.nickName || selectedItem?.name || ''
|
||
showApproverPicker.value = false
|
||
}
|
||
|
||
/**
|
||
* 上传文件到服务器
|
||
* 业务背景:将文件上传到服务器,返回文件URL
|
||
* @param {String} filePath - 文件路径
|
||
* @returns {Promise<String>} 返回文件URL
|
||
*/
|
||
const uploadFilePromise = (filePath) => {
|
||
console.log(filePath, 'filePath')
|
||
return new Promise((resolve, reject) => {
|
||
uni.uploadFile({
|
||
url: '/common/upload',
|
||
filePath: filePath,
|
||
name: 'file',
|
||
formData: {
|
||
user: 'test',
|
||
},
|
||
success: (res) => {
|
||
if (res.data) {
|
||
try {
|
||
const data = JSON.parse(res.data)
|
||
if (data.code === 200) {
|
||
const fileName = data.fileName || data.data
|
||
resolve(fileName)
|
||
} else {
|
||
reject(new Error(data.msg || '上传失败'))
|
||
}
|
||
} catch (err) {
|
||
reject(new Error('解析响应失败'))
|
||
}
|
||
} else {
|
||
reject(new Error('上传失败'))
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
reject(err)
|
||
},
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 处理文件上传
|
||
* 业务背景:上传文件到服务器,保存完整的文件信息对象
|
||
* 设计决策:保存文件信息对象,包含 url, size, name, type, status, message 等字段
|
||
*/
|
||
const handleAfterRead = async (event) => {
|
||
const { file } = event
|
||
console.log(file, 'file')
|
||
try {
|
||
const fileName = await uploadFilePromise(file[0].url)
|
||
|
||
// 构建文件信息对象
|
||
const fileInfo = {
|
||
url: fileName,
|
||
size: file.size || 0,
|
||
name: file.name || '',
|
||
type: file.type || '',
|
||
status: 'success',
|
||
message: '',
|
||
}
|
||
|
||
fileList.value.push({
|
||
...file,
|
||
url: fileName,
|
||
status: 'success',
|
||
})
|
||
formData.value.attachments.push(fileInfo)
|
||
} catch (error) {
|
||
console.error('文件上传失败:', error)
|
||
uni.showToast({
|
||
title: '上传失败',
|
||
icon: 'none',
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理文件删除
|
||
*/
|
||
const handleDelete = (event) => {
|
||
const { index } = event
|
||
fileList.value.splice(index, 1)
|
||
formData.value.attachments.splice(index, 1)
|
||
}
|
||
|
||
/**
|
||
* 处理提交
|
||
* 业务背景:按照后台接口要求的参数格式组装提交数据
|
||
* 设计决策:
|
||
* 1. 将日期格式化为 YYYY-MM-DD
|
||
* 2. 将文件信息数组转换为 JSON 字符串
|
||
* 3. 将数字字段转换为数字类型
|
||
* 4. 组装 startEndTime 格式为 "YYYY-MM-DD / YYYY-MM-DD"
|
||
*/
|
||
const handleSubmit = async () => {
|
||
console.log(formData.value, 'formData.value.attachments')
|
||
try {
|
||
await formRef.value.validate()
|
||
submitting.value = true
|
||
|
||
const startTime = dayjs(formData.value.startDate).format('YYYY-MM-DD')
|
||
const endTime = dayjs(formData.value.endDate).format('YYYY-MM-DD')
|
||
const startEndTime = `${startTime} / ${endTime}`
|
||
|
||
const submitData = {
|
||
approverId: formData.value.approverId,
|
||
approverName: formData.value.approverName,
|
||
endTime: endTime,
|
||
fileInfo: JSON.stringify(formData.value.attachments),
|
||
leaveDays: Number(formData.value.duration) || 0,
|
||
leaveTypeId: formData.value.leaveTypeId,
|
||
leaveTypeName: formData.value.leaveTypeName,
|
||
lzDaysAfterLeave: Number(formData.value.afterDays) || 0,
|
||
lzDaysBeforeLeave: Number(formData.value.beforeDays) || 0,
|
||
reason: formData.value.reason,
|
||
startEndTime: startEndTime,
|
||
startTime: startTime,
|
||
}
|
||
|
||
await submitLeaveRequestApi(submitData)
|
||
uni.showToast({
|
||
title: '提交成功',
|
||
icon: 'success',
|
||
})
|
||
// 重置表单
|
||
formData.value = {
|
||
leaveTypeId: '',
|
||
leaveTypeName: '',
|
||
startDate: null,
|
||
endDate: null,
|
||
reason: '',
|
||
duration: '0',
|
||
beforeDays: '0',
|
||
afterDays: '0',
|
||
approverId: '',
|
||
approverName: '',
|
||
attachments: [],
|
||
}
|
||
fileList.value = []
|
||
} catch (error) {
|
||
console.error('提交失败:', error)
|
||
if (error.message) {
|
||
uni.showToast({
|
||
title: error.message,
|
||
icon: 'none',
|
||
})
|
||
}
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载请假类型列表
|
||
*/
|
||
const loadLeaveTypeList = async () => {
|
||
try {
|
||
const res = await getLeaveTypeListApi()
|
||
leaveTypeList.value = res?.rows || res?.data || []
|
||
} catch (error) {
|
||
console.error('加载请假类型列表失败:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载审批人列表
|
||
*/
|
||
const loadApproverList = async () => {
|
||
try {
|
||
const res = await getApproverListApi()
|
||
approverList.value = res?.rows || res?.data || []
|
||
} catch (error) {
|
||
console.error('加载审批人列表失败:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载请假详情数据
|
||
* 业务背景:根据选择的开始日期和结束日期,调用接口获取请假时长、履职天数等信息
|
||
* 设计决策:在用户选择完日期范围后自动调用,回显相关字段
|
||
*/
|
||
const loadLeaveDetail = async () => {
|
||
if (!formData.value.startDate || !formData.value.endDate) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
const startDate = dayjs(formData.value.startDate).format('YYYY-MM-DD')
|
||
const endDate = dayjs(formData.value.endDate).format('YYYY-MM-DD')
|
||
|
||
const res = await getLeaveDetailApi({
|
||
startDate,
|
||
endDate,
|
||
})
|
||
|
||
// 回显数据
|
||
if (res && res.data) {
|
||
// 请假时长
|
||
if (res.data.leaveDays !== undefined) {
|
||
formData.value.duration = String(res.data.leaveDays)
|
||
}
|
||
|
||
// 请假前需履职天数
|
||
if (res.data.lzDaysBeforeLeave !== undefined) {
|
||
formData.value.beforeDays = String(res.data.lzDaysBeforeLeave)
|
||
}
|
||
|
||
// 请假后需履职天数
|
||
if (res.data.lzDaysAfterLeave !== undefined) {
|
||
formData.value.afterDays = String(res.data.lzDaysAfterLeave)
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('加载请假详情失败:', error)
|
||
uni.showToast({
|
||
title: '获取请假详情失败',
|
||
icon: 'none',
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理年份变更
|
||
*/
|
||
const handleYearChange = (item) => {
|
||
queryParams.value.year = item.value
|
||
queryParams.value.pageNum = 1
|
||
recordList.value = []
|
||
loadRecordList()
|
||
}
|
||
|
||
/**
|
||
* 处理月份变更
|
||
*/
|
||
const handleMonthChange = (item) => {
|
||
queryParams.value.month = item.value
|
||
queryParams.value.pageNum = 1
|
||
recordList.value = []
|
||
loadRecordList()
|
||
}
|
||
|
||
/**
|
||
* 根据状态值映射展示样式
|
||
* 业务背景:后端可能返回多种状态文案,需要统一前端展示颜色
|
||
* 设计决策:简单映射几类常见状态,其余走默认灰色
|
||
*/
|
||
const mapStatusType = (statusText = '') => {
|
||
const text = statusText.toString()
|
||
if (['待审批', '审批中', '审核中'].some((v) => text.includes(v))) return 'pending'
|
||
if (['同意', '通过', '已审批', '完成', '结束'].some((v) => text.includes(v))) return 'success'
|
||
if (['拒绝', '驳回', '不通过'].some((v) => text.includes(v))) return 'error'
|
||
return 'default'
|
||
}
|
||
|
||
/**
|
||
* 判断是否可修改/撤销
|
||
* 业务背景:仅未来的请假可修改和撤销
|
||
* 设计决策:使用开始日期与今日对比,精确到天
|
||
*/
|
||
const canEditLeave = (startDate) => {
|
||
if (!startDate) return false
|
||
return dayjs(startDate).isAfter(dayjs(), 'day')
|
||
}
|
||
|
||
/**
|
||
* 计算规范化的开始、结束日期文本
|
||
* 业务背景:后端字段命名不统一,需前端兜底
|
||
*/
|
||
const normalizeStartEnd = (item) => {
|
||
const startRaw =
|
||
item.startTime || item.startDate || item.beginDate || item.startDateStr || item.start_at
|
||
const endRaw = item.endTime || item.endDate || item.finishDate || item.endDateStr || item.end_at
|
||
const start = startRaw ? dayjs(startRaw) : null
|
||
const end = endRaw ? dayjs(endRaw) : null
|
||
return {
|
||
startRaw,
|
||
endRaw,
|
||
startText: start?.format('YYYY-MM-DD') || '',
|
||
endText: end?.format('YYYY-MM-DD') || '',
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载记录列表
|
||
* 业务背景:根据时间范围与页码获取请假记录/审批列表
|
||
* 设计决策:请求参数使用 startTime/endTime,移除 pageSize,前端保留分页 pageNum
|
||
* 注意:请假审批和请假记录使用同一个接口
|
||
*/
|
||
const loadRecordList = async () => {
|
||
try {
|
||
loading.value = true
|
||
// 请假审批和请假记录使用同一个接口
|
||
const res = await getMyLeaveRecordListApi({
|
||
pageNum: queryParams.value.pageNum,
|
||
startTime: computedRecordRange.value.startTime,
|
||
endTime: computedRecordRange.value.endTime,
|
||
})
|
||
total.value = res?.total || 0
|
||
if (res?.rows && res.rows.length > 0) {
|
||
// 处理数据,添加占位字段
|
||
const processedData = res.rows.map((item) => {
|
||
const normalized = normalizeStartEnd(item)
|
||
return {
|
||
...item,
|
||
title:
|
||
item.userName || item.nickName
|
||
? `${item.userName || item.nickName}的请假`
|
||
: item.title || '请假记录',
|
||
typeText: item.leaveTypeName || item.typeName || item.leaveType || '请假',
|
||
createTime: item.createTime || item.createTimeStr || item.date || '',
|
||
dateRange:
|
||
(item.startTime && item.endTime && `${item.startTime}~${item.endTime}`) ||
|
||
(normalized.startText && normalized.endText
|
||
? `${normalized.startText}~${normalized.endText}`
|
||
: '') ||
|
||
item.startEndTime ||
|
||
'',
|
||
daysText:
|
||
(item.leaveDays && `${item.leaveDays}天`) ||
|
||
(item.duration && `${item.duration}天`) ||
|
||
'',
|
||
statusText:
|
||
item.statusText ||
|
||
item.statusName ||
|
||
item.statusLabel ||
|
||
item.status ||
|
||
'待审批',
|
||
statusType: mapStatusType(
|
||
item.statusText || item.statusName || item.statusLabel || item.status || '',
|
||
),
|
||
...normalized,
|
||
canEdit: canEditLeave(normalized.startRaw || normalized.startText),
|
||
}
|
||
})
|
||
recordList.value = [...recordList.value, ...processedData]
|
||
}
|
||
} catch (error) {
|
||
console.error('加载记录列表失败:', error)
|
||
// 模拟数据
|
||
if (queryParams.value.pageNum === 1) {
|
||
recordList.value = [
|
||
{
|
||
id: 1,
|
||
title: '请假记录示例1',
|
||
subtitle: '这是请假记录的描述信息',
|
||
createTime: '2025-01-15 10:30:00',
|
||
},
|
||
{
|
||
id: 2,
|
||
title: '请假记录示例2',
|
||
subtitle: '这是请假记录的描述信息',
|
||
createTime: '2025-01-14 14:20:00',
|
||
},
|
||
]
|
||
total.value = 2
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理滚动到底部
|
||
*/
|
||
const onHandleScrollToLower = debounce(() => {
|
||
if (hasMore.value && !loading.value) {
|
||
queryParams.value.pageNum++
|
||
loadRecordList()
|
||
}
|
||
}, 1000)
|
||
|
||
/**
|
||
* 处理记录点击
|
||
*/
|
||
const handleRecordClick = (item) => {
|
||
console.log('点击记录:', item)
|
||
// TODO: 跳转到记录详情页面
|
||
}
|
||
|
||
/**
|
||
* 处理修改操作
|
||
* 业务背景:点击记录的“修改”按钮后进入表单回显并重新计算履职天数
|
||
*/
|
||
const handleModify = async (item) => {
|
||
// 回显表单数据
|
||
prefillFormData(item)
|
||
// 切换到表单Tab
|
||
activeTab.value = 'form'
|
||
isEditing.value = true
|
||
editingRecord.value = item
|
||
|
||
// 重新计算履职天数
|
||
if (formData.value.startDate && formData.value.endDate) {
|
||
await fetchPerformanceDays()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 预填表单数据
|
||
* 业务背景:修改时需要用列表行的数据回显到表单
|
||
* 设计决策:日期统一转为时间戳,避免与日期组件冲突
|
||
*/
|
||
const prefillFormData = (item) => {
|
||
const { startRaw, endRaw } = normalizeStartEnd(item)
|
||
|
||
formData.value.leaveTypeId = item.leaveTypeId || item.leaveType || ''
|
||
formData.value.leaveTypeName = item.leaveTypeName || item.typeText || ''
|
||
formData.value.reason = item.reason || item.description || ''
|
||
formData.value.startDate = startRaw ? dayjs(startRaw).valueOf() : null
|
||
formData.value.endDate = endRaw ? dayjs(endRaw).valueOf() : null
|
||
formData.value.duration = item.leaveDays ? String(item.leaveDays) : item.duration || '0'
|
||
formData.value.beforeDays = item.lzDaysBeforeLeave
|
||
? String(item.lzDaysBeforeLeave)
|
||
: item.beforeDays || '0'
|
||
formData.value.afterDays = item.lzDaysAfterLeave
|
||
? String(item.lzDaysAfterLeave)
|
||
: item.afterDays || '0'
|
||
formData.value.approverId = item.approverId || ''
|
||
formData.value.approverName = item.approverName || ''
|
||
formData.value.attachments = item.fileInfo ? JSON.parse(item.fileInfo || '[]') : []
|
||
fileList.value = formData.value.attachments.map((file) => ({
|
||
...file,
|
||
status: 'success',
|
||
}))
|
||
}
|
||
|
||
/**
|
||
* 重新获取履职天数
|
||
* 业务背景:修改日期后需重新计算请假时长及履职天数
|
||
* 设计决策:复用后端接口 getPersonPerformanceDaysApi,保持字段与老代码一致
|
||
*/
|
||
const fetchPerformanceDays = async () => {
|
||
if (!formData.value.startDate || !formData.value.endDate) return
|
||
const startDate = dayjs(formData.value.startDate).format('YYYY-MM-DD')
|
||
const endDate = dayjs(formData.value.endDate).format('YYYY-MM-DD')
|
||
try {
|
||
const res = await getPersonPerformanceDaysApi({
|
||
startDate,
|
||
endDate,
|
||
})
|
||
if (res && res.data) {
|
||
formData.value.duration = String(res.data.leaveDays || 0)
|
||
formData.value.beforeDays = String(res.data.lzDaysBeforeLeave || 0)
|
||
formData.value.afterDays = String(res.data.lzDaysAfterLeave || 0)
|
||
}
|
||
} catch (error) {
|
||
console.error('获取履职天数失败:', error)
|
||
uni.showToast({
|
||
title: '获取请假天数失败',
|
||
icon: 'none',
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理撤销
|
||
* 业务背景:未来的请假可撤销
|
||
*/
|
||
const handleRevoke = (item) => {
|
||
uni.showToast({
|
||
title: '撤销功能待接入接口',
|
||
icon: 'none',
|
||
})
|
||
console.log('撤销', item)
|
||
}
|
||
|
||
/**
|
||
* 处理结束
|
||
* 业务背景:结束前需校验是否存在未结束的请假,避免重复操作
|
||
* 设计决策:先调用检查接口,按返回信息二次确认,再调用结束接口,完成后刷新列表
|
||
*/
|
||
const handleFinish = async (item) => {
|
||
try {
|
||
const checkRes = await checkHasUnfinishedLeaveApi()
|
||
const content = checkRes?.msg || '确认结束当前请假吗?'
|
||
|
||
uni.showModal({
|
||
title: '提示',
|
||
content,
|
||
cancelText: '再想想',
|
||
confirmText: '确认结束',
|
||
confirmColor: '#00aa7c',
|
||
success: async (res) => {
|
||
if (!res.confirm) return
|
||
try {
|
||
const endRes = await endLeaveRequestApi()
|
||
uni.showToast({
|
||
title: endRes?.msg || '结束成功',
|
||
icon: 'none',
|
||
})
|
||
// 刷新列表
|
||
queryParams.value.pageNum = 1
|
||
recordList.value = []
|
||
await loadRecordList()
|
||
} catch (err) {
|
||
console.error('结束失败:', err)
|
||
uni.showToast({
|
||
title: err?.message || '结束失败,请重试',
|
||
icon: 'none',
|
||
})
|
||
}
|
||
},
|
||
})
|
||
} catch (error) {
|
||
console.error('检查未结束请假失败:', error)
|
||
uni.showToast({
|
||
title: error?.data?.msg || '检查失败,请稍后重试',
|
||
icon: 'none',
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理审批同意
|
||
* 业务背景:审批人同意请假申请
|
||
*/
|
||
const handleApprove = async (item) => {
|
||
if (!item.id) {
|
||
uni.showToast({
|
||
title: '数据异常,无法审批',
|
||
icon: 'none',
|
||
})
|
||
return
|
||
}
|
||
|
||
uni.showModal({
|
||
title: '确认审批',
|
||
content: '确认同意该请假申请吗?',
|
||
cancelText: '再想想',
|
||
confirmText: '确认',
|
||
confirmColor: '#07c160',
|
||
success: async (res) => {
|
||
if (!res.confirm) return
|
||
try {
|
||
await approveLeaveRequestApi({
|
||
id: item.id,
|
||
approverComment: '同意',
|
||
approverStatus: 1,
|
||
})
|
||
uni.showToast({
|
||
title: '审批成功',
|
||
icon: 'success',
|
||
})
|
||
// 刷新列表
|
||
queryParams.value.pageNum = 1
|
||
recordList.value = []
|
||
await loadRecordList()
|
||
} catch (error) {
|
||
console.error('审批失败:', error)
|
||
uni.showToast({
|
||
title: error?.data?.msg || '审批失败,请重试',
|
||
icon: 'none',
|
||
})
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 处理审批拒绝
|
||
* 业务背景:审批人拒绝请假申请
|
||
*/
|
||
const handleReject = async (item) => {
|
||
if (!item.id) {
|
||
uni.showToast({
|
||
title: '数据异常,无法审批',
|
||
icon: 'none',
|
||
})
|
||
return
|
||
}
|
||
|
||
uni.showModal({
|
||
title: '确认拒绝',
|
||
content: '确认拒绝该请假申请吗?',
|
||
cancelText: '再想想',
|
||
confirmText: '确认拒绝',
|
||
confirmColor: '#ff4d4f',
|
||
success: async (res) => {
|
||
if (!res.confirm) return
|
||
try {
|
||
await rejectLeaveRequestApi({
|
||
id: item.id,
|
||
approverComment: '不同意',
|
||
approverStatus: 2,
|
||
})
|
||
uni.showToast({
|
||
title: '已拒绝',
|
||
icon: 'success',
|
||
})
|
||
// 刷新列表
|
||
queryParams.value.pageNum = 1
|
||
recordList.value = []
|
||
await loadRecordList()
|
||
} catch (error) {
|
||
console.error('拒绝失败:', error)
|
||
uni.showToast({
|
||
title: error?.msg || '操作失败,请重试',
|
||
icon: 'none',
|
||
})
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
const handleBack = () => {
|
||
uni.navigateBack()
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 初始化
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page-container {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
.content-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
padding: 20rpx;
|
||
padding-top: 0;
|
||
}
|
||
|
||
.tabs-container {
|
||
display: flex;
|
||
margin-top: 10rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 8rpx;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 10rpx 0;
|
||
position: relative;
|
||
transition: all 0.3s ease;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.tab-item.active {
|
||
background: linear-gradient(135deg, #07c160 0%, #06a050 100%);
|
||
box-shadow: 0 2rpx 8rpx rgba(7, 193, 96, 0.3);
|
||
}
|
||
|
||
.tab-text {
|
||
font-size: 30rpx;
|
||
color: #666;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.tab-item.active .tab-text {
|
||
color: #fff;
|
||
font-weight: 600;
|
||
}
|
||
|
||
// 表单样式
|
||
.scroll-container {
|
||
// flex: 1;
|
||
overflow-y: auto;
|
||
// padding: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.form-picker-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16rpx 24rpx;
|
||
background: #fff;
|
||
border-radius: 0;
|
||
border: 1rpx solid #dcdfe6;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
min-height: 70rpx;
|
||
}
|
||
|
||
.picker-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
flex: 1;
|
||
}
|
||
|
||
.picker-text.placeholder {
|
||
color: #999;
|
||
}
|
||
|
||
.char-count {
|
||
text-align: right;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.submit-section {
|
||
padding: 40rpx 0;
|
||
}
|
||
|
||
// 列表样式
|
||
.list-wrapper {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.filter-section {
|
||
margin: 10rpx 0;
|
||
}
|
||
|
||
.filter-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
// 选择框样式
|
||
::v-deep .filter-item .picker-wrapper {
|
||
border: 1rpx solid #dcdfe6;
|
||
background: #fff;
|
||
border-radius: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.list-container {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.record-item {
|
||
background: #fff;
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 16rpx;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.record-item:active {
|
||
transform: translateY(-2rpx);
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.record-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.record-user {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.record-title {
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.record-date {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.record-type {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.record-body {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.record-range {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
color: #555;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.record-range-text {
|
||
color: #555;
|
||
}
|
||
|
||
.record-days {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.record-status {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.status-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.status-tag {
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 24rpx;
|
||
border: 1rpx solid #dcdfe6;
|
||
color: #666;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
.status-success {
|
||
color: #07c160;
|
||
border-color: #07c160;
|
||
background: rgba(7, 193, 96, 0.08);
|
||
}
|
||
|
||
.status-success.status-outline {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.status-pending {
|
||
color: #ffb100;
|
||
border-color: #ffb100;
|
||
background: rgba(255, 177, 0, 0.12);
|
||
}
|
||
|
||
.status-error {
|
||
color: #ff4d4f;
|
||
border-color: #ff4d4f;
|
||
background: rgba(255, 77, 79, 0.12);
|
||
}
|
||
|
||
.record-actions {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.loading-text {
|
||
text-align: center;
|
||
padding: 40rpx 0;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.empty-state {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.back-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.back-btn:active {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
// 表单样式优化
|
||
::v-deep .up-form-item {
|
||
padding-bottom: 24rpx;
|
||
margin-bottom: 24rpx;
|
||
border-bottom: 1rpx solid #e0e0e0;
|
||
}
|
||
|
||
::v-deep .up-form-item:last-child {
|
||
border-bottom: none;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
::v-deep .up-form-item__body__left__content__label {
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
font-weight: 600;
|
||
width: 200rpx !important;
|
||
}
|
||
|
||
::v-deep .up-form-item__body__right {
|
||
flex: 1;
|
||
}
|
||
|
||
::v-deep .up-input {
|
||
border: 1rpx solid #dcdfe6;
|
||
border-radius: 0;
|
||
background: #fff;
|
||
width: 100%;
|
||
min-height: 70rpx;
|
||
}
|
||
|
||
::v-deep .up-input__content {
|
||
border: 1rpx solid #dcdfe6;
|
||
border-radius: 0;
|
||
}
|
||
|
||
::v-deep .up-input__content__field-wrapper__field {
|
||
padding: 16rpx 24rpx;
|
||
}
|
||
|
||
::v-deep .up-textarea {
|
||
border: 1rpx solid #dcdfe6;
|
||
border-radius: 0;
|
||
background: #fff;
|
||
width: 100%;
|
||
}
|
||
|
||
::v-deep .up-textarea__field {
|
||
border: 1rpx solid #dcdfe6;
|
||
border-radius: 0;
|
||
padding: 16rpx 24rpx;
|
||
}
|
||
|
||
::v-deep .up-form-item__body__right__message {
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
// 禁用输入框样式
|
||
::v-deep .up-input--disabled {
|
||
background: #f5f7fa;
|
||
color: #666;
|
||
}
|
||
|
||
::v-deep .up-input--disabled .up-input__content {
|
||
background: #f5f7fa;
|
||
}
|
||
</style>
|