5006ee68 by lttnew

考点添加照片

1 parent f7aaee44
......@@ -1691,10 +1691,18 @@ export function selfAdd(ids) {
}
export function commitExamPointApply(params) {
const data = {...(params || {})}
if (Array.isArray(data.photos)) {
data.photos = data.photos.filter(Boolean).join(',')
}
const query = Object.keys(data)
.filter(key => data[key] !== undefined && data[key] !== null && data[key] !== '')
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
.join('&')
return request({
url: `/member/examPointApply/commit?selfSelect=${params.selfSelect}`,
url: `/member/examPointApply/commit${query ? `?${query}` : ''}`,
method: 'post',
params
params: {}
})
}
......
// dev
// const baseUrl_api = 'http://192.168.1.159:8787'
const baseUrl_api = 'http://192.168.1.159:8787'
// const baseUrl_api = 'http://47.98.186.233:8787'
const baseUrl_api = 'https://tk001.wxjylt.com/stage-api/'
// const baseUrl_api = 'https://tk001.wxjylt.com/stage-api/'
const loginImage_api = 'https://tk001.wxjylt.com/stage-api'
const payUrl = 'https://wxpay.cmbc.com.cn/mobilePlatform/appserver/lcbpPay.do'
......
......@@ -7,6 +7,66 @@
<text v-else class="tips-text">单个审核</text>
</view> -->
<!-- 申请详情 -->
<view v-if="type === 'single' && detailForm.id" class="detail-section">
<view class="section-header">
<uni-icons color="#AD181F" size="18" type="paperclip"></uni-icons>
<text class="section-title">基本信息</text>
</view>
<view class="info-card">
<view class="info-row">
<text class="label">申请单位</text>
<text class="value">{{ detailForm.memName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">所属协会</text>
<text class="value">{{ detailForm.shenMemName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">审核状态</text>
<text :class="getStatusClass(getCurrentAuditStatus())" class="value">
{{ getStatusText(getCurrentAuditStatus()) }}
</text>
</view>
<view class="info-row">
<text class="label">是否需要省级协会指派</text>
<text class="value">{{ detailForm.selfSelect == 1 ? '否' : '是' }}</text>
</view>
<view class="info-row">
<text class="label">会员有效期</text>
<text class="value">{{ formatDate(detailForm.memValidDate) }}</text>
</view>
<view class="info-row">
<text class="label">申请日期</text>
<text class="value">{{ formatDate(detailForm.commitTime) }}</text>
</view>
<view class="info-row">
<text class="label">审核日期</text>
<text class="value">{{ formatDate(getCurrentAuditTime()) }}</text>
</view>
<view class="info-row">
<text class="label">考官</text>
<text class="value">{{ detailForm.examiners || '-' }}</text>
</view>
<!-- <view v-if="photoList.length > 0" class="venue-block">
<view class="venue-title">
<uni-icons color="#AD181F" size="16" type="image"></uni-icons>
<text>场馆照片</text>
</view>
<view class="photo-list">
<image
v-for="(item, index) in photoList"
:key="item"
class="venue-photo"
:src="item"
mode="aspectFill"
@click="previewPhoto(index)"
/>
</view>
</view> -->
</view>
</view>
<!-- 审核表单 -->
<view class="form-section">
<view class="section-header">
......@@ -57,7 +117,7 @@
</view>
<view v-if="userType==2&&selfSelect==0" class="section">
<!-- <view v-if="userType==2&&selfSelect==0" class="section">
<view class="section examiner-section">
<button class="add-btn" @click="handelAddExamine">+ 添加考官</button>
</view>
......@@ -72,7 +132,7 @@
<button class="del-btn" @click="handleDel(item)">删除</button>
</view>
</view>
</view>
</view> -->
<!-- 提交按钮 -->
......@@ -101,6 +161,7 @@
<script setup>
import * as api from '@/common/api_exam.js'
import { fillImgUrl } from '@/common/utils.js'
import {ref} from 'vue'
import {onLoad, onShow} from '@dcloudio/uni-app'
import {listApi, examinerDel} from "@/common/api.js";
......@@ -113,6 +174,8 @@ const selfSelect = ref('1')
const userType = ref('')
const list = ref([])
const memId = ref('')
const detailForm = ref({})
const photoList = ref([])
const app = getApp();
const form = ref({
flag: '1',
......@@ -145,6 +208,15 @@ onLoad((options) => {
}
userType.value = app.globalData.userType
if (options.item) {
try {
const itemData = JSON.parse(decodeURIComponent(options.item))
detailForm.value = itemData
photoList.value = normalizePhotos(itemData.photos)
} catch (e) {
console.error('解析审核详情失败', e)
}
}
console.log(userType.value)
})
......@@ -165,6 +237,49 @@ async function getExaminer() {
list.value = res.rows
}
function normalizePhotos(photos) {
if (!photos) return []
const list = Array.isArray(photos) ? photos : String(photos).split(',')
return list
.map(item => String(item || '').trim())
.filter(Boolean)
.map(item => fillImgUrl(item))
}
function previewPhoto(index) {
uni.previewImage({
current: photoList.value[index],
urls: photoList.value
})
}
function getStatusText(status) {
const statusMap = {1: '审核中', 2: '审核通过', 3: '审核拒绝'}
return statusMap[status] || '-'
}
function getStatusClass(status) {
const classMap = {
1: 'text-warning',
2: 'text-success',
3: 'text-danger'
}
return classMap[status] || ''
}
function getCurrentAuditStatus() {
return userType.value == 1 ? detailForm.value.auditStatus : detailForm.value.shenAuditStatus
}
function getCurrentAuditTime() {
return userType.value == 1 ? detailForm.value.auditTime : detailForm.value.shenAuditTime
}
function formatDate(dateStr) {
if (!dateStr) return '-'
return String(dateStr).substring(0, 10)
}
function doSubmit() {
if (form.value.flag == '0' && !form.value.reason) {
......@@ -272,7 +387,8 @@ async function confirmDel() {
}
}
.form-section {
.form-section,
.detail-section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
......@@ -292,6 +408,73 @@ async function confirmDel() {
}
}
.info-card {
.info-row {
display: flex;
justify-content: space-between;
padding: 16rpx 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 26rpx;
color: #999;
flex-shrink: 0;
}
.value {
flex: 1;
font-size: 26rpx;
color: #333;
text-align: right;
margin-left: 20rpx;
word-break: break-all;
&.text-success {
color: #4caf50;
}
&.text-warning {
color: #ff9800;
}
&.text-danger {
color: #f44336;
}
}
}
}
.photo-list {
display: flex;
flex-wrap: wrap;
gap: 18rpx;
}
.venue-block {
padding-top: 22rpx;
}
.venue-title {
display: flex;
align-items: center;
gap: 8rpx;
margin-bottom: 18rpx;
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.venue-photo {
width: 204rpx;
height: 204rpx;
border-radius: 14rpx;
background: #f5f5f5;
}
.form-card {
.form-item {
margin-bottom: 30rpx;
......
......@@ -44,6 +44,24 @@
</view>
</view>
<!-- 场馆照片 -->
<view v-if="photoList.length > 0" class="section">
<view class="section-header">
<uni-icons color="#AD181F" size="18" type="image"></uni-icons>
<text class="section-title">场馆照片</text>
</view>
<view class="photo-list">
<image
v-for="(item, index) in photoList"
:key="item"
class="venue-photo"
:src="item"
mode="aspectFill"
@click="previewPhoto(index)"
/>
</view>
</view>
<!-- 审核记录 -->
<view v-if="auditList.length > 0" class="section">
<view class="section-header">
......@@ -94,6 +112,7 @@
<script setup>
import * as api from '@/common/api_exam.js'
import { fillImgUrl } from '@/common/utils.js'
import {ref} from 'vue'
import {onLoad} from '@dcloudio/uni-app'
......@@ -101,6 +120,7 @@ const app = getApp()
const form = ref({})
const auditList = ref([])
const examinerList = ref([])
const photoList = ref([])
const userType = ref('')
const auditStatusMap = {1: '审核中', 2: '审核通过', 3: '审核拒绝'}
......@@ -113,6 +133,7 @@ onLoad((options) => {
if (itemData.auditLogs) {
auditList.value = JSON.parse(itemData.auditLogs)
}
photoList.value = normalizePhotos(itemData.photos)
if (itemData.memId) {
getExaminerList(itemData.memId)
}
......@@ -145,6 +166,22 @@ function getStatusClass(status) {
return classMap[status] || ''
}
function normalizePhotos(photos) {
if (!photos) return []
const list = Array.isArray(photos) ? photos : String(photos).split(',')
return list
.map(item => String(item || '').trim())
.filter(Boolean)
.map(item => fillImgUrl(item))
}
function previewPhoto(index) {
uni.previewImage({
current: index,
urls: photoList.value
})
}
function getCurrentAuditStatus() {
return userType.value == 1 ? form.value.auditStatus : form.value.shenAuditStatus
}
......@@ -282,6 +319,19 @@ function formatDateTime(dateStr) {
}
}
.photo-list {
display: flex;
flex-wrap: wrap;
gap: 18rpx;
}
.venue-photo {
width: 204rpx;
height: 204rpx;
border-radius: 14rpx;
background: #f5f5f5;
}
/* 考官表格 */
.examiner-table {
......
......@@ -339,10 +339,9 @@ function goView(item) {
}
function goAudit(item) {
// const itemStr = encodeURIComponent(JSON.stringify(item))
const itemStr = encodeURIComponent(JSON.stringify(item))
uni.navigateTo({
url: `/level/ztx/examinationAudit?ids=${item.id}&type=single&selfSelect=${item.selfSelect}&memId=${item.memId}`
url: `/level/ztx/examinationAudit?ids=${item.id}&type=single&selfSelect=${item.selfSelect}&memId=${item.memId}&item=${itemStr}`
})
}
......
......@@ -92,7 +92,8 @@ function handleAdd() {
const ids = selectedIds.value.join(',')
uni.showLoading({title: '添加中...'})
api.selfAdd(ids).then(() => {
const request = memId.value ? api.otherAdd(memId.value, ids) : api.selfAdd(ids)
request.then(() => {
uni.hideLoading()
uni.showToast({title: '添加成功', icon: 'success'})
uni.navigateBack()
......
......@@ -21,12 +21,17 @@
<!-- 温馨提示 -->
<view class="tip-box">
<text class="tip-text">温馨提示:
根据中国跆拳道协会考点管理办法请添加考点考官
<view class="tip-text">温馨提示:
</view>
<view class="tip-text">
根据中国跆拳道协会考点管理办法请添加考点考官。
</view>
<view class="tip-text">
如若已有考官:点击【录入考官信息】,根据相关信息填写考官资料进行录入。
如若没有考官:点击【申请省跆协指派考官】,选择省内相关考官并及时参加考官培训
完成考官设置后,才能继续提交考点申报。请您尽快操作,以免影响后续考试安排。
</text>
</view>
<view class="tip-text">如若没有考官:点击【申请省跆协指派考官】,选择省内相关考官并及时参加考官培训
完成考官设置后,才能继续提交考点申报。请您尽快操作,以免影响后续考试安排。</view>
</view>
<!-- 省跆协指派提示 -->
......@@ -142,7 +147,14 @@ let modalAction = '' // 'del', 'success', 'assign', 'apply'
let pendingDelItem = null
onLoad((option) => {
// memId.value = app.globalData.memberInfo.memId
memId.value = option.memId || app.globalData.memberInfo?.memId || ''
if (option.photos) {
try {
form.value.photos = JSON.parse(decodeURIComponent(option.photos))
} catch (e) {
form.value.photos = []
}
}
getExaminer()
})
......@@ -155,7 +167,7 @@ onShow(() => {
async function getExaminer() {
loading.value = true
const res = await api.listApi({memId: app.globalData.memberInfo.memId})
const res = await api.listApi({memId: memId.value || app.globalData.memberInfo?.memId})
list.value = res.rows
loading.value = false
}
......@@ -253,7 +265,7 @@ function searchAndAdd() {
// 执行添加考官
function doAddExaminer(row) {
uni.showLoading({ title: '添加中' })
api.selfAdd(row.perId).then(() => {
api.otherAdd(memId.value || app.globalData.memberInfo?.memId, row.perId).then(() => {
uni.hideLoading()
uni.showToast({ title: '添加成功', icon: 'success' })
addPopup.value.close()
......@@ -266,6 +278,12 @@ function doAddExaminer(row) {
// 提交申请
async function handelSubmit() {
if (!form.value.photos || form.value.photos.length === 0) {
return uni.showToast({title: '请先上传场馆照片', icon: 'none'})
}
if (Array.isArray(form.value.photos)) {
form.value.photos = form.value.photos.filter(Boolean).join(',')
}
if (!form.value.selfSelect) {
return uni.showToast({title: '请选择考官类型', icon: 'none'})
}
......
......@@ -161,6 +161,13 @@
}
},
{
"path": "pages/index/examPointVenueImage",
"style": {
"navigationBarTitleText": "场馆照片",
"enablePullDownRefresh": false
}
},
{
"path": "pages/index/orderList",
"style": {
"navigationBarTitleText": "订单列表",
......
<template>
<view class="venue-page">
<view class="content-card">
<view class="title">上传场馆照片</view>
<view class="desc">请上传 1~3 张清晰的场馆照片,照片将作为考点申请材料提交审核。</view>
<view class="photo-grid">
<view
v-for="(item, index) in photoList"
:key="item.url"
class="photo-item"
>
<image class="photo" :src="item.previewUrl" mode="aspectFill" @click="previewPhoto(index)" />
<view class="delete-btn" @click.stop="deletePhoto(index)">×</view>
</view>
<view v-if="photoList.length < 3" class="upload-card" @click="choosePhoto">
<view class="plus">+</view>
<view class="upload-text">添加照片</view>
</view>
</view>
<view class="tip">最多上传 3 张,支持从相册选择或拍照上传。</view>
</view>
<view class="footer">
<button class="back-btn" @click="goBack">上一步</button>
<button class="next-btn" @click="goNext">下一步</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import * as api from '@/common/api.js'
import { fillImgUrl } from '@/common/utils.js'
const photoList = ref([])
const memId = ref('')
onLoad((option) => {
memId.value = option.memId || ''
})
function choosePhoto() {
const count = 3 - photoList.value.length
if (count <= 0) return
uni.chooseImage({
count,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: async (res) => {
const paths = res.tempFilePaths || []
for (const path of paths) {
if (photoList.value.length >= 3) break
await uploadPhoto(path)
}
}
})
}
async function uploadPhoto(path) {
if (!path) return
try {
uni.showLoading({ title: '上传中', mask: true })
const res = await api.uploadImgCorpPhoto(path)
const url = getUploadUrl(res)
if (!url) {
uni.showToast({ title: '上传返回数据异常', icon: 'none' })
return
}
photoList.value.push({
url,
previewUrl: fillImgUrl(url)
})
} catch (err) {
uni.showToast({ title: '上传失败,请重新上传', icon: 'none' })
} finally {
uni.hideLoading()
}
}
function getUploadUrl(res) {
const data = res?.data
if (typeof data === 'string') return data
return data?.ms || data?.url || data?.fang || res?.msg || ''
}
function deletePhoto(index) {
photoList.value.splice(index, 1)
}
function previewPhoto(index) {
uni.previewImage({
current: index,
urls: photoList.value.map(item => item.previewUrl)
})
}
function goNext() {
if (photoList.value.length === 0) {
uni.showToast({ title: '请上传1~3张场馆照片', icon: 'none' })
return
}
const photos = encodeURIComponent(JSON.stringify(photoList.value.map(item => item.url)))
uni.navigateTo({
url: `/myCenter/examPointApply?photos=${photos}&memId=${memId.value || ''}`
})
}
function goBack() {
uni.navigateBack({ delta: 1 })
}
</script>
<style lang="scss" scoped>
.venue-page {
min-height: 100vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
.content-card {
flex: 1;
margin: 30rpx;
padding: 36rpx;
background: #fff;
border-radius: 18rpx;
}
.title {
font-size: 34rpx;
font-weight: 600;
color: #222;
}
.desc {
margin-top: 18rpx;
color: #777;
font-size: 26rpx;
line-height: 1.6;
}
.photo-grid {
display: flex;
flex-wrap: wrap;
gap: 22rpx;
margin-top: 36rpx;
}
.photo-item,
.upload-card {
position: relative;
width: 196rpx;
height: 196rpx;
border-radius: 16rpx;
overflow: hidden;
}
.photo {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 42rpx;
height: 42rpx;
line-height: 38rpx;
text-align: center;
border-radius: 50%;
color: #fff;
background: #C4121B;
font-size: 34rpx;
}
.upload-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 2rpx dashed #d7d7d7;
background: #fafafa;
}
.plus {
color: #C4121B;
font-size: 58rpx;
line-height: 1;
}
.upload-text {
margin-top: 12rpx;
color: #777;
font-size: 24rpx;
}
.tip {
margin-top: 26rpx;
color: #999;
font-size: 24rpx;
}
.footer {
padding: 30rpx;
display: flex;
justify-content: space-between;
background: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.back-btn,
.next-btn {
width: 48%;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
}
.back-btn {
background: #fff;
color: #C4121B;
border: 1rpx solid #C4121B;
}
.next-btn {
background: #C4121B;
color: #fff;
border: none;
}
</style>
......@@ -50,9 +50,18 @@
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const memId = ref('')
onLoad((option) => {
memId.value = option.memId || ''
})
function goApply() {
uni.navigateTo({
url: '/myCenter/examPointApply'
url: `/pages/index/examPointVenueImage?memId=${memId.value || ''}`
})
}
function goBack() {
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!