支付宝下单和其他细节优化

This commit is contained in:
2026-05-21 15:10:02 +08:00
parent 39bf8548df
commit 1637ee5bbc
33 changed files with 1511 additions and 232 deletions
+63 -2
View File
@@ -2,6 +2,18 @@
<div class="jobs-page dflex">
<SideNav />
<div class="jobs-page__content">
<div v-if="false" class="fs18 p20 bgeee">
支付
<iframe
v-if="paymentFormHtml"
:srcdoc="paymentFormHtml"
frameborder="0"
style="width: 100%; height: 600px;"
scrolling="auto"
></iframe>
</div>
<!-- 页面标题 + Tab 切换 -->
<JobPageHeader v-model:activeTab="activeTab" :favoriteCount="favoriteTotal" :applyCount="applyTotal" />
<!-- 收藏统计栏 -->
@@ -43,7 +55,7 @@
:categoryIds="selectedCategoryIds"
:maxSelect="3"
:level="3"
:allowParentSelect="false"
:allowParentSelect="true"
@update:categoryIds="onCategoryChange"
/>
<!-- 行业筛选使用行业选择组件 -->
@@ -52,7 +64,7 @@
:industryIds="selectedIndustryIds"
:maxSelect="3"
:level="2"
:allowParentSelect="false"
:allowParentSelect="true"
@update:industryIds="onIndustryChange"
/>
<!-- 其他筛选条件 -->
@@ -246,6 +258,9 @@
<!-- 职位问题反馈弹窗 -->
<JobFeedbackDialog ref="feedbackDialogRef" v-model="showFeedbackDialog" :job-id="feedbackJobId" />
<!-- 欢迎上传简历弹窗 -->
<ProfileWelcomeDialog v-model="showWelcomeDialog" />
</div>
</template>
@@ -261,9 +276,21 @@ import JobFeedbackDialog from '@/components/JobFeedbackDialog.vue'
import IndustrySelector from '@/components/tools/IndustrySelector.vue'
import JobCategorySelector from '@/components/tools/JobCategorySelector.vue'
import RegionSelector from '@/components/tools/RegionSelector.vue'
import ProfileWelcomeDialog from '@/components/ProfileWelcomeDialog.vue'
import { fetchProfile } from '@/api/profile'
import { fetchJobList, fetchFavoriteList, toggleJobFavorite, removeJobFavorite, fetchFavoriteCount, fetchApplyList, fetchApplyCount, removeJobFromList } from '@/api/jobs'
import type { JobListItem, JobListParams, FavoriteListParams, ApplyListParams, ApplyCountData } from '@/api/jobs'
// 2. 注意:这里的字符串不需要再手动转义双引号了
const paymentFormHtml = ref(`
<form name="punchout_form" method="post" action="https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=UTF-8&method=alipay.trade.page.pay&sign=o8MQ7cEAh4yrf6ri5iT9gXbtR%2BTncX36p9JNZf8nmXxEp1WKO6dF1HDC6DPF8nxJbus6OG9E5UPSoiHgqrEiu3aeOs4LMcYzXXZP0UQzG%2F6pXyZrvlL70G8uwpnV6MgQojzhKheG0vYIJgSPaGsHKjwcp%2BT8yOZg3eEDIgFYEBf2a3c7kANc7%2FuOOGCSD9Ip9zqwX9LQ3b433NPH3AbGQkFSLsdGOTY62I7yI8hEF7EM7nNm%2BYryieqju21yN76JrV4k%2BgIGHn83I5oT3y7zurzdTqHLD4UawpPWdJ5B7p6wnJ4cN4uzO8JgRpNqogYqqKBPcHIlB9o4uax3bcRqhg%3D%3D&notify_url=http%3A%2F%2F39.108.134.218%3A10106%2Fapi%2F%2Fpublic%2FalipayNotify%2Fpay&version=1.0&app_id=2018062160374941&sign_type=RSA2&timestamp=2026-05-20+15%3A29%3A21&alipay_sdk=alipay-sdk-java-4.40.791.ALL&format=json">
<input type="hidden" name="biz_content" value='{"out_trade_no":"OP2057000709638877186","product_code":"FAST_INSTANT_TRADE_PAY","qr_pay_mode":"4","qrcode_width":200,"subject":"周会员","total_amount":"0.10"}'>
<input type="submit" value="立即支付" style="display:none">
</form>
<script>document.forms[0].submit();<\/script>
`)
// ==================== 路由相关 ====================
const router = useRouter()
@@ -273,6 +300,9 @@ const store = useStore()
/** 登录状态 — 从 store 读取 */
const isAuthenticated = computed(() => store.state.isAuthenticated)
/** 用户昵称 — 从全局 store 读取 */
const nickName = computed(() => store.state.userInfo?.nick || '')
// ==================== 页面状态 ====================
/** 当前激活的 Tab,从 URL query 参数读取,默认"推荐" */
@@ -302,6 +332,9 @@ const feedbackJobId = ref<string | null>(null)
/** 当前问助手的岗位 ID(传给 AiChat 组件) */
const currentAskJobId = ref<string>('')
/** 欢迎弹窗的显示状态 */
const showWelcomeDialog = ref(false)
/** 点击"问助手"按钮,传入岗位 ID 给 AiChat */
function askAssistant(job: JobItem) {
currentAskJobId.value = job.id
@@ -516,6 +549,19 @@ onMounted(async () => {
// 仅登录状态下才加载需要鉴权的数据
if (isAuthenticated.value) {
// 加载用户个人信息到全局 store
store.dispatch('loadUserInfo')
// 检查个人资料是否存在,不存在则弹出欢迎弹窗
try {
const profileRes = await fetchProfile()
if (profileRes.code === '0' && (!profileRes.data || profileRes.data === null)) {
showWelcomeDialog.value = true
}
} catch {
// 接口异常不阻塞页面加载
}
// 加载收藏统计(用于 Tab 标签显示)
loadFavoriteCount()
@@ -861,6 +907,21 @@ watch(
{ deep: true },
)
// 监听登录状态变化 — 登录成功后检查个人资料是否存在
watch(isAuthenticated, async (newVal, oldVal) => {
if (newVal && !oldVal) {
// 从未登录变为已登录,检查个人资料
try {
const profileRes = await fetchProfile()
if (profileRes.code === '0' && (!profileRes.data || profileRes.data === null)) {
showWelcomeDialog.value = true
}
} catch {
// 接口异常不阻塞
}
}
})
// 监听 Tab 切换,重置列表状态并加载对应数据
watch(activeTab, (newTab, oldTab) => {
if (initializing) return
+45 -24
View File
@@ -9,6 +9,9 @@
:initial-data="editInitialData"
@save="handleSaveEdit"
/>
<!-- 欢迎上传简历弹窗 -->
<ProfileWelcomeDialog v-model="showWelcomeDialog" />
<!-- 页面标题 -->
<div class="profile-page__header">
@@ -51,12 +54,13 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import SideNav from '@/components/SideNav.vue'
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
import ProfilePageContent from '@/components/ProfilePageContent.vue'
import ProfileWelcomeDialog from '@/components/ProfileWelcomeDialog.vue'
import { saveProfile, fetchProfile, fetchEducation, saveEducation, fetchWork, saveWork, fetchInternship, saveInternship, fetchProject, saveProject, fetchCompetition, saveCompetition } from '@/api/profile'
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
@@ -68,8 +72,26 @@ onMounted(async () => {
if (!store.state.regions.length) {
store.dispatch('loadCommonData')
}
// 请求个人资料主表数据,填充非数组字段
await loadProfile()
// 请求个人资料主表数据,判断是否需要弹出欢迎弹窗
const profileRes = await fetchProfile()
if (profileRes.code === '0' && (!profileRes.data || profileRes.data === null)) {
// 无个人资料数据,弹出欢迎上传弹窗
showWelcomeDialog.value = true
return
}
// 有数据,正常填充
if (profileRes.code === '0' && profileRes.data) {
const d = profileRes.data
profile.value.name = d.name || ''
profile.value.phone = d.mobileNumber || ''
profile.value.email = d.email || ''
profile.value.idNumber = d.idCard || ''
profile.value.regionCode = d.regionCode || ''
profile.value.wechat = d.wechatNumber || ''
profile.value.skills = d.skills || []
profile.value.certificates = d.certificates || []
profile.value.portfolioUrl = d.portfolioUrl || ''
}
// 请求教育经历数据
await loadEducation()
// 请求工作经历数据
@@ -82,27 +104,6 @@ onMounted(async () => {
await loadCompetition()
})
/** 加载个人资料主表数据 */
async function loadProfile() {
try {
const res = await fetchProfile()
if (res.code === '0' && res.data) {
const d = res.data
profile.value.name = d.name || ''
profile.value.phone = d.mobileNumber || ''
profile.value.email = d.email || ''
profile.value.idNumber = d.idCard || ''
profile.value.regionCode = d.regionCode || ''
profile.value.wechat = d.wechatNumber || ''
profile.value.skills = d.skills || []
profile.value.certificates = d.certificates || []
profile.value.portfolioUrl = d.portfolioUrl || ''
}
} catch {
console.error('[Profile] 加载个人资料失败')
}
}
/** 加载教育经历数据 */
async function loadEducation() {
try {
@@ -213,6 +214,26 @@ async function loadCompetition() {
/** 编辑抽屉的显示状态 */
const showEditDrawer = ref(false)
/** 欢迎弹窗的显示状态 */
const showWelcomeDialog = ref(false)
/** 登录状态 */
const isAuthenticated = computed(() => store.state.isAuthenticated)
/** 监听登录状态变化 — 登录成功后检查个人资料是否存在 */
watch(isAuthenticated, async (newVal, oldVal) => {
if (newVal && !oldVal) {
try {
const profileRes = await fetchProfile()
if (profileRes.code === '0' && (!profileRes.data || profileRes.data === null)) {
showWelcomeDialog.value = true
}
} catch {
// 接口异常不阻塞
}
}
})
/** 当前编辑的模块名称 */
const editModule = ref('info')
+12 -77
View File
@@ -15,7 +15,11 @@
<!-- 简历表格 -->
<div class="resume-page__table-wrap">
<table class="resume-page__table">
<!-- 空状态提示 -->
<div v-if="resumeList.length === 0" class="resume-page__empty">
<p>暂无简历请点击上方按钮上传简历</p>
</div>
<table v-else class="resume-page__table">
<thead>
<tr>
<th class="resume-page__th">简历</th>
@@ -82,40 +86,21 @@
@saved="loadResumeList"
/>
<!-- 导出简历格式选择弹窗 -->
<el-dialog
<!-- 导出简历弹窗组件 -->
<ResumeExportDialog
v-model="exportDialogVisible"
title="导出简历"
width="3.6rem"
:close-on-click-modal="false"
class="resume-export-dialog"
>
<!-- 格式选择 -->
<el-radio-group v-model="exportFormat" class="resume-export-dialog__radio-group">
<el-radio value="pdf">PDF 简历</el-radio>
<el-radio value="word">Word 简历</el-radio>
</el-radio-group>
<template #footer>
<el-button @click="exportDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="exporting" @click="doExport">下载</el-button>
</template>
</el-dialog>
<!-- 隐藏的简历模板用于导出时渲染DOM -->
<div v-if="exportTemplateData" style="position:absolute;left:-9999px;top:0;">
<JobResumeTemplate ref="exportTemplateRef" :resume-data="exportTemplateData" />
</div>
:resume-id="exportResumeId"
:resume-name="exportResumeName"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import SideNav from '@/components/SideNav.vue'
import ResumeEditNameDialog from '@/components/ResumeEditNameDialog.vue'
import JobResumeTemplate from '@/components/JobResumeTemplate.vue'
import type { ResumeTemplateData } from '@/components/JobResumeTemplate.vue'
import { exportResumePdf, exportResumeWord, loadResumeTemplateData } from '@/utils/resumeExport'
import ResumeExportDialog from '@/components/ResumeExportDialog.vue'
import { uploadResume } from '@/utils/aiRequest'
import {
fetchResumeList, deleteResume, type ResumeListItem,
@@ -228,60 +213,10 @@ const editTargetPosition = ref('')
/** 导出弹窗是否可见 */
const exportDialogVisible = ref(false)
/** 导出格式:pdf 或 word */
const exportFormat = ref<'pdf' | 'word'>('pdf')
/** 导出中状态 */
const exporting = ref(false)
/** 当前导出的简历 ID */
const exportResumeId = ref('')
/** 当前导出的简历名称(用于文件名) */
const exportResumeName = ref('')
/** 导出用的简历模板数据 */
const exportTemplateData = ref<ResumeTemplateData | null>(null)
/** 导出用的简历模板组件引用 */
const exportTemplateRef = ref<InstanceType<typeof JobResumeTemplate> | null>(null)
/** 执行导出下载 */
async function doExport() {
exporting.value = true
try {
// 1. 加载简历完整数据
const data = await loadResumeTemplateData(exportResumeId.value)
if (!data) {
ElMessage.error('获取简历数据失败')
return
}
// 2. 设置模板数据,等待DOM渲染
exportTemplateData.value = data
await nextTick()
// 3. 获取渲染后的DOM
const element = exportTemplateRef.value?.resumeRef
if (!element) {
ElMessage.error('简历模板渲染失败')
return
}
const fileName = exportResumeName.value || '简历'
if (exportFormat.value === 'pdf') {
await exportResumePdf(element, fileName)
} else {
exportResumeWord(element, fileName)
}
ElMessage.success('导出成功')
exportDialogVisible.value = false
} catch (err) {
console.error('[导出简历] 失败', err)
ElMessage.error('导出失败,请稍后重试')
} finally {
exporting.value = false
// 清理隐藏模板数据
exportTemplateData.value = null
}
}
/** 弹出菜单操作项 */
const popupActions = ['设为默认简历', '编辑名称岗位', '导出简历', '删除']
+33 -7
View File
@@ -29,6 +29,22 @@
@submit="handleFixSubmit"
/>
<!-- 导出简历弹窗组件 -->
<ResumeExportDialog
v-model="exportDialogVisible"
:resume-id="resumeId"
:resume-name="resumeMain.resumeName || '简历'"
/>
<!-- 编辑简历名称弹窗 -->
<ResumeEditNameDialog
v-model="editNameVisible"
:resume-id="resumeId"
:resume-name="resumeMain.resumeName || ''"
:target-position="resumeMain.targetPosition || ''"
@saved="loadResumeDetail"
/>
<!-- 顶部标题 -->
<h2 class="resume-detail__page-title">我的简历</h2>
@@ -43,7 +59,6 @@
<span class="resume-detail__tab-name">{{ resumeMain.resumeName || '未命名简历' }}</span>
</div>
<div class="resume-detail__toolbar-right">
<button class="resume-detail__tool-btn" @click="handleFeedback">问题反馈</button>
<button class="resume-detail__tool-btn" @click="handleEdit">
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__tool-icon">
<path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
@@ -407,6 +422,8 @@ import SideNav from '@/components/SideNav.vue'
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
import ResumeAnalysisReportDrawer from '@/components/ResumeAnalysisReportDrawer.vue'
import ResumeIssueFixDrawer from '@/components/ResumeIssueFixDrawer.vue'
import ResumeExportDialog from '@/components/ResumeExportDialog.vue'
import ResumeEditNameDialog from '@/components/ResumeEditNameDialog.vue'
import {
fetchResumeMain,
fetchResumeEducation,
@@ -675,14 +692,23 @@ function goBack() {
router.push('/resume')
}
/** 问题反馈 */
function handleFeedback() { console.log('问题反馈') }
/** 编辑简历信息 */
function handleEdit() { console.log('编辑简历信息') }
/** 导出简历 */
function handleExport() { console.log('导出') }
/** 编辑简历名称弹窗显示状态 */
const editNameVisible = ref(false)
/** 编辑简历信息 — 打开编辑名称弹窗 */
function handleEdit() {
editNameVisible.value = true
}
/** 导出简历弹窗显示状态 */
const exportDialogVisible = ref(false)
/** 导出简历 — 打开导出弹窗 */
function handleExport() {
exportDialogVisible.value = true
}
/** 删除简历 — 二次确认后调用删除接口,成功后返回简历列表 */
async function handleDelete() {