diff --git a/components.d.ts b/components.d.ts index b087363..70ade84 100644 --- a/components.d.ts +++ b/components.d.ts @@ -53,6 +53,7 @@ declare module 'vue' { ResumeIssueFixDrawer: typeof import('./src/components/ResumeIssueFixDrawer.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SettingsDeleteAccountDialog: typeof import('./src/components/SettingsDeleteAccountDialog.vue')['default'] SettingsDialog: typeof import('./src/components/SettingsDialog.vue')['default'] SideNav: typeof import('./src/components/SideNav.vue')['default'] } diff --git a/src/api/setting.ts b/src/api/setting.ts new file mode 100644 index 0000000..e7a608b --- /dev/null +++ b/src/api/setting.ts @@ -0,0 +1,18 @@ +import request from '@/utils/request' +import type { ApiResult } from '@/api/auth' + +/** 提交反馈参数 */ +export interface UserFeedbackParams { + /** 反馈类型 */ + type: number + /** 反馈内容 */ + content: string +} + +/** + * 提交反馈 + * POST /UserFeedbackParams + */ +export function userFeedback(data: UserFeedbackParams) { + return request.post('/user/feedback', data) +} \ No newline at end of file diff --git a/src/assets/styles/components/member-dialog.scss b/src/assets/styles/components/member-dialog.scss index b004b4d..b34681d 100644 --- a/src/assets/styles/components/member-dialog.scss +++ b/src/assets/styles/components/member-dialog.scss @@ -231,7 +231,7 @@ &__plan-btn { width: 100%; padding: 0.1rem 0; - border-radius: 0.2rem; + border-radius: 0.12rem; font-size: 0.13rem; font-weight: 600; cursor: pointer; diff --git a/src/assets/styles/components/settings-delete-account-dialog.scss b/src/assets/styles/components/settings-delete-account-dialog.scss new file mode 100644 index 0000000..a68f1a5 --- /dev/null +++ b/src/assets/styles/components/settings-delete-account-dialog.scss @@ -0,0 +1,448 @@ +@use '../variables' as *; + +// ==================== 注销账号弹窗 ==================== +.delete-account-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: $overlay-bg; + z-index: 2200; + display: flex; + align-items: center; + justify-content: center; +} + +.delete-account-dialog { + position: relative; + width: 9.0rem; + min-height: 5.6rem; + background: $bg-white; + border-radius: 0.12rem; + padding: 0.32rem 0.4rem; + box-shadow: 0 0.04rem 0.2rem rgba(0, 0, 0, 0.15); + overflow-y: auto; + max-height: 90vh; + + // 关闭按钮 + &__close { + position: absolute; + top: 0.16rem; + right: 0.2rem; + font-size: 0.18rem; + color: $text-light; + cursor: pointer; + z-index: 1; + transition: color 0.2s; + + &:hover { + color: $text-dark; + } + } + + // ===== 步骤条 ===== + &__steps { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0.28rem; + } + + &__step { + display: flex; + align-items: center; + gap: 0.06rem; + } + + &__step-num { + width: 0.22rem; + height: 0.22rem; + border-radius: 50%; + background: $bg-main; + color: $text-light; + font-size: 0.12rem; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + } + + &__step-label { + font-size: 0.13rem; + color: $text-light; + } + + &__step--active &__step-num { + background: $accent; + color: $bg-white; + } + + &__step--active &__step-label { + color: $accent; + font-weight: 600; + } + + &__step--done &__step-num { + background: $accent; + color: $bg-white; + } + + &__step--done &__step-label { + color: $text-middle; + } + + &__step-line { + width: 0.6rem; + height: 1px; + background: $border-color; + margin: 0 0.12rem; + } + + // ===== 标题 ===== + &__title { + font-size: 0.22rem; + font-weight: 700; + color: $text-dark; + margin: 0 0 0.08rem 0; + } + + &__subtitle { + font-size: 0.13rem; + color: $text-middle; + margin: 0 0 0.24rem 0; + line-height: 1.6; + } + + // ===== 主体内容 ===== + &__body { + display: flex; + gap: 0.24rem; + } + + // ===== 左侧警告列表 ===== + &__warnings { + flex: 1; + min-width: 0; + } + + &__warning-item { + display: flex; + gap: 0.12rem; + padding: 0.16rem; + border: 1px solid $border-color; + border-radius: 0.08rem; + margin-bottom: 0.12rem; + + &--danger { + border-color: rgba($danger, 0.3); + background: rgba($danger, 0.02); + } + } + + &__warning-icon { + flex-shrink: 0; + width: 0.2rem; + height: 0.2rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.12rem; + font-weight: 700; + margin-top: 0.02rem; + + &--danger { + background: $danger; + color: $bg-white; + } + + &--orange { + color: #F5A623; + background: none; + font-size: 0.1rem; + } + } + + &__warning-content { + flex: 1; + min-width: 0; + } + + &__warning-title { + font-size: 0.14rem; + font-weight: 700; + color: $text-dark; + margin-bottom: 0.04rem; + + &--danger { + color: $danger; + } + } + + &__warning-desc { + font-size: 0.12rem; + color: $text-middle; + line-height: 1.6; + margin: 0; + } + + // ===== 确认勾选 ===== + &__confirm-check { + margin-top: 0.16rem; + margin-bottom: 0.2rem; + } + + &__checkbox-label { + display: flex; + align-items: flex-start; + gap: 0.08rem; + font-size: 0.12rem; + color: $text-dark; + cursor: pointer; + line-height: 1.6; + } + + &__checkbox { + margin-top: 0.03rem; + flex-shrink: 0; + } + + // ===== 底部按钮 ===== + &__actions { + display: flex; + gap: 0.16rem; + + } + + &__btn { + height: 0.44rem; + line-height: 0.44rem; + border-radius: 0.14rem; + font-size: 0.13rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: none; + flex: 1; + + &:disabled { + opacity: 0.5; + + cursor: not-allowed; + } + + &--outline { + background: #DBE4F0; + border: 1px solid $border-color; + color: $text-dark; + + &:hover:not(:disabled) { + border-color: $text-middle; + } + } + + &--danger { + background: #DC2626; + color: $bg-white; + + &:hover:not(:disabled) { + background: darken(#DC2626, 8%); + } + } + + &--primary { + background: $accent; + color: $bg-white; + + &:hover { + background: $accent-hover; + } + } + } + + // ===== 右侧账户状态卡片 ===== + &__account-status { + width: 2.6rem; + flex-shrink: 0; + background: $bg-white; + border: 1px solid $border-color; + border-radius: 0.1rem; + padding: 0.2rem; + align-self: flex-start; + } + + &__status-title { + font-size: 0.16rem; + font-weight: 700; + color: $text-dark; + margin: 0 0 0.16rem 0; + } + + &__status-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.1rem 0; + border-bottom: 1px solid $border-color; + + &:last-of-type { + border-bottom: none; + } + } + + &__status-label { + font-size: 0.12rem; + color: $text-middle; + } + + &__status-value { + font-size: 0.13rem; + color: $text-dark; + font-weight: 600; + + &--danger { + color: $danger; + } + } + + &__status-notice { + margin-top: 0.12rem; + padding: 0.1rem 0.12rem; + background: rgba($danger, 0.05); + border: 1px solid rgba($danger, 0.2); + border-radius: 0.06rem; + font-size: 0.11rem; + color: $danger; + line-height: 1.6; + } + + // ===== 步骤2:安全验证 ===== + &__verify { + display: flex; + align-items: center; + justify-content: center; + padding: 0.2rem 0; + } + + &__verify-card { + background: $bg-white; + border: 1px solid $border-color; + border-radius: 0.12rem; + padding: 0.32rem 0.4rem; + width: 4.6rem; + text-align: center; + } + + &__verify-card-title { + font-size: 0.18rem; + font-weight: 700; + color: $text-dark; + margin: 0 0 0.08rem 0; + text-align: left; + } + + &__verify-card-desc { + font-size: 0.13rem; + color: $accent; + margin: 0 0 0.24rem 0; + text-align: left; + } + + &__verify-input-row { + display: flex; + gap: 0.12rem; + margin-bottom: 0.24rem; + } + + &__verify-input { + flex: 1; + height: 0.4rem; + border: 1px solid $border-color; + border-radius: 0.2rem; + padding: 0 0.16rem; + font-size: 0.14rem; + outline: none; + transition: border-color 0.2s; + + &:focus { + border-color: $accent; + } + } + + &__verify-send { + white-space: nowrap; + padding: 0 0.2rem; + height: 0.4rem; + border: 1px solid $border-color; + border-radius: 0.2rem; + background: $bg-white; + color: $text-dark; + font-size: 0.13rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + + &:hover:not(:disabled) { + border-color: $accent; + color: $accent; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + &__verify-next-btn { + width: 100%; + height: 0.44rem; + border: none; + border-radius: 0.22rem; + background: $accent; + color: $bg-white; + font-size: 0.15rem; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; + margin-bottom: 0.12rem; + + &:hover:not(:disabled) { + background: $accent-hover; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + &__verify-tip { + font-size: 0.12rem; + color: $text-light; + margin: 0; + } + + // ===== 步骤3:完成 ===== + &__done { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 0.6rem 0; + text-align: center; + } + + &__done-icon { + width: 0.6rem; + height: 0.6rem; + border-radius: 50%; + background: $accent; + color: $bg-white; + font-size: 0.28rem; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0.2rem; + } +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 5cc650f..5b5fec4 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -32,6 +32,7 @@ @use './components/agent-match-job-add.scss'; @use './components/agent-apply-progress.scss'; @use './components/ai-thinking-indicator.scss'; +@use './components/settings-delete-account-dialog.scss'; // 全局样式(优先级最高) @use './auto.scss'; diff --git a/src/components/SettingsDeleteAccountDialog.vue b/src/components/SettingsDeleteAccountDialog.vue new file mode 100644 index 0000000..358899f --- /dev/null +++ b/src/components/SettingsDeleteAccountDialog.vue @@ -0,0 +1,211 @@ + + + diff --git a/src/components/SettingsDialog.vue b/src/components/SettingsDialog.vue index b1bda0e..1b99bd4 100644 --- a/src/components/SettingsDialog.vue +++ b/src/components/SettingsDialog.vue @@ -227,6 +227,9 @@ + + + @@ -239,6 +242,7 @@ import JobGoalDialog from './JobGoalDialog.vue' import { resolveRegionName } from '@/utils/region' import { resolveIndustryName } from '@/utils/industry' import { resolveJobCategoryName } from '@/utils/jobCategory' +import SettingsDeleteAccountDialog from './SettingsDeleteAccountDialog.vue' /** 组件 Props — 控制弹窗显示/隐藏 */ const props = defineProps<{ modelValue: boolean }>() @@ -277,6 +281,9 @@ const reminders = reactive({ /** 求职目标弹窗显示状态 */ const showGoalDialog = ref(false) +/** 注销账号弹窗显示状态 */ +const showDeleteAccount = ref(false) + /** 岗位名称列表 */ const intentionCategoryNames = computed(() => { const ids = store.state.jobIntention.categoryIds || [] @@ -313,15 +320,9 @@ watch(() => props.modelValue, (val) => { } }) -/** 注销账号 — 弹出二次确认 */ +/** 注销账号 — 打开注销账号弹窗 */ const handleDeleteAccount = () => { - ElMessageBox.confirm('此操作将永久删除你的账号及所有数据,是否继续?', '注销账号', { - confirmButtonText: '确认注销', - cancelButtonText: '取消', - type: 'warning', - }).then(() => { - ElMessage.success('账号已注销') - }).catch(() => {}) + showDeleteAccount.value = true } /** 管理订阅 */ diff --git a/src/components/SideNav.vue b/src/components/SideNav.vue index 25f3c89..a17b8e9 100644 --- a/src/components/SideNav.vue +++ b/src/components/SideNav.vue @@ -83,11 +83,11 @@ @@ -363,6 +363,9 @@ const applyStatusTabs = computed(() => [ function switchApplyStatus(status: number) { applyStatusFilter.value = applyStatusFilter.value === status ? null : status pageNum.value = 1 + hasNoMoreData.value = false + jobList.value = [] + total.value = 0 loadApplyList() } @@ -579,8 +582,11 @@ const loading = ref(false) /** 是否正在加载下一页 */ const loadingMore = ref(false) +/** 接口返回空列表时标记为无更多数据 */ +const hasNoMoreData = ref(false) + /** 是否已加载全部数据 */ -const noMore = computed(() => jobList.value.length >= total.value && total.value > 0) +const noMore = computed(() => hasNoMoreData.value || (jobList.value.length >= total.value && total.value > 0)) /** 职位列表数据 */ const jobList = ref([]) @@ -661,8 +667,14 @@ async function loadNextPage() { applied: false, showMenu: false, })) - jobList.value.push(...newItems) - total.value = Number(res.data.total) + // 返回空列表时标记无更多数据,停止滚动加载 + if (newItems.length === 0) { + hasNoMoreData.value = true + pageNum.value-- + } else { + jobList.value.push(...newItems) + total.value = Number(res.data.total) + } } } catch (e) { // 加载失败时回退页码 @@ -693,6 +705,9 @@ function onListScroll() { /** 筛选条件变化时重置到第一页并重新加载 */ function reloadFirstPage() { pageNum.value = 1 + hasNoMoreData.value = false + jobList.value = [] + total.value = 0 // 筛选条件变化时清除缓存 store.commit('SET_JOB_LIST_CACHE', null) loadCurrentTab() @@ -752,8 +767,14 @@ async function loadFavoriteNextPage() { applied: false, showMenu: false, })) - jobList.value.push(...newItems) - total.value = Number(res.data.total) + // 返回空列表时标记无更多数据,停止滚动加载 + if (newItems.length === 0) { + hasNoMoreData.value = true + pageNum.value-- + } else { + jobList.value.push(...newItems) + total.value = Number(res.data.total) + } } } catch (e) { pageNum.value-- @@ -812,8 +833,14 @@ async function loadApplyNextPage() { applied: true, showMenu: false, })) - jobList.value.push(...newItems) - total.value = Number(res.data.total) + // 返回空列表时标记无更多数据,停止滚动加载 + if (newItems.length === 0) { + hasNoMoreData.value = true + pageNum.value-- + } else { + jobList.value.push(...newItems) + total.value = Number(res.data.total) + } } } catch (e) { pageNum.value-- @@ -850,6 +877,7 @@ watch(activeTab, (newTab, oldTab) => { keyword.value = '' pageNum.value = 1 total.value = 0 + hasNoMoreData.value = false loadCurrentTab() // 切换到收藏 Tab 时刷新统计 if (newTab === 'collected') {