You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
476 lines
22 KiB
476 lines
22 KiB
|
1 day ago
|
<template>
|
||
|
|
<view class="release-page">
|
||
|
|
<view class="nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
||
|
|
<view class="back" @tap="back"><uni-icons type="left" size="23" color="#173b32"></uni-icons></view>
|
||
|
|
<view>
|
||
|
|
<view class="nav-title">{{isEdit ? '编辑校园圈' : '发布校园圈'}}</view>
|
||
|
|
<view class="nav-sub">实名发布,内容和图片会进行安全审核</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
<scroll-view scroll-y class="form-scroll" :style="{ paddingTop: navHeight + 'px' }">
|
||
|
|
<view class="type-tabs">
|
||
|
|
<view class="type-tab" :class="{ active: form.postType === 'wall' }" @tap="chooseType('wall')">校园墙</view>
|
||
|
|
<view class="type-tab" :class="{ active: form.partnerType === 'game' }" @tap="chooseType('game')">游戏搭子</view>
|
||
|
|
<view class="type-tab" :class="{ active: form.partnerType === 'daily' }" @tap="chooseType('daily')">日常搭子</view>
|
||
|
|
<view class="type-tab" :class="{ active: form.partnerType === 'sport' }" @tap="chooseType('sport')">运动搭子</view>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<view class="card">
|
||
|
|
<view class="anonymous-row">
|
||
|
|
<view>
|
||
|
|
<view class="anonymous-title">匿名发布</view>
|
||
|
|
<view class="anonymous-desc">开启后统一显示默认头像和“半径同学”</view>
|
||
|
|
</view>
|
||
|
|
<switch :checked="form.anonymous" color="#34c7a2" @change="toggleAnonymous" />
|
||
|
|
</view>
|
||
|
|
<view class="field">
|
||
|
|
<view class="label">标题</view>
|
||
|
|
<input v-model.trim="form.title" maxlength="30" placeholder="一句话说明你想发布的内容,最多30字" />
|
||
|
|
</view>
|
||
|
|
<view class="field">
|
||
|
|
<view class="label">二级分类</view>
|
||
|
|
<picker :range="categoryNames" @change="chooseCategory">
|
||
|
|
<view class="picker-value">{{selectedCategoryName || '请选择分类'}} <text>›</text></view>
|
||
|
|
</picker>
|
||
|
|
</view>
|
||
|
|
<view class="field">
|
||
|
|
<view class="label">正文</view>
|
||
|
|
<textarea v-model.trim="form.contentText" maxlength="1000" placeholder="友善真实地描述,禁止违规内容" />
|
||
|
|
</view>
|
||
|
|
<view class="field" v-if="form.postType === 'partner'">
|
||
|
|
<view class="label">地点 <text>最多30字,可不填</text></view>
|
||
|
|
<input v-model.trim="form.location" maxlength="30" placeholder="例如:东操场、图书馆三楼" />
|
||
|
|
</view>
|
||
|
|
<view class="field" v-if="form.postType === 'partner'">
|
||
|
|
<view class="label">时间 <text>最多20字,可不填</text></view>
|
||
|
|
<input v-model.trim="form.activityTime" maxlength="20" placeholder="例如:今晚8点、周末下午" />
|
||
|
|
</view>
|
||
|
|
<view class="field" v-if="form.postType === 'partner'">
|
||
|
|
<view class="label">人数 <text>填写整数,可不填</text></view>
|
||
|
|
<input v-model="form.targetCount" type="number" placeholder="期望人数,可不填" @input="onTargetCountInput" />
|
||
|
|
</view>
|
||
|
|
<view class="field" v-if="tagOptions.length">
|
||
|
|
<view class="label">热门标签</view>
|
||
|
|
<view class="tag-list">
|
||
|
|
<view class="tag" :class="{ active: form.tags.indexOf(item.name) > -1 }" v-for="item in tagOptions" :key="item.key" @tap="toggleTag(item.name)">
|
||
|
|
{{item.name}}
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
<view class="field">
|
||
|
|
<view class="label">联系方式 <text>微信/手机号可选填</text></view>
|
||
|
|
<input v-model.trim="form.contactWechat" maxlength="20" placeholder="微信号,可不填" />
|
||
|
|
<input v-model.trim="form.contactPhone" type="number" maxlength="11" placeholder="手机号,可不填" @input="onContactPhoneInput" />
|
||
|
|
</view>
|
||
|
|
<view class="field">
|
||
|
|
<view class="label">图片 <text>最多9张</text></view>
|
||
|
|
<view class="image-list">
|
||
|
|
<view class="image-item" v-for="(item,index) in form.images" :key="item">
|
||
|
|
<image :src="item" mode="aspectFill"></image>
|
||
|
|
<view class="remove" @tap="removeImage(index)">×</view>
|
||
|
|
</view>
|
||
|
|
<view class="image-add" v-if="form.images.length < 9" hover-class="none" @tap.stop="chooseImages" @click.stop="chooseImages">
|
||
|
|
<uni-icons type="plusempty" size="28" color="#7d918c"></uni-icons>
|
||
|
|
<text>上传</text>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
<view class="audit-inline" :class="{ fail: imageAuditStatus === 'fail' }" v-if="imageAuditing || imageAuditMessage">
|
||
|
|
<view class="mini-spinner" v-if="imageAuditing"></view>
|
||
|
|
<text>{{imageAuditing ? '图片审核中,请稍候...' : imageAuditMessage}}</text>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
<view class="safe-tip">发布、评论和聊天都会进行内容审核,违规内容会记录用于审核计费和后台治理。</view>
|
||
|
|
<view class="audit-inline form-audit" :class="{ fail: formAuditStatus === 'fail' }" v-if="formAuditMessage && !submitting">
|
||
|
|
<text>{{formAuditMessage}}</text>
|
||
|
|
</view>
|
||
|
|
<view class="bottom-space"></view>
|
||
|
|
</scroll-view>
|
||
|
|
|
||
|
|
<view class="submit-bar">
|
||
|
|
<view class="submit-btn" :class="{ disabled: submitting }" @tap="submit">{{submitting ? '提交审核中...' : '提交发布'}}</view>
|
||
|
|
</view>
|
||
|
|
<view class="audit-mask" v-if="submitting">
|
||
|
|
<view class="audit-card">
|
||
|
|
<view class="spinner"></view>
|
||
|
|
<view class="audit-title">表单内容审核中</view>
|
||
|
|
<view class="audit-desc">图片、标题和正文正在审核,请勿重复点击或退出页面</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
<ie-auth-dialog />
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import { getIeProfile } from '@/common/ieApi.js'
|
||
|
|
import { ensureIeVerifiedBeforeAction } from '@/common/ieRealNameAuth.js'
|
||
|
|
import {
|
||
|
|
listCampusCategories,
|
||
|
|
getCampusPostDetail,
|
||
|
|
publishCampusPost,
|
||
|
|
uploadCampusReleaseImage
|
||
|
|
} from '@/common/campusApi.js'
|
||
|
|
import IeAuthDialog from '@/package1/components/ie-auth-dialog/ie-auth-dialog.vue'
|
||
|
|
|
||
|
|
export default {
|
||
|
|
components: { IeAuthDialog },
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
statusBarTop: 24,
|
||
|
|
editId: '',
|
||
|
|
isEdit: false,
|
||
|
|
categories: [],
|
||
|
|
tags: [],
|
||
|
|
selectedCategoryIndex: -1,
|
||
|
|
submitting: false,
|
||
|
|
submitLocked: false,
|
||
|
|
imageChoosing: false,
|
||
|
|
imageAuditing: false,
|
||
|
|
imageAuditStatus: '',
|
||
|
|
imageAuditMessage: '',
|
||
|
|
formAuditStatus: '',
|
||
|
|
formAuditMessage: '',
|
||
|
|
form: {
|
||
|
|
postType: 'wall',
|
||
|
|
partnerType: '',
|
||
|
|
title: '',
|
||
|
|
categoryId: '',
|
||
|
|
categoryName: '',
|
||
|
|
content: '',
|
||
|
|
contentText: '',
|
||
|
|
images: [],
|
||
|
|
tags: [],
|
||
|
|
location: '',
|
||
|
|
activityTime: '',
|
||
|
|
targetCount: '',
|
||
|
|
contactWechat: '',
|
||
|
|
contactPhone: '',
|
||
|
|
anonymous: false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
computed: {
|
||
|
|
navHeight() {
|
||
|
|
return this.statusBarTop + uni.upx2px(120)
|
||
|
|
},
|
||
|
|
currentParentCode() {
|
||
|
|
return this.form.postType === 'wall' ? 'wall' : this.form.partnerType
|
||
|
|
},
|
||
|
|
categoryNames() {
|
||
|
|
return this.categories.map(item => item.name)
|
||
|
|
},
|
||
|
|
selectedCategoryName() {
|
||
|
|
const item = this.categories[this.selectedCategoryIndex]
|
||
|
|
return item ? item.name : ''
|
||
|
|
},
|
||
|
|
tagOptions() {
|
||
|
|
return this.tags
|
||
|
|
.filter(item => item.parentCode === this.currentParentCode)
|
||
|
|
.map((item, index) => ({
|
||
|
|
...item,
|
||
|
|
key: item.id || item.code || item.name || String(index)
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
},
|
||
|
|
onLoad(options = {}) {
|
||
|
|
if (options.type && options.type !== 'wall') this.chooseType(options.type)
|
||
|
|
if (options.id) {
|
||
|
|
this.editId = options.id
|
||
|
|
this.isEdit = true
|
||
|
|
}
|
||
|
|
if (uni.getMenuButtonBoundingClientRect) {
|
||
|
|
this.statusBarTop = uni.getMenuButtonBoundingClientRect().top || this.statusBarTop
|
||
|
|
}
|
||
|
|
this.loadMeta()
|
||
|
|
if (this.isEdit) this.loadEditDetail()
|
||
|
|
this.ensureVerified(true)
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
back() {
|
||
|
|
uni.navigateBack()
|
||
|
|
},
|
||
|
|
getArea() {
|
||
|
|
const area = uni.getStorageSync('area')
|
||
|
|
if (!area) return {}
|
||
|
|
if (typeof area === 'string') {
|
||
|
|
try { return JSON.parse(area) } catch (e) { return {} }
|
||
|
|
}
|
||
|
|
return area
|
||
|
|
},
|
||
|
|
chooseType(type) {
|
||
|
|
if (type === 'wall') {
|
||
|
|
this.form.postType = 'wall'
|
||
|
|
this.form.partnerType = ''
|
||
|
|
} else {
|
||
|
|
this.form.postType = 'partner'
|
||
|
|
this.form.partnerType = type
|
||
|
|
}
|
||
|
|
this.selectedCategoryIndex = -1
|
||
|
|
this.form.categoryId = ''
|
||
|
|
this.form.categoryName = ''
|
||
|
|
this.form.tags = []
|
||
|
|
this.loadMeta()
|
||
|
|
},
|
||
|
|
loadMeta() {
|
||
|
|
const area = this.getArea()
|
||
|
|
Promise.all([
|
||
|
|
listCampusCategories({ regionId: area.id || '', parentCode: this.currentParentCode, categoryType: 'category' }),
|
||
|
|
listCampusCategories({ regionId: area.id || '', categoryType: 'tag' })
|
||
|
|
]).then(([categories, tags]) => {
|
||
|
|
this.categories = Array.isArray(categories) ? categories : []
|
||
|
|
this.tags = Array.isArray(tags) ? tags : []
|
||
|
|
this.applyEditCategory()
|
||
|
|
})
|
||
|
|
},
|
||
|
|
loadEditDetail() {
|
||
|
|
getCampusPostDetail(this.editId).then(detail => {
|
||
|
|
if (!detail) return
|
||
|
|
this.form = {
|
||
|
|
...this.form,
|
||
|
|
postType: detail.postType || 'wall',
|
||
|
|
partnerType: detail.partnerType || '',
|
||
|
|
title: detail.title || '',
|
||
|
|
categoryId: detail.categoryId || '',
|
||
|
|
categoryName: detail.categoryName || '',
|
||
|
|
content: detail.content || detail.contentText || '',
|
||
|
|
contentText: detail.contentText || '',
|
||
|
|
images: detail.imageList || [],
|
||
|
|
tags: detail.tagList || [],
|
||
|
|
location: detail.location || '',
|
||
|
|
activityTime: detail.activityTime || '',
|
||
|
|
targetCount: detail.targetCount || '',
|
||
|
|
contactWechat: detail.contactWechat || '',
|
||
|
|
contactPhone: detail.contactPhone || '',
|
||
|
|
anonymous: detail.userName === '半径同学' && !detail.userAvatar
|
||
|
|
}
|
||
|
|
this.applyEditCategory()
|
||
|
|
})
|
||
|
|
},
|
||
|
|
applyEditCategory() {
|
||
|
|
if (!this.form.categoryId || !this.categories.length) return
|
||
|
|
this.selectedCategoryIndex = this.categories.findIndex(item => item.id === this.form.categoryId)
|
||
|
|
},
|
||
|
|
chooseCategory(e) {
|
||
|
|
const index = Number(e.detail.value)
|
||
|
|
const item = this.categories[index]
|
||
|
|
if (!item) return
|
||
|
|
this.selectedCategoryIndex = index
|
||
|
|
this.form.categoryId = item.id
|
||
|
|
this.form.categoryName = item.name
|
||
|
|
},
|
||
|
|
toggleTag(tag) {
|
||
|
|
const index = this.form.tags.indexOf(tag)
|
||
|
|
if (index > -1) this.form.tags.splice(index, 1)
|
||
|
|
else this.form.tags.push(tag)
|
||
|
|
},
|
||
|
|
toggleAnonymous(e) {
|
||
|
|
this.form.anonymous = !!(e && e.detail && e.detail.value)
|
||
|
|
},
|
||
|
|
onTargetCountInput(e) {
|
||
|
|
const value = String((e && e.detail && e.detail.value) || '').replace(/[^\d]/g, '')
|
||
|
|
this.form.targetCount = value
|
||
|
|
return value
|
||
|
|
},
|
||
|
|
onContactPhoneInput(e) {
|
||
|
|
const value = String((e && e.detail && e.detail.value) || '').replace(/[^\d]/g, '').slice(0, 11)
|
||
|
|
this.form.contactPhone = value
|
||
|
|
return value
|
||
|
|
},
|
||
|
|
chooseImages() {
|
||
|
|
if (this.imageChoosing || this.imageAuditing) return
|
||
|
|
this.imageChoosing = true
|
||
|
|
this.imageAuditStatus = ''
|
||
|
|
this.imageAuditMessage = ''
|
||
|
|
uni.chooseImage({
|
||
|
|
count: 9 - this.form.images.length,
|
||
|
|
sizeType: ['compressed'],
|
||
|
|
success: async res => {
|
||
|
|
const paths = res.tempFilePaths || []
|
||
|
|
if (!paths.length) return
|
||
|
|
this.imageAuditing = true
|
||
|
|
try {
|
||
|
|
for (const path of paths) {
|
||
|
|
const audit = await uploadCampusReleaseImage(path)
|
||
|
|
if (audit && audit.passed && audit.url) {
|
||
|
|
this.form.images.push(audit.url)
|
||
|
|
} else {
|
||
|
|
this.imageAuditStatus = 'fail'
|
||
|
|
this.imageAuditMessage = '图片审核不通过:' + ((audit && audit.message) || '请更换图片后重试')
|
||
|
|
uni.showToast({ title: this.imageAuditMessage, icon: 'none', duration: 3000 })
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (this.imageAuditStatus !== 'fail') {
|
||
|
|
this.imageAuditMessage = ''
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
this.imageAuditStatus = 'fail'
|
||
|
|
this.imageAuditMessage = '图片审核失败,请稍后重试'
|
||
|
|
uni.showToast({ title: this.imageAuditMessage, icon: 'none', duration: 3000 })
|
||
|
|
} finally {
|
||
|
|
this.imageAuditing = false
|
||
|
|
}
|
||
|
|
},
|
||
|
|
fail: err => {
|
||
|
|
if (err && err.errMsg && err.errMsg.indexOf('cancel') === -1) {
|
||
|
|
uni.showToast({ title: '无法选择图片,请检查相册权限', icon: 'none' })
|
||
|
|
}
|
||
|
|
},
|
||
|
|
complete: () => {
|
||
|
|
this.imageChoosing = false
|
||
|
|
}
|
||
|
|
})
|
||
|
|
},
|
||
|
|
removeImage(index) {
|
||
|
|
this.form.images.splice(index, 1)
|
||
|
|
},
|
||
|
|
validate() {
|
||
|
|
this.form.title = (this.form.title || '').trim()
|
||
|
|
this.form.contentText = (this.form.contentText || '').trim()
|
||
|
|
this.form.location = (this.form.location || '').trim()
|
||
|
|
this.form.activityTime = (this.form.activityTime || '').trim()
|
||
|
|
this.form.targetCount = String(this.form.targetCount || '').trim()
|
||
|
|
this.form.contactWechat = (this.form.contactWechat || '').trim()
|
||
|
|
this.form.contactPhone = String(this.form.contactPhone || '').trim()
|
||
|
|
if (!this.form.title) return '请填写标题'
|
||
|
|
if (this.form.title.length > 30) return '标题不能超过30字'
|
||
|
|
if (!this.form.categoryId) return '请选择二级分类'
|
||
|
|
if (this.imageAuditing) return '图片正在审核中,请稍候'
|
||
|
|
if (this.form.contentText.length > 1000) return '正文不能超过1000字'
|
||
|
|
if (this.form.postType === 'partner' && !this.form.partnerType) return '请选择搭子类型'
|
||
|
|
if (this.form.location.length > 30) return '地点不能超过30字'
|
||
|
|
if (this.form.activityTime.length > 20) return '时间不能超过20字'
|
||
|
|
if (this.form.targetCount && (!/^\d+$/.test(this.form.targetCount) || Number(this.form.targetCount) < 1)) return '人数请填写正整数'
|
||
|
|
if (this.form.contactWechat && !/^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/.test(this.form.contactWechat)) return '微信号格式不正确'
|
||
|
|
if (this.form.contactPhone && !/^1[3-9]\d{9}$/.test(this.form.contactPhone)) return '手机号格式不正确'
|
||
|
|
return ''
|
||
|
|
},
|
||
|
|
goReleaseHome() {
|
||
|
|
uni.redirectTo({
|
||
|
|
url: '/package1/tabbar/release',
|
||
|
|
fail: () => uni.navigateBack()
|
||
|
|
})
|
||
|
|
},
|
||
|
|
async ensureVerified(redirectOnFail = false) {
|
||
|
|
const profile = await getIeProfile().catch(() => null)
|
||
|
|
const ok = await ensureIeVerifiedBeforeAction({
|
||
|
|
profile: profile || {},
|
||
|
|
actionText: '发布校园圈内容',
|
||
|
|
authDialog: { badge: '校园圈', title: '实名认证', desc: '发布、评论和聊天前需要完成实名认证' },
|
||
|
|
reload: getIeProfile
|
||
|
|
})
|
||
|
|
if (!ok && redirectOnFail) {
|
||
|
|
setTimeout(() => this.goReleaseHome(), 120)
|
||
|
|
}
|
||
|
|
return ok
|
||
|
|
},
|
||
|
|
async submit() {
|
||
|
|
if (this.submitLocked || this.submitting) return
|
||
|
|
const error = this.validate()
|
||
|
|
if (error) {
|
||
|
|
uni.showToast({ title: error, icon: 'none' })
|
||
|
|
return
|
||
|
|
}
|
||
|
|
const verified = await this.ensureVerified(true)
|
||
|
|
if (!verified) return
|
||
|
|
this.submitLocked = true
|
||
|
|
this.submitting = true
|
||
|
|
this.formAuditStatus = ''
|
||
|
|
this.formAuditMessage = ''
|
||
|
|
const area = this.getArea()
|
||
|
|
publishCampusPost({
|
||
|
|
id: this.editId || undefined,
|
||
|
|
regionId: area.id || '',
|
||
|
|
regionName: area.name || area.title || '',
|
||
|
|
...this.getSubmitUserProfile(),
|
||
|
|
...this.form,
|
||
|
|
content: this.form.contentText,
|
||
|
|
targetCount: this.form.targetCount ? Number(this.form.targetCount) : undefined
|
||
|
|
}).then(post => {
|
||
|
|
if (!post) throw new Error('发布失败')
|
||
|
|
const publishedAt = Date.now()
|
||
|
|
uni.setStorageSync('campusPostPublishedAt', publishedAt)
|
||
|
|
uni.$emit('campusPostPublished', { id: post.id || this.editId || '', publishedAt })
|
||
|
|
uni.$emit('campusUnreadChange')
|
||
|
|
uni.showToast({ title: '发布成功', icon: 'success' })
|
||
|
|
setTimeout(() => uni.navigateBack(), 600)
|
||
|
|
}).catch(err => {
|
||
|
|
this.submitLocked = false
|
||
|
|
const message = (err && err.message) || '发布失败,请稍后重试'
|
||
|
|
this.formAuditStatus = 'fail'
|
||
|
|
this.formAuditMessage = message.indexOf('审核') > -1 || message.indexOf('违规') > -1
|
||
|
|
? '表单审核不通过:' + message
|
||
|
|
: message
|
||
|
|
uni.showToast({ title: this.formAuditMessage, icon: 'none', duration: 3000 })
|
||
|
|
}).finally(() => {
|
||
|
|
this.submitting = false
|
||
|
|
})
|
||
|
|
},
|
||
|
|
getSubmitUserProfile() {
|
||
|
|
if (this.form.anonymous) {
|
||
|
|
return {
|
||
|
|
userName: '半径同学',
|
||
|
|
userAvatar: ''
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
userName: uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '',
|
||
|
|
userAvatar: uni.getStorageSync('avatar') || uni.getStorageSync('avatarUrl') || uni.getStorageSync('headimgurl') || ''
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.release-page { position: relative; min-height: 100vh; overflow: hidden; background: radial-gradient(circle at 12% 8%, rgba(68, 224, 186, .32), transparent 260rpx), radial-gradient(circle at 88% 18%, rgba(93, 175, 255, .18), transparent 300rpx), linear-gradient(180deg, #effff9 0%, #f7fbfa 44%, #ffffff 100%); }
|
||
|
|
.release-page::before { content: ''; position: fixed; right: -130rpx; top: 210rpx; z-index: 0; width: 280rpx; height: 280rpx; border-radius: 50%; background: rgba(44, 211, 175, .16); filter: blur(10rpx); pointer-events: none; animation: releaseGlow 4s ease-in-out infinite; }
|
||
|
|
.release-page::after { content: ''; position: fixed; left: -150rpx; bottom: 220rpx; z-index: 0; width: 320rpx; height: 320rpx; border-radius: 50%; background: rgba(58, 214, 166, .08); filter: blur(12rpx); pointer-events: none; animation: releaseGlow 5s ease-in-out infinite reverse; }
|
||
|
|
.nav { position: fixed; left: 0; right: 0; top: 0; z-index: 10; padding: 20rpx 28rpx 24rpx; display: flex; align-items: center; gap: 18rpx; background: linear-gradient(180deg, rgba(237, 255, 249, .98), rgba(247, 255, 252, .94)); box-shadow: 0 10rpx 28rpx rgba(31, 120, 94, .07); }
|
||
|
|
.back { width: 68rpx; height: 68rpx; border-radius: 50%; background: rgba(255,255,255,.88); display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 24rpx rgba(34, 100, 82, .10); }
|
||
|
|
.nav-title { font-size: 36rpx; font-weight: 900; color: #173b32; letter-spacing: -1rpx; }
|
||
|
|
.nav-sub { font-size: 22rpx; color: #6f817c; margin-top: 6rpx; }
|
||
|
|
.form-scroll { height: 100vh; box-sizing: border-box; }
|
||
|
|
.type-tabs { position: relative; z-index: 1; display: grid; grid-template-columns: repeat(4, 1fr); gap: 14rpx; padding: 30rpx 26rpx 24rpx; }
|
||
|
|
.type-tab { padding: 20rpx 8rpx; border-radius: 24rpx; background: rgba(255,255,255,.82); color: #546861; text-align: center; font-size: 24rpx; font-weight: 700; box-shadow: 0 10rpx 24rpx rgba(34, 82, 73, .06); border: 2rpx solid rgba(255,255,255,.8); }
|
||
|
|
.type-tab.active { background: linear-gradient(135deg, #34d8ad, #25c6c5); color: #fff; font-weight: 900; box-shadow: 0 14rpx 30rpx rgba(38, 196, 169, .24); }
|
||
|
|
.card { position: relative; z-index: 1; margin: 0 24rpx; padding: 28rpx 26rpx 18rpx; background: rgba(255,255,255,.94); border-radius: 34rpx; box-shadow: 0 18rpx 42rpx rgba(40, 84, 75, .10); border: 1rpx solid rgba(255,255,255,.9); backdrop-filter: blur(8rpx); }
|
||
|
|
.anonymous-row { display: flex; align-items: center; justify-content: space-between; padding: 0 0 26rpx; border-bottom: 1rpx solid #edf4f1; }
|
||
|
|
.anonymous-title { color: #263d38; font-size: 28rpx; font-weight: 900; }
|
||
|
|
.anonymous-desc { margin-top: 8rpx; color: #94a39e; font-size: 22rpx; }
|
||
|
|
.field { padding: 28rpx 0; border-bottom: 1rpx solid #edf4f1; }
|
||
|
|
.field:last-child { border-bottom: 0; }
|
||
|
|
.label { font-size: 28rpx; font-weight: 900; color: #263d38; margin-bottom: 18rpx; }
|
||
|
|
.label text { color: #94a39e; font-size: 22rpx; font-weight: 400; }
|
||
|
|
input, textarea { width: 100%; box-sizing: border-box; font-size: 26rpx; color: #263d38; background: #f6fbf9; border-radius: 22rpx; padding: 0 22rpx; margin-bottom: 16rpx; border: 1rpx solid rgba(225, 238, 234, .9); box-shadow: inset 0 2rpx 0 rgba(255,255,255,.8); }
|
||
|
|
input { height: 72rpx; line-height: 72rpx; }
|
||
|
|
textarea { height: 260rpx; padding-top: 20rpx; line-height: 1.6; }
|
||
|
|
.picker-value { min-height: 72rpx; background: #f6fbf9; border-radius: 22rpx; padding: 0 22rpx; color: #526761; display: flex; align-items: center; justify-content: space-between; border: 1rpx solid rgba(225, 238, 234, .9); }
|
||
|
|
.tag-list { display: flex; flex-wrap: wrap; gap: 12rpx; }
|
||
|
|
.tag { padding: 12rpx 20rpx; border-radius: 999rpx; background: #eef7f4; color: #526761; font-size: 23rpx; font-weight: 700; }
|
||
|
|
.tag.active { background: linear-gradient(135deg, #34d8ad, #25c6c5); color: #fff; box-shadow: 0 8rpx 18rpx rgba(38, 196, 169, .18); }
|
||
|
|
.image-list { position: relative; z-index: 2; display: flex; flex-wrap: wrap; gap: 14rpx; }
|
||
|
|
.image-item, .image-add { width: 154rpx; height: 154rpx; border-radius: 22rpx; overflow: hidden; position: relative; background: #f3f8f6; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #7d918c; font-size: 22rpx; box-shadow: none; }
|
||
|
|
.image-add { border: 2rpx dashed #d8e6e1; background: #f8fbfa; color: #788b86; }
|
||
|
|
.image-add:active { background: #eef7f3; }
|
||
|
|
.image-add text { margin-top: 6rpx; }
|
||
|
|
.image-item image { width: 100%; height: 100%; }
|
||
|
|
.remove { position: absolute; right: 8rpx; top: 8rpx; width: 34rpx; height: 34rpx; line-height: 34rpx; text-align: center; background: rgba(0,0,0,.5); color: #fff; border-radius: 50%; }
|
||
|
|
.audit-inline { display: flex; align-items: center; gap: 10rpx; margin-top: 18rpx; padding: 16rpx 18rpx; border-radius: 18rpx; background: #effbf7; color: #2c8b72; font-size: 23rpx; line-height: 1.45; }
|
||
|
|
.audit-inline.fail { background: #fff1ef; color: #d64b3c; }
|
||
|
|
.form-audit { position: relative; z-index: 1; margin: 18rpx 30rpx 0; }
|
||
|
|
.mini-spinner { width: 26rpx; height: 26rpx; flex: 0 0 26rpx; border-radius: 50%; border: 4rpx solid #c9f2e8; border-top-color: #2fc49f; animation: spin .8s linear infinite; }
|
||
|
|
.safe-tip { position: relative; z-index: 1; margin: 24rpx 30rpx; padding: 18rpx 22rpx; border-radius: 22rpx; background: rgba(235, 250, 246, .86); color: #6f837d; font-size: 23rpx; line-height: 1.6; }
|
||
|
|
.bottom-space { height: 240rpx; }
|
||
|
|
.submit-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 30; padding: 20rpx 28rpx 34rpx; background: rgba(255,255,255,.98); box-shadow: 0 -8rpx 28rpx rgba(31, 67, 59, .08); }
|
||
|
|
.submit-btn { height: 92rpx; line-height: 92rpx; text-align: center; border-radius: 999rpx; background: linear-gradient(135deg, #34d8ad, #25c6c5); color: #fff; font-size: 30rpx; font-weight: 900; box-shadow: 0 14rpx 30rpx rgba(38, 196, 169, .28); }
|
||
|
|
.submit-btn.disabled { opacity: .65; }
|
||
|
|
.audit-mask { position: fixed; inset: 0; z-index: 50; background: rgba(20, 35, 31, .45); display: flex; align-items: center; justify-content: center; }
|
||
|
|
.audit-card { width: 440rpx; padding: 44rpx 34rpx; border-radius: 32rpx; background: #fff; text-align: center; }
|
||
|
|
.spinner { width: 54rpx; height: 54rpx; margin: 0 auto 20rpx; border-radius: 50%; border: 6rpx solid #d8f5ee; border-top-color: #34c7a2; animation: spin .8s linear infinite; }
|
||
|
|
.audit-title { font-size: 30rpx; font-weight: 800; color: #183a32; }
|
||
|
|
.audit-desc { margin-top: 10rpx; color: #788883; font-size: 24rpx; }
|
||
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||
|
|
@keyframes releaseGlow { 0%, 100% { transform: scale(.92); opacity: .65; } 50% { transform: scale(1.08); opacity: 1; } }
|
||
|
|
</style>
|