Browse Source

接口对接

master
“wangzihua” 3 months ago
parent
commit
5168b591fb
  1. BIN
      dist.zip
  2. 7
      src/router/mobile.js
  3. 34
      src/views/mobile/index.vue
  4. 928
      src/views/mobile/service/create.vue
  5. 812
      src/views/mobile/service/detail.vue

BIN
dist.zip

Binary file not shown.

7
src/router/mobile.js

@ -44,6 +44,12 @@ const mobileRoutes = [
component: () => import('/@/views/mobile/service/create.vue'), component: () => import('/@/views/mobile/service/create.vue'),
meta: { requiresAuth: true, mobileOnly: true } meta: { requiresAuth: true, mobileOnly: true }
}, },
{
path: '/mobile/service/detail',
name: 'MobileServiceDetail',
component: () => import('/@/views/mobile/service/detail.vue'),
meta: { requiresAuth: true, mobileOnly: true }
},
{ {
path: '/mobile', path: '/mobile',
redirect: '/mobile/login' redirect: '/mobile/login'
@ -84,7 +90,6 @@ function mobileRouteGuard(to, from, next) {
next() next()
} }
// 导出移动端路由数组和守卫函数 // 导出移动端路由数组和守卫函数
export const mobileRouters = mobileRoutes export const mobileRouters = mobileRoutes
export { mobileRouteGuard } export { mobileRouteGuard }

34
src/views/mobile/index.vue

@ -102,7 +102,7 @@ async function getServiceList() {
try { try {
const res = await serviceApplicationsApi.queryPage({ const res = await serviceApplicationsApi.queryPage({
pageNum: 1, pageNum: 1,
pageSize: 20 pageSize: 10
}) })
if (res.code === 0) { if (res.code === 0) {
@ -127,26 +127,42 @@ function handleCreate() {
// //
function viewDetail(item) { function viewDetail(item) {
//
console.log('查看申报详情:', item) console.log('查看申报详情:', item)
// router.push(`/mobile/service/detail/${item.id}`)
// 稿status=0
if (item.firmAuditStatus === 0) {
// 稿
router.push(`/mobile/service/create?applicationId=${item.applicationId}`)
} else {
//
router.push(`/mobile/service/detail?applicationId=${item.applicationId}`)
}
} }
// 退 // 退
function handleLogout() { async function handleLogout() {
try {
// 退
await loginApi.logout()
} catch (e) {
console.error('退出登录接口调用失败:', e)
// 使退
} finally {
//
localStorage.removeItem('token') localStorage.removeItem('token')
localStorage.removeItem('userInfo') localStorage.removeItem('userInfo')
router.push('/mobile/login') router.push('/mobile/login')
} }
}
// PC // PC
function getStatusText(status) { function getStatusText(status) {
const statusMap = { const statusMap = {
0: '待审核', 0: '未提交',
1: '审核通过', 1: '审核',
2: '审核拒绝', 2: '审核',
3: '已完成', 3: '已通过',
4: '草稿' 4: '驳回'
} }
return statusMap[status] || '未知状态' return statusMap[status] || '未知状态'
} }

928
src/views/mobile/service/create.vue

File diff suppressed because it is too large

812
src/views/mobile/service/detail.vue

@ -0,0 +1,812 @@
<template>
<div class="mobile-detail-page">
<!-- 头部导航 -->
<div class="page-header">
<div class="header-left">
<button class="back-btn" @click="handleBack">
<span></span>
</button>
</div>
<div class="header-title">
{{ readonlyMode ? '申报详情' : '编辑申报' }}
</div>
<div class="header-right">
<!-- 占位 -->
</div>
</div>
<!-- 表单内容 -->
<div class="form-content">
<!-- 基础信息 -->
<div class="form-section">
<div class="section-title">基础信息</div>
<!-- 姓名 -->
<div class="form-item">
<label class="form-label">姓名</label>
<input
v-model="form.actualName"
class="form-input"
placeholder="员工姓名"
readonly
/>
</div>
<!-- 执业证号 -->
<div class="form-item">
<label class="form-label">执业证号</label>
<input
v-model="form.certificateNumber"
class="form-input"
placeholder="执业证号"
readonly
/>
</div>
<!-- 执业机构 -->
<div class="form-item">
<label class="form-label">执业机构</label>
<input
v-model="form.firmName"
class="form-input"
placeholder="执业机构名称"
readonly
/>
</div>
<!-- 职务 -->
<div class="form-item">
<label class="form-label">职务</label>
<template v-if="readonlyMode">
<input
v-model="form.positionName"
class="form-input"
placeholder="职务"
readonly
/>
</template>
<template v-else>
<select
v-model="form.positionId"
class="form-select"
:disabled="readonlyMode"
>
<option value="">请选择职务</option>
<option
v-for="position in positionList"
:key="position.positionId"
:value="position.positionId"
>
{{ position.positionName }}
</option>
</select>
</template>
</div>
</div>
<!-- 服务信息 -->
<div class="form-section">
<div class="section-title">服务信息</div>
<!-- 服务开始时间 -->
<div class="form-item">
<label class="form-label">服务开始时间</label>
<input
v-model="form.serviceStart"
class="form-input"
placeholder="服务开始时间"
:readonly="readonlyMode"
/>
</div>
<!-- 服务结束时间 -->
<div class="form-item">
<label class="form-label">服务结束时间</label>
<input
v-model="form.serviceEnd"
class="form-input"
placeholder="服务结束时间"
:readonly="readonlyMode"
/>
</div>
<!-- 服务时长 -->
<div class="form-item">
<label class="form-label">服务时长小时</label>
<input
v-model="form.serviceDuration"
class="form-input"
placeholder="服务时长(小时)"
:readonly="readonlyMode"
/>
</div>
</div>
<!-- 活动信息 -->
<div class="form-section">
<div class="section-title">活动信息</div>
<!-- 活动类型 -->
<div class="form-item">
<label class="form-label">活动类型</label>
<template v-if="readonlyMode">
<input
v-model="form.activityCategoryName"
class="form-input"
placeholder="活动类型"
readonly
/>
</template>
<template v-else>
<select
v-model="form.activityCategoryId"
class="form-select"
@change="onActivityCategoryChange"
>
<option value="">请选择活动类型</option>
<option
v-for="category in activityCategoryList"
:key="category.categoryId"
:value="category.categoryId"
>
{{ category.categoryName }}
</option>
</select>
</template>
</div>
<!-- 活动名称 -->
<div class="form-item">
<label class="form-label">活动名称</label>
<template v-if="readonlyMode">
<input
v-model="form.activityName"
class="form-input"
placeholder="活动名称"
readonly
/>
</template>
<template v-else>
<select
v-model="form.activityNameId"
class="form-select"
:disabled="!form.activityCategoryId"
>
<option value="">请选择活动名称</option>
<option
v-for="activity in activityList"
:key="activity.goodsId"
:value="activity.goodsId"
>
{{ activity.goodsName }}
</option>
</select>
</template>
</div>
<!-- 参加人数受益人数 -->
<div class="form-item">
<label class="form-label">参加人数受益人数</label>
<input
v-model="form.beneficiaryCount"
class="form-input"
placeholder="参加人数(受益人数)"
:readonly="readonlyMode"
/>
</div>
<!-- 组织单位名称 -->
<div class="form-item">
<label class="form-label">组织单位名称</label>
<input
v-model="form.organizerName"
class="form-input"
placeholder="组织单位名称"
:readonly="readonlyMode"
/>
</div>
<!-- 服务对象负责人/联系人姓名 -->
<div class="form-item">
<label class="form-label">服务对象负责人/联系人姓名</label>
<input
v-model="form.organizerContact"
class="form-input"
placeholder="服务对象负责人/联系人姓名"
:readonly="readonlyMode"
/>
</div>
<!-- 联系方式 -->
<div class="form-item">
<label class="form-label">联系方式</label>
<input
v-model="form.organizerPhone"
class="form-input"
placeholder="联系方式"
:readonly="readonlyMode"
/>
</div>
<!-- 服务内容 -->
<div class="form-item">
<label class="form-label">服务内容描述</label>
<template v-if="readonlyMode">
<div class="service-content-display">
{{ stripHtmlTags(form.serviceContent) || '无' }}
</div>
</template>
<template v-else>
<textarea
v-model="form.serviceContent"
class="form-textarea"
placeholder="服务内容描述"
rows="3"
></textarea>
</template>
</div>
</div>
<!-- 证明材料 -->
<div class="form-section">
<div class="section-title">证明材料</div>
<!-- 文件列表 -->
<div class="form-item">
<label class="form-label">证明材料</label>
<div class="file-list" v-if="uploadedFiles.length > 0">
<div
v-for="file in uploadedFiles"
:key="file.id"
class="file-item"
>
<span class="file-name">{{ file.name || file.fileName || '未命名文件' }}</span>
</div>
</div>
<div v-else-if="form.attachmentIds && form.attachmentIds.trim()">
<div class="form-tip">证明材料已上传但文件列表为空</div>
</div>
<div v-else>
<div class="form-tip">暂无证明材料</div>
</div>
</div>
</div>
<!-- 审核信息 -->
<div class="form-section">
<div class="section-title">审核信息</div>
<!-- 审核状态 -->
<div class="form-item">
<div class="status-display" :class="getStatusClass(form.firmAuditStatus)">
{{ getStatusText(form.firmAuditStatus) }}
</div>
</div>
<!-- 审核意见 -->
<div class="form-item" v-if="form.auditRemark">
<label class="form-label">审核意见</label>
<textarea
v-model="form.auditRemark"
class="form-textarea"
placeholder="审核意见"
rows="3"
readonly
></textarea>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<button class="btn btn-secondary" @click="handleBack">
返回
</button>
<template v-if="!readonlyMode">
<button class="btn btn-secondary" @click="handleSave" :disabled="loading">
保存草稿
</button>
<button class="btn btn-primary" @click="handleSubmit" :disabled="loading">
提交申报
</button>
</template>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api'
import { message } from 'ant-design-vue'
import { loginApi } from '/@/api/system/login-api'
import { positionApi } from '/@/api/system/position-api'
import { categoryApi } from '/@/api/business/category/category-api'
import { goodsApi } from '/@/api/business/goods/goods-api'
import { fileApi } from '/@/api/support/file-api'
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const'
const router = useRouter()
const route = useRoute()
const loading = ref(false)
const readonlyMode = ref(true) //
//
const form = reactive({
applicationId: undefined, //ID
userId: undefined, //ID
actualName: '', //
certificateNumber: '', //
firmId: undefined, //ID
firmName: '', //
positionId: '', //ID
positionName: '', //
serviceStart: '', //
serviceEnd: '', //
serviceDuration: null, //
activityCategoryId: '', //ID
activityNameId: '', //ID
activityName: '', //
beneficiaryCount: null, //
organizerName: '', //
organizerContact: '', //
organizerPhone: '', //
serviceContent: '', //
proofMaterials: [], //
attachmentIds: undefined, //ID
status: undefined, //
firmAuditStatus: null, //
auditRemark: '' //
})
//
const positionList = ref([])
const activityCategoryList = ref([])
const activityList = ref([])
const uploadedFiles = ref([])
//
async function getDetail() {
const applicationId = route.query.applicationId
if (!applicationId) {
message.error('申报ID不存在')
router.back()
return
}
loading.value = true
try {
const res = await serviceApplicationsApi.queryDetail(applicationId)
if (res.code === 0) {
Object.assign(form, res.data)
//
// status=0稿
// status>=1
readonlyMode.value = form.status !== 0
// 稿
if (!readonlyMode.value) {
await getSelectOptions()
}
// 5. IDPC
if (form.attachmentIds && form.attachmentIds.trim()) {
await getFileListByAttachmentIds(form.attachmentIds)
}
console.log('详情数据加载完成:', form)
console.log('附件ID:', form.attachmentIds)
console.log('文件列表:', uploadedFiles.value)
} else {
message.error(res.msg || '获取详情失败')
}
} catch (error) {
console.error('获取详情失败:', error)
message.error('网络错误,请稍后重试')
} finally {
loading.value = false
}
}
// attachmentIds
async function getFileListByAttachmentIds(attachmentIds) {
try {
if (!attachmentIds || !attachmentIds.trim()) {
uploadedFiles.value = []
console.log('attachmentIds为空,清空文件列表')
return
}
console.log('开始获取文件列表,attachmentIds:', attachmentIds)
// attachmentIds
const result = await fileApi.getFileList(attachmentIds)
console.log('文件列表API返回结果:', result)
if (result && result.code === 0 && result.data && result.data.length > 0) {
//
uploadedFiles.value = result.data.map(file => ({
id: file.id,
name: file.fileName || file.name || '未命名文件',
fileName: file.fileName || file.name || '未命名文件',
fileUrl: file.fileUrl,
fileSize: file.fileSize,
uploadTime: file.uploadTime
}))
console.log('文件列表加载成功,文件数量:', uploadedFiles.value.length)
console.log('文件详情:', uploadedFiles.value)
} else {
uploadedFiles.value = []
console.log('文件列表为空或获取失败,result:', result)
}
} catch (error) {
console.error('获取文件列表失败:', error)
uploadedFiles.value = []
message.error('获取文件列表失败')
}
}
//
async function getSelectOptions() {
try {
//
const positionRes = await positionApi.queryList()
positionList.value = positionRes.data || []
//
const categoryRes = await categoryApi.queryCategoryTree({
categoryType: 1 //
})
activityCategoryList.value = categoryRes.data || []
} catch (error) {
console.error('获取选项数据失败:', error)
}
}
//
async function onActivityCategoryChange() {
if (form.activityCategoryId) {
try {
const activityRes = await goodsApi.queryGoodsList({
categoryId: form.activityCategoryId,
shelvesFlag: true,
pageNum: 1,
pageSize: 100
})
activityList.value = activityRes.data?.list || []
} catch (error) {
console.error('获取活动列表失败:', error)
activityList.value = []
}
} else {
activityList.value = []
}
form.activityNameId = ''
}
//
function handleBack() {
router.back()
}
// 稿
async function handleSave() {
if (readonlyMode.value) return
loading.value = true
try {
const submitData = {
...form,
status: 0 // 稿
}
const res = await serviceApplicationsApi.update(submitData)
if (res.code === 0) {
message.success('保存成功')
router.push('/mobile/service')
} else {
message.error(res.msg || '保存失败')
}
} catch (error) {
console.error('保存失败:', error)
message.error('保存失败,请稍后重试')
} finally {
loading.value = false
}
}
//
async function handleSubmit() {
if (readonlyMode.value) return
//
if (!form.positionId) {
message.error('请选择职务')
return
}
if (!form.serviceStart) {
message.error('请选择服务开始时间')
return
}
if (!form.serviceEnd) {
message.error('请选择服务结束时间')
return
}
if (!form.activityCategoryId) {
message.error('请选择活动类型')
return
}
if (!form.activityNameId) {
message.error('请选择活动名称')
return
}
if (!form.serviceContent) {
message.error('请输入服务内容描述')
return
}
loading.value = true
try {
const submitData = {
...form,
status: 1 //
}
const res = await serviceApplicationsApi.submit(form.applicationId)
if (res.code === 0) {
message.success('申报提交成功')
setTimeout(() => {
router.push('/mobile/service')
}, 1000)
} else {
message.error(res.msg || '提交失败')
}
} catch (error) {
console.error('提交失败:', error)
message.error(error.message || '提交失败,请稍后重试')
} finally {
loading.value = false
}
}
//
function getStatusText(status) {
const statusMap = {
0: '未提交',
1: '待审核',
2: '审核中',
3: '已通过',
4: '驳回'
}
return statusMap[status] || '未知状态'
}
//
function getStatusClass(status) {
const classMap = {
0: 'status-pending',
1: 'status-approved',
2: 'status-rejected',
3: 'status-completed',
4: 'status-draft'
}
return classMap[status] || 'status-unknown'
}
// HTML
function stripHtmlTags(html) {
if (!html) return ''
// HTML
return html.replace(/<[^>]*>/g, '').trim()
}
onMounted(() => {
getDetail()
})
</script>
<style scoped>
.mobile-detail-page {
min-height: 100vh;
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* 头部导航 */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
color: white;
padding: 0 16px;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.back-btn {
background: none;
border: none;
color: white;
font-size: 20px;
padding: 8px;
border-radius: 50%;
transition: background-color 0.3s;
}
.back-btn:active {
background-color: rgba(255, 255, 255, 0.2);
}
.header-title {
font-size: 17px;
font-weight: 600;
flex: 1;
text-align: center;
}
/* 表单内容 */
.form-content {
padding: 16px;
padding-bottom: 80px;
}
.form-section {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 1px solid #e8e8e8;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #1890ff;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #e8f4ff;
position: relative;
}
.section-title::before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 60px;
height: 2px;
background: #1890ff;
border-radius: 1px;
}
.form-item {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 6px;
line-height: 1.4;
}
.form-input, .form-textarea {
width: 100%;
padding: 12px;
border: 1px solid #d9d9d9;
border-radius: 8px;
font-size: 14px;
background: #f5f5f5;
color: #666;
transition: all 0.3s;
box-sizing: border-box;
}
.form-textarea {
resize: vertical;
min-height: 80px;
line-height: 1.5;
}
/* 状态显示 */
.status-display {
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
text-align: center;
}
.status-pending {
background: #fff7e6;
color: #fa8c16;
border: 1px solid #ffd591;
}
.status-approved {
background: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
.status-rejected {
background: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.status-completed {
background: #e6f7ff;
color: #1890ff;
border: 1px solid #91d5ff;
}
.status-draft {
background: #f5f5f5;
color: #666;
border: 1px solid #d9d9d9;
}
/* 操作按钮 */
.action-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 12px 16px;
border-top: 1px solid #e8e8e8;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
}
.btn {
width: 100%;
padding: 12px 16px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
text-align: center;
}
.btn-primary {
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.4);
}
/* 响应式调整 */
@media (max-width: 375px) {
.form-content {
padding: 12px;
padding-bottom: 80px;
}
.form-section {
padding: 12px;
}
}
</style>
Loading…
Cancel
Save