feat: 本地 Java 发票 OCR + 策划方案多角色开放 + H5 相机脱敏修复
- OCR: 进程内 PaddleOCR ONNX Runtime 识别替代远程 Python 微服务 (新增 ocr/core/service/config 引擎, 删 OcrClient/OcrConfig, PDF 文本层优先 + QR 快路径) - 投稿: 项目设计投稿 → 项目策划方案, doctor/executor/sponsor 三角色开放 (BizProjectPlan 按非 admin/manager 隔离, 复用 Submissions.vue, 设计文件改 OssFileUploader) - H5: <video> 去掉 autoplay + safePlay 重试, 穿透 uni-app wrapper 找原生 video - 脱敏: 签到表高斯模糊由横向条带改为全高竖条带 (20%~50% 宽度) - 域名: ringdoctor.com → risingdoctor.com
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-loading="loading" class="page-card doctor-submission-detail">
|
||||
<!-- 面包屑 (标准: 首页 / 分类 / 当前页) -->
|
||||
<div class="breadcrumb">首页 / 我的项目设计投稿 / 投稿详情</div>
|
||||
<div class="breadcrumb">首页 / 我的项目策划方案 / 策划方案详情</div>
|
||||
|
||||
<!-- 表单 (只读, 用 el-input readonly 展示) -->
|
||||
<el-form label-width="120px" class="readonly-form">
|
||||
@@ -68,6 +68,8 @@ import request from '@/utils/request'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
// 投稿模块路径前缀: 按当前角色 (doctor/executor/sponsor) 拼, 避免串到别的角色侧栏
|
||||
const base = computed(() => `/${route.meta.role || 'doctor'}`)
|
||||
|
||||
const loading = ref(false)
|
||||
const detail = ref({})
|
||||
@@ -87,7 +89,7 @@ const display = computed(() => ({
|
||||
}))
|
||||
|
||||
function goBack() {
|
||||
router.push({ path: '/doctor/submissions', query: { _t: Date.now() } })
|
||||
router.push({ path: `${base.value}/submissions`, query: { _t: Date.now() } })
|
||||
}
|
||||
|
||||
async function load() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-loading="loadingDetail" class="page-card doctor-submission-new">
|
||||
<div class="breadcrumb">
|
||||
<span>首页 / 我的项目设计投稿 / {{ isEdit ? '修改项目设计投稿' : '新建项目设计投稿' }}</span>
|
||||
<span>首页 / 我的项目策划方案 / {{ isEdit ? '修改项目策划方案' : '新建项目策划方案' }}</span>
|
||||
</div>
|
||||
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="submission-form">
|
||||
@@ -41,20 +41,14 @@
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="设计文件" prop="designFileUrl">
|
||||
<el-upload
|
||||
drag
|
||||
action="#"
|
||||
accept=".pdf,.png,.jpg,.jpeg"
|
||||
:show-file-list="true"
|
||||
:file-list="designFileList"
|
||||
:before-upload="beforeDesignUpload"
|
||||
:http-request="uploadDesign"
|
||||
:on-remove="onDesignRemove"
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将设计文件拖到此处,或<em>点击上传</em></div>
|
||||
<div class="el-upload__tip" slot="tip">支持 PDF / 图片, 单文件 ≤ 20MB</div>
|
||||
</el-upload>
|
||||
<OssFileUploader
|
||||
v-model="form.designFileUrl"
|
||||
dir="ry8080/submission/design/"
|
||||
placeholder="点击上传设计文件"
|
||||
hint="支持 PDF / 图片, 单文件 ≤ 20MB"
|
||||
:max-size="20"
|
||||
block
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -76,24 +70,24 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { reactive, ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import request from '@/utils/request'
|
||||
import { bizAdd, bizUpdate } from '@/api/public'
|
||||
import { uploadToOss } from '@/utils/oss'
|
||||
import DictSelect from '@/components/DictSelect.vue'
|
||||
import OssFileUploader from '@/components/OssFileUploader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
// 投稿模块路径前缀: 按当前角色 (doctor/executor/sponsor) 拼, 避免串到别的角色侧栏
|
||||
const base = computed(() => `/${route.meta.role || 'doctor'}`)
|
||||
|
||||
const formRef = ref(null)
|
||||
const saving = ref(false)
|
||||
const loadingDetail = ref(false)
|
||||
const designFileList = ref([])
|
||||
const designUploading = ref(false)
|
||||
const isEdit = !!route.params.planId
|
||||
|
||||
// 项目方向 options (复用 manager 侧接口)
|
||||
@@ -124,36 +118,6 @@ const rules = {
|
||||
designFileUrl: [{ required: true, message: '请上传设计文件', trigger: 'change' }]
|
||||
}
|
||||
|
||||
function beforeDesignUpload(file) {
|
||||
const max = 20 * 1024 * 1024
|
||||
if (file.size > max) { ElMessage.error('设计文件不能超过 20MB'); return false }
|
||||
const accept = ['.pdf', '.png', '.jpg', '.jpeg']
|
||||
const ext = '.' + (file.name.split('.').pop() || '').toLowerCase()
|
||||
if (!accept.includes(ext)) { ElMessage.error('仅支持 PDF / 图片格式'); return false }
|
||||
return true
|
||||
}
|
||||
|
||||
async function uploadDesign(opts) {
|
||||
const file = opts.file
|
||||
designUploading.value = true
|
||||
try {
|
||||
const url = await uploadToOss(file, 'ry8080/submission/design/')
|
||||
form.designFileUrl = url
|
||||
designFileList.value = [{ name: file.name, url }]
|
||||
ElMessage.success('设计文件上传成功')
|
||||
} catch (e) {
|
||||
ElMessage.error('上传失败: ' + (e?.message || e))
|
||||
designFileList.value = []
|
||||
} finally {
|
||||
designUploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onDesignRemove() {
|
||||
form.designFileUrl = ''
|
||||
designFileList.value = []
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
const planId = route.params.planId
|
||||
if (!planId) return
|
||||
@@ -170,10 +134,6 @@ async function loadDetail() {
|
||||
form.designFileUrl = d.designFileUrl || ''
|
||||
form.status = d.status && String(d.status).trim() ? String(d.status) : '0'
|
||||
form.remark = d.remark || ''
|
||||
if (d.designFileUrl) {
|
||||
const name = d.designFileUrl.split('/').pop() || `${d.planName || '设计文件'}.pdf`
|
||||
designFileList.value = [{ name, url: d.designFileUrl }]
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '加载失败')
|
||||
goBack()
|
||||
@@ -188,7 +148,7 @@ async function loadDetail() {
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.push({ path: '/doctor/submissions', query: { _t: Date.now() } })
|
||||
router.push({ path: `${base.value}/submissions`, query: { _t: Date.now() } })
|
||||
}
|
||||
|
||||
function confirmCancel() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="page-card">
|
||||
<div class="breadcrumb">首页 / 我的项目设计投稿</div>
|
||||
<div class="breadcrumb">首页 / 我的项目策划方案</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="策划方案名称"><el-input v-model="q.planName" placeholder="输入策划方案名称" clearable style="width: 200px" /></el-form-item>
|
||||
@@ -81,15 +81,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { reactive, ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import request from '@/utils/request'
|
||||
import { bizList, bizUpdate } from '@/api/public'
|
||||
import DictSelect from '@/components/DictSelect.vue'
|
||||
import AuditStatusTag from '@/components/AuditStatusTag.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
// 投稿模块路径前缀: 按当前角色 (doctor/executor/sponsor) 拼, 避免串到别的角色侧栏
|
||||
const base = computed(() => `/${route.meta.role || 'doctor'}`)
|
||||
|
||||
const q = reactive({
|
||||
planName: '',
|
||||
@@ -146,13 +149,13 @@ function canSubmit(row) {
|
||||
function onSelectionChange(arr) { selected.value = arr }
|
||||
|
||||
function onCreate() {
|
||||
router.push({ path: '/doctor/submission/new' })
|
||||
router.push({ path: `${base.value}/submission/new` })
|
||||
}
|
||||
function onView(row) {
|
||||
router.push({ path: `/doctor/submission/detail/${row.planId}` })
|
||||
router.push({ path: `${base.value}/submission/detail/${row.planId}` })
|
||||
}
|
||||
function onEdit(row) {
|
||||
router.push({ path: `/doctor/submission/edit/${row.planId}` })
|
||||
router.push({ path: `${base.value}/submission/edit/${row.planId}` })
|
||||
}
|
||||
|
||||
async function onSubmit(row) {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero-cta">
|
||||
<span class="cta-btn" @click="onSubmit">项目提案</span>
|
||||
<span v-if="canPropose" class="cta-btn" @click="onSubmit">项目提案</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -169,6 +169,11 @@ function openPlan(id) {
|
||||
const topNavClass = computed(() => isScrolled.value ? 'is-scrolled' : '')
|
||||
const loggedIn = computed(() => !!userStore.token)
|
||||
const userName = computed(() => userStore.user?.userName || '用户')
|
||||
// 投稿角色: admin/manager 不开放投稿 → 隐藏「项目提案」按钮; 未登录 user 为 null → 显示(点击引导登录)
|
||||
const canPropose = computed(() => {
|
||||
const r = userStore.user?.role || ''
|
||||
return r !== 'admin' && r !== 'manager'
|
||||
})
|
||||
|
||||
const planSvg = `<svg viewBox="0 150 1200 370" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="2025-2030年项目规划">
|
||||
<defs>
|
||||
@@ -257,7 +262,9 @@ function onSubmit() {
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
router.push('/publicity')
|
||||
const role = userStore.user?.role
|
||||
const map = { doctor: '/doctor/submissions', executor: '/executor/submissions', sponsor: '/sponsor/submissions' }
|
||||
router.push(map[role] || '/doctor/submissions')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user