fix(settings): fix disable account API and add feedback dialog

- Fix disable account API parameter format (pass null instead of empty object)
- Add feedback dialog after account disable (aligned with jcloud dashboard)
- Implement star rating, reason selection, and feedback submission
- Add submitFeedback API function
- Add missing Chinese translations for account status messages
- Add feedback dialog translations and validation logic
This commit is contained in:
jingrow 2026-01-04 21:23:29 +08:00
parent 79b92e7aae
commit f30502bf7d
3 changed files with 239 additions and 3 deletions

View File

@ -1296,6 +1296,10 @@
"Change your account login password": "更改您的账户登录密码",
"Disable Account": "禁用账户",
"Enable Account": "启用账户",
"Your account has been disabled successfully": "您的账户已成功禁用",
"Failed to disable account": "禁用账户失败",
"Your account has been enabled successfully": "您的账户已成功启用",
"Failed to enable account": "启用账户失败",
"Disable your account and stop billing": "禁用您的账户并停止计费",
"Enable your account and resume billing": "启用您的账户并恢复计费",
"After confirming this action:": "确认此操作后:",
@ -1329,6 +1333,7 @@
"Tip: Password should contain symbols, numbers and uppercase letters": "提示:密码应包含符号、数字和大写字母",
"Password updated successfully": "密码更新成功",
"Your password has been updated": "您的密码已更新",
"Profile updated successfully": "个人资料更新成功",
"Current password is incorrect": "当前密码不正确",
"Setting you as a developer...": "正在将您设置为开发者...",
"You can now publish apps to our marketplace": "您现在可以在我们的应用市场发布应用了",
@ -1357,5 +1362,23 @@
"Failed to disable two-factor authentication": "禁用双因素认证失败",
"Failed to load QR code": "加载二维码失败",
"Enabling two-factor authentication...": "正在启用双因素认证...",
"Disabling two-factor authentication...": "正在禁用双因素认证..."
"Disabling two-factor authentication...": "正在禁用双因素认证...",
"Tell us why you are leaving": "告诉我们您离开的原因",
"By sharing your thoughts, help us improve your experience.": "通过分享您的想法,帮助我们改善您的体验。",
"Please rate your experience": "请评价您的体验",
"Select a reason": "选择一个原因",
"The reason I am leaving Jingrow is...": "我离开 Jingrow 的原因是...",
"我要迁移到其他产品": "我要迁移到其他产品",
"我只是在探索这个产品": "我只是在探索这个产品",
"我更喜欢自己托管实例": "我更喜欢自己托管实例",
"已将站点迁移到另一个Jingrow账户": "已将站点迁移到另一个Jingrow账户",
"我不喜欢Jingrow的体验": "我不喜欢Jingrow的体验",
"Jingrow对我来说太贵了": "Jingrow对我来说太贵了",
"支付问题": "支付问题",
"缺少功能": "缺少功能",
"我的原因不在此列表中": "我的原因不在此列表中",
"请选择一个原因": "请选择一个原因",
"请评价您的体验": "请评价您的体验",
"请简要说明原因": "请简要说明原因",
"Your feedback has been submitted successfully": "您的反馈已成功提交"
}

View File

