feat: 员工端二期 7 张单据表单页与通用详情渲染(#46)
This commit is contained in:
@@ -0,0 +1,749 @@
|
||||
<template>
|
||||
<view class="form-page">
|
||||
<uni-card :border="false" :padding="'16px'" title="基本信息">
|
||||
<view class="auto-row">
|
||||
<text class="label">报送人</text>
|
||||
<text class="value">{{ reporterName || '—' }}</text>
|
||||
</view>
|
||||
<view class="auto-row">
|
||||
<text class="label">报送部门</text>
|
||||
<text class="value">{{ reportOrgName || '未归属部门' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">生效日期<text class="required">*</text></text>
|
||||
<uni-datetime-picker
|
||||
v-model="form.effectiveDate"
|
||||
type="date"
|
||||
placeholder="选择生效日期"
|
||||
/>
|
||||
</view>
|
||||
<text class="hint">
|
||||
允许未来生效;不允许早于归档日的追溯修改,归档时若生效日期早于当天将被拒绝。
|
||||
</text>
|
||||
</uni-card>
|
||||
|
||||
<uni-card :border="false" :padding="'16px'" title="价格明细">
|
||||
<view v-for="(row, index) in lines" :key="row.key" class="line-card">
|
||||
<view class="line-head">
|
||||
<text class="line-title">第 {{ index + 1 }} 行</text>
|
||||
<text class="delete" @click="removeLine(index)">删除</text>
|
||||
</view>
|
||||
|
||||
<view class="line-row">
|
||||
<view class="line-col">
|
||||
<text class="field-label">名称<text class="required">*</text></text>
|
||||
<input
|
||||
v-model="row.itemName"
|
||||
class="field-input"
|
||||
type="text"
|
||||
maxlength="200"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</view>
|
||||
<view class="line-col">
|
||||
<text class="field-label">位置</text>
|
||||
<input
|
||||
v-model="row.location"
|
||||
class="field-input"
|
||||
type="text"
|
||||
maxlength="200"
|
||||
placeholder="选填"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="line-row">
|
||||
<view class="line-col">
|
||||
<text class="field-label">单位<text class="required">*</text></text>
|
||||
<input
|
||||
v-model="row.unit"
|
||||
class="field-input"
|
||||
type="text"
|
||||
maxlength="20"
|
||||
placeholder="如 项"
|
||||
/>
|
||||
</view>
|
||||
<view class="line-col">
|
||||
<text class="field-label">数量<text class="required">*</text></text>
|
||||
<input
|
||||
v-model="row.quantity"
|
||||
class="field-input"
|
||||
type="digit"
|
||||
placeholder="最多3位小数"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="line-row">
|
||||
<view class="line-col">
|
||||
<text class="field-label">单价(元)<text class="required">*</text></text>
|
||||
<input
|
||||
v-model="row.unitPrice"
|
||||
class="field-input"
|
||||
type="digit"
|
||||
placeholder="最多2位小数"
|
||||
/>
|
||||
</view>
|
||||
<view class="line-col">
|
||||
<text class="field-label">金额(元)</text>
|
||||
<text class="amount-value">{{ rowAmount(row) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field tight">
|
||||
<text class="field-label">备注</text>
|
||||
<input
|
||||
v-model="row.remark"
|
||||
class="field-input"
|
||||
type="text"
|
||||
maxlength="200"
|
||||
placeholder="选填"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="lines.length === 0" class="empty">请添加至少一行价格条目</view>
|
||||
|
||||
<button class="add-button" @click="addLine">添加明细行({{ lines.length }}/100)</button>
|
||||
|
||||
<view class="total-row">
|
||||
<text class="total-label">合计金额</text>
|
||||
<text class="total-value">¥{{ totalAmount }}</text>
|
||||
</view>
|
||||
</uni-card>
|
||||
|
||||
<uni-card :border="false" :padding="'16px'" title="附件">
|
||||
<view class="category-row">
|
||||
<text class="label">附件分类</text>
|
||||
<view class="chips">
|
||||
<view
|
||||
v-for="item in categories"
|
||||
:key="item.value"
|
||||
class="chip"
|
||||
:class="{ active: category === item.value }"
|
||||
@click="category = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="hint">提交时需至少上传 1 张签字件(经办人签字原件扫描件)。</text>
|
||||
|
||||
<view v-for="item in attachments" :key="item.id" class="attachment">
|
||||
<view class="attachment-main">
|
||||
<text class="attachment-name">{{ item.name }}</text>
|
||||
<text class="attachment-meta">
|
||||
{{ item.categoryName }} · {{ formatSize(item.length) }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="delete" @click="removeAttachment(item)">删除</text>
|
||||
</view>
|
||||
|
||||
<view v-if="attachments.length === 0" class="empty">暂无附件</view>
|
||||
|
||||
<button class="upload-button" :loading="uploading" @click="chooseAttachment">
|
||||
上传附件({{ attachments.length }}/9)
|
||||
</button>
|
||||
<text class="hint">支持 jpg/jpeg/png/pdf/doc/docx/xls/xlsx,单张不超过20MB</text>
|
||||
</uni-card>
|
||||
|
||||
<view class="footer">
|
||||
<button class="footer-button ghost" :loading="saving" @click="saveDraft">
|
||||
保存草稿
|
||||
</button>
|
||||
<button class="footer-button primary" :loading="submitting" @click="submit">
|
||||
提交
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import Decimal from 'decimal.js';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { getInstance, saveDocument, submitInstance } from '@/api/approval/instance';
|
||||
import { deleteAttachment, uploadAttachment } from '@/api/approval/attachment';
|
||||
import type { ApprovalAttachment } from '@/api/approval/model';
|
||||
import { showError, showSuccess } from '@/utils/app';
|
||||
|
||||
const FLOW_CODE = 'facility';
|
||||
const MAX_LINES = 100;
|
||||
|
||||
interface FacilityLine {
|
||||
key: number;
|
||||
itemName: string;
|
||||
location: string;
|
||||
unit: string;
|
||||
quantity: string;
|
||||
unitPrice: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
const categories = [
|
||||
{ value: 'SIGNED', label: '签字件' },
|
||||
{ value: 'VOUCHER', label: '凭证' },
|
||||
{ value: 'OTHER', label: '其他' }
|
||||
];
|
||||
|
||||
const mode = ref<'create' | 'edit'>('create');
|
||||
const instanceId = ref(0);
|
||||
const form = reactive({ effectiveDate: '' });
|
||||
const lines = ref<FacilityLine[]>([]);
|
||||
const attachments = ref<ApprovalAttachment[]>([]);
|
||||
const category = ref('SIGNED');
|
||||
const saving = ref(false);
|
||||
const submitting = ref(false);
|
||||
const uploading = ref(false);
|
||||
let lineKey = 1;
|
||||
|
||||
const reporterName = computed(
|
||||
() => userStore.info?.realName || userStore.info?.nickname || ''
|
||||
);
|
||||
const reportOrgName = computed(() => userStore.info?.organizationName || '');
|
||||
const totalAmount = computed(() => {
|
||||
let total = new Decimal(0);
|
||||
for (const row of lines.value) {
|
||||
const amount = rowAmount(row);
|
||||
if (amount) {
|
||||
total = total.add(new Decimal(amount));
|
||||
}
|
||||
}
|
||||
return total.toFixed(2);
|
||||
});
|
||||
|
||||
onLoad(options => {
|
||||
mode.value = options?.mode === 'edit' ? 'edit' : 'create';
|
||||
instanceId.value = Number(options?.id) || 0;
|
||||
if (instanceId.value) {
|
||||
loadDetail();
|
||||
} else {
|
||||
addLine();
|
||||
}
|
||||
});
|
||||
|
||||
function textOf(value: any): string {
|
||||
return value === undefined || value === null ? '' : String(value);
|
||||
}
|
||||
|
||||
function createLine(source?: Record<string, any>): FacilityLine {
|
||||
return {
|
||||
key: lineKey++,
|
||||
itemName: textOf(source?.itemName),
|
||||
location: textOf(source?.location),
|
||||
unit: textOf(source?.unit),
|
||||
quantity: textOf(source?.quantity),
|
||||
unitPrice: textOf(source?.unitPrice),
|
||||
remark: textOf(source?.remark)
|
||||
};
|
||||
}
|
||||
|
||||
function addLine() {
|
||||
if (lines.value.length >= MAX_LINES) {
|
||||
showError('明细不能超过100行', null);
|
||||
return;
|
||||
}
|
||||
lines.value.push(createLine());
|
||||
}
|
||||
|
||||
function removeLine(index: number) {
|
||||
lines.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/** 行金额:数量 × 单价,保留两位小数(HALF_UP),仅用于展示与提交 */
|
||||
function rowAmount(row: FacilityLine): string {
|
||||
if (!row.quantity || !row.unitPrice) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
return new Decimal(row.quantity)
|
||||
.mul(new Decimal(row.unitPrice))
|
||||
.toDecimalPlaces(2, Decimal.ROUND_HALF_UP)
|
||||
.toFixed(2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function isValidDecimal(value: string, scale: number): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const pattern = new RegExp('^\\d+(\\.\\d{1,' + scale + '})?$');
|
||||
return pattern.test(value.trim());
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
try {
|
||||
const detail = await getInstance(instanceId.value);
|
||||
if (!detail.permissions?.canEdit) {
|
||||
showError('当前状态不可修改', null);
|
||||
return;
|
||||
}
|
||||
const fields = detail.document?.fields || [];
|
||||
form.effectiveDate = fieldValue(fields, 'effectiveDate').substring(0, 10);
|
||||
const rows = detail.document?.lines || [];
|
||||
lines.value = rows.length > 0 ? rows.map(row => createLine(row)) : [createLine()];
|
||||
attachments.value = detail.attachments || [];
|
||||
} catch (error: any) {
|
||||
showError(error?.message || '加载失败', null);
|
||||
}
|
||||
}
|
||||
|
||||
function fieldValue(fields: Array<{ key: string; value?: string | null }>, key: string) {
|
||||
const field = fields.find(item => item.key === key);
|
||||
return field?.value ? String(field.value) : '';
|
||||
}
|
||||
|
||||
/** 本地日期 yyyy-MM-dd(不用 toISOString,避免时区偏移一天) */
|
||||
function today(): string {
|
||||
const now = new Date();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
return now.getFullYear() + '-' + month + '-' + day;
|
||||
}
|
||||
|
||||
function validate(): string | null {
|
||||
if (!form.effectiveDate) {
|
||||
return '请选择生效日期';
|
||||
}
|
||||
if (form.effectiveDate < today()) {
|
||||
return '生效日期不能早于今天,追溯修改不被允许';
|
||||
}
|
||||
if (lines.value.length === 0) {
|
||||
return '请至少填写一行明细';
|
||||
}
|
||||
for (let index = 0; index < lines.value.length; index++) {
|
||||
const row = lines.value[index];
|
||||
const lineNo = index + 1;
|
||||
if (!row.itemName.trim()) {
|
||||
return '第' + lineNo + '行名称不能为空';
|
||||
}
|
||||
if (row.itemName.trim().length > 200) {
|
||||
return '第' + lineNo + '行名称不能超过200字';
|
||||
}
|
||||
if (row.location.trim().length > 200) {
|
||||
return '第' + lineNo + '行位置不能超过200字';
|
||||
}
|
||||
if (!row.unit.trim()) {
|
||||
return '第' + lineNo + '行单位不能为空';
|
||||
}
|
||||
if (row.unit.trim().length > 20) {
|
||||
return '第' + lineNo + '行单位不能超过20字';
|
||||
}
|
||||
if (!isValidDecimal(row.quantity, 3)) {
|
||||
return '第' + lineNo + '行数量必须为数字,最多3位小数';
|
||||
}
|
||||
if (!isValidDecimal(row.unitPrice, 2)) {
|
||||
return '第' + lineNo + '行单价必须为数字,最多2位小数';
|
||||
}
|
||||
if (row.remark.trim().length > 200) {
|
||||
return '第' + lineNo + '行备注不能超过200字';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildLines(): Array<Record<string, any>> {
|
||||
return lines.value.map(row => ({
|
||||
itemName: row.itemName.trim(),
|
||||
location: row.location.trim() || undefined,
|
||||
unit: row.unit.trim(),
|
||||
quantity: row.quantity.trim(),
|
||||
unitPrice: row.unitPrice.trim(),
|
||||
amount: rowAmount(row),
|
||||
remark: row.remark.trim() || undefined
|
||||
}));
|
||||
}
|
||||
|
||||
async function ensureDraft(): Promise<number> {
|
||||
const message = validate();
|
||||
if (message) {
|
||||
showError(message, null);
|
||||
return 0;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
const id = await saveDocument(FLOW_CODE, {
|
||||
id: instanceId.value || undefined,
|
||||
effectiveDate: form.effectiveDate,
|
||||
lines: buildLines()
|
||||
});
|
||||
instanceId.value = id;
|
||||
mode.value = 'edit';
|
||||
return id;
|
||||
} catch (error: any) {
|
||||
showError(error?.message || '保存失败', null);
|
||||
return 0;
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDraft() {
|
||||
const id = await ensureDraft();
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
showSuccess('草稿已保存', null);
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
const id = await ensureDraft();
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提交确认',
|
||||
content: '提交后字段与附件将锁定,确认提交?',
|
||||
success: async result => {
|
||||
if (!result.confirm) {
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
try {
|
||||
const response = await submitInstance(id);
|
||||
showSuccess(response.message || '提交成功', () => {
|
||||
uni.redirectTo({
|
||||
url: '/packageApproval/pages/detail?id=' + id
|
||||
});
|
||||
});
|
||||
} catch (error: any) {
|
||||
showError(error?.message || '提交失败', null);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function chooseAttachment() {
|
||||
if (attachments.value.length >= 9) {
|
||||
showError('每单最多上传9张附件', null);
|
||||
return;
|
||||
}
|
||||
const remaining = 9 - attachments.value.length;
|
||||
// #ifdef H5
|
||||
(uni as any).chooseFile({
|
||||
count: remaining,
|
||||
extension: ['.jpg', '.jpeg', '.png', '.pdf', '.doc', '.docx', '.xls', '.xlsx'],
|
||||
success: (result: any) => uploadFiles(result.tempFiles || []),
|
||||
fail: () => undefined
|
||||
});
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
(uni as any).chooseMessageFile({
|
||||
count: remaining,
|
||||
type: 'file',
|
||||
success: (result: any) => uploadFiles(result.tempFiles || []),
|
||||
fail: () => undefined
|
||||
});
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
(uni as any).chooseImage({
|
||||
count: remaining,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['camera', 'album'],
|
||||
success: (result: any) =>
|
||||
(result.tempFilePaths || []).forEach((path: string) =>
|
||||
uploadFiles([{ path, name: path.split('/').pop() || '附件' }])
|
||||
),
|
||||
fail: () => undefined
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
|
||||
async function uploadFiles(files: Array<{ path: string; name?: string; size?: number }>) {
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
const id = await ensureDraft();
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
uploading.value = true;
|
||||
try {
|
||||
for (const file of files) {
|
||||
if (file.size && file.size > 20 * 1024 * 1024) {
|
||||
showError('单个附件不能超过20MB', null);
|
||||
continue;
|
||||
}
|
||||
const uploaded = await uploadAttachment(id, file.path, category.value);
|
||||
attachments.value = [...attachments.value, uploaded];
|
||||
}
|
||||
uni.showToast({ title: '上传成功', icon: 'success' });
|
||||
} catch (error: any) {
|
||||
showError(error?.message || '上传失败', null);
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeAttachment(item: ApprovalAttachment) {
|
||||
uni.showModal({
|
||||
title: '删除附件',
|
||||
content: '确认删除该附件?',
|
||||
success: async result => {
|
||||
if (!result.confirm) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteAttachment(item.id);
|
||||
attachments.value = attachments.value.filter(
|
||||
attachment => attachment.id !== item.id
|
||||
);
|
||||
uni.showToast({ title: '已删除', icon: 'success' });
|
||||
} catch (error: any) {
|
||||
showError(error?.message || '删除失败', null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formatSize(value?: number) {
|
||||
if (!value) {
|
||||
return '0 B';
|
||||
}
|
||||
if (value < 1024) {
|
||||
return value + ' B';
|
||||
}
|
||||
if (value < 1024 * 1024) {
|
||||
return (value / 1024).toFixed(1) + ' KB';
|
||||
}
|
||||
return (value / 1024 / 1024).toFixed(1) + ' MB';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.form-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f6f8;
|
||||
padding-bottom: 180rpx;
|
||||
}
|
||||
|
||||
.auto-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 160rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.field.tight {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #d81e06;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
height: 72rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 72rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.line-card {
|
||||
margin-top: 20rpx;
|
||||
padding: 16rpx;
|
||||
background: #fafbfc;
|
||||
border: 1rpx solid #ebeef5;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.line-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.line-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.line-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.line-col {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
display: block;
|
||||
height: 72rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 72rpx;
|
||||
color: #d81e06;
|
||||
background: #fff7f6;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
margin-top: 24rpx;
|
||||
font-size: 26rpx;
|
||||
background: #f7f8fa;
|
||||
color: #303133;
|
||||
border: 1rpx dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 24rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #d81e06;
|
||||
}
|
||||
|
||||
.category-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chip {
|
||||
padding: 8rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
background: #f2f3f5;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.chip.active {
|
||||
color: #ffffff;
|
||||
background: #d81e06;
|
||||
}
|
||||
|
||||
.attachment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.attachment-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.attachment-meta {
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.delete {
|
||||
margin-left: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: #d81e06;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 24rpx 0;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
background: #f7f8fa;
|
||||
color: #303133;
|
||||
border: 1rpx dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.hint {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #ffffff;
|
||||
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.footer-button {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.footer-button.primary {
|
||||
background: #d81e06;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.footer-button.ghost {
|
||||
background: #ffffff;
|
||||
color: #303133;
|
||||
border: 1rpx solid #dcdfe6;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user