@ -291,7 +291,7 @@ export const disableAccount = async (totpCode?: string): Promise<{ success: bool
try {
const response = await axios.post(
`/api/action/jcloud.api.account.disable_account`,
totpCode ? { totp_code: totpCode } : {},
{ totp_code: totpCode || null },
{
headers: get_session_api_headers(),
withCredentials: true
@ -552,3 +552,30 @@ export const disable2FA = async (totpCode: string): Promise<{ success: boolean;
}
}
}
// 提交反馈
export const submitFeedback = async (team: string, message: string, note: string, rating: number, route?: string): Promise<{ success: boolean; message?: string }> => {
try {
const response = await axios.post(
`/api/action/jcloud.api.account.feedback`,
{
team,
message,
note,
rating,
route: route || null
},
{
headers: get_session_api_headers(),
withCredentials: true
}
)
return { success: true, message: response.data?.message || '反馈已提交' }
} catch (error: any) {
return {
success: false,
message: error.response?.data?.detail || error.response?.data?.message || error.message || '提交反馈失败'
}
}
}

View File

@ -820,6 +820,85 @@
<p class="account-status-dialog-text">{{ t('Do you want to continue?') }}</p>
</div>
</n-modal>
<!-- 反馈对话框 -->
<n-modal
v-model:show="showFeedbackDialog"
preset="card"
:title="t('Tell us why you are leaving')"
:style="{ width: '600px' }"
:mask-closable="false"
:close-on-esc="false"
>
<n-space vertical :size="20">
<p class="text-sm text-gray-800">
{{ t('By sharing your thoughts, help us improve your experience.') }}
</p>
<!-- 星级评分 -->
<div>
<span class="block text-sm leading-4 text-gray-600 mb-2">
{{ t('Please rate your experience') }}
</span>
<div class="star-rating">
<span
v-for="rating in [1, 2, 3, 4, 5]"
:key="rating"
class="star-rating-star"
@click="feedbackRating = rating"
@mouseenter="hoveredRating = rating"
@mouseleave="hoveredRating = 0"
>
<n-icon :size="20">
<Icon
:icon="(hoveredRating || feedbackRating) >= rating ? 'tabler:star-filled' : 'tabler:star'"
:style="{ color: (hoveredRating || feedbackRating) >= rating ? '#ECAC4B' : '#C0C6CC' }"
/>
</n-icon>
</span>
</div>
</div>
<!-- 原因选择 -->
<n-form-item :label="t('Select a reason')" required>
<n-select
v-model:value="feedbackReason"
:options="feedbackReasons.map(r => ({ label: r, value: r }))"
:placeholder="t('Select a reason')"
/>
</n-form-item>
<!-- 详细说明 -->
<n-form-item :label="t('The reason I am leaving Jingrow is...')">
<n-input
v-model:value="feedbackNote"
type="textarea"
:rows="4"
:placeholder="t('The reason I am leaving Jingrow is...')"
/>
</n-form-item>
<!-- 错误提示 -->
<n-alert
v-if="feedbackError"
type="error"
:title="feedbackError"
/>
</n-space>
<template #footer>
<n-space justify="end">
<n-button @click="closeFeedbackDialog">{{ t('Cancel') }}</n-button>
<n-button
type="primary"
:loading="feedbackLoading"
@click="handleSubmitFeedback"
>
{{ t('Submit') }}
</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
@ -880,7 +959,8 @@ import {
getPartnerName,
get2FAQRCodeUrl,
enable2FA,
disable2FA
disable2FA,
submitFeedback
} from '../../shared/api/account'
import ClickToCopyField from '../../shared/components/ClickToCopyField.vue'
@ -1000,6 +1080,15 @@ const showEnableAccountDialog = ref(false)
const disableAccountLoading = ref(false)
const enableAccountLoading = ref(false)
//
const showFeedbackDialog = ref(false)
const feedbackRating = ref(0)
const feedbackReason = ref('')
const feedbackNote = ref('')
const feedbackLoading = ref(false)
const feedbackError = ref('')
const hoveredRating = ref(0)
// API Secret
const showCreateSecretDialog = ref(false)
const createSecretData = ref<{ api_key: string; api_secret: string } | null>(null)
@ -1678,6 +1767,8 @@ const handleDisableAccountConfirm = async () => {
message.success(t('Your account has been disabled successfully'))
showDisableAccountDialog.value = false
await loadUserAccountInfo()
//
showFeedbackDialog.value = true
} else {
message.error(result.message || t('Failed to disable account'))
}
@ -1686,6 +1777,84 @@ const handleDisableAccountConfirm = async () => {
}
}
//
const feedbackReasons = [
t('我要迁移到其他产品'),
t('我只是在探索这个产品'),
t('我更喜欢自己托管实例'),
t('已将站点迁移到另一个Jingrow账户'),
t('我不喜欢Jingrow的体验'),
t('Jingrow对我来说太贵了'),
t('支付问题'),
t('缺少功能'),
t('我的原因不在此列表中')
]
//
const handleSubmitFeedback = async () => {
//
if (!feedbackReason.value) {
feedbackError.value = t('请选择一个原因')
return
}
if (feedbackRating.value === 0) {
feedbackError.value = t('请评价您的体验')
return
}
const requiresNote = [
t('支付问题'),
t('缺少功能'),
t('我的原因不在此列表中')
].includes(feedbackReason.value)
if (requiresNote && !feedbackNote.value) {
feedbackError.value = t('请简要说明原因')
return
}
feedbackLoading.value = true
feedbackError.value = ''
try {
const result = await submitFeedback(
teamInfo.value?.name || '',
feedbackReason.value,
feedbackNote.value,
feedbackRating.value
)
if (result.success) {
message.success(t('Your feedback has been submitted successfully'))
showFeedbackDialog.value = false
//
feedbackRating.value = 0
feedbackReason.value = ''
feedbackNote.value = ''
//
setTimeout(() => {
window.location.href = '/dashboard'
}, 1000)
} else {
feedbackError.value = result.message || t('提交反馈失败')
}
} catch (error: any) {
feedbackError.value = error.message || t('提交反馈失败')
} finally {
feedbackLoading.value = false
}
}
//
const closeFeedbackDialog = () => {
showFeedbackDialog.value = false
feedbackRating.value = 0
feedbackReason.value = ''
feedbackNote.value = ''
feedbackError.value = ''
}
//
const handleEnableAccountConfirm = async () => {
enableAccountLoading.value = true
@ -2168,6 +2337,23 @@ onMounted(async () => {
margin-top: 0;
}
/* 星级评分样式 */
.star-rating {
display: flex;
gap: 4px;
align-items: center;
}
.star-rating-star {
display: inline-block;
cursor: pointer;
transition: transform 0.1s;
}
.star-rating-star:hover {
transform: scale(1.1);
}
/* 响应式设计 */
@media (max-width: 1200px) {
.settings-page :deep(.n-grid) {