Skip to content

Commit 727452a

Browse files
committed
feat: 添加无需授权场景的可选联系方式填写的对话框
1 parent 29bace3 commit 727452a

File tree

10 files changed

+665
-8
lines changed

10 files changed

+665
-8
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<template>
2+
<q-dialog v-model="isVisible" persistent>
3+
<q-card style="width: 90%; max-width: 360px">
4+
<!-- Header -->
5+
<q-card-section class="bg-teal text-white q-pa-sm">
6+
<div class="text-subtitle1">{{ t('exportFlow.optionalContact.title') }}</div>
7+
</q-card-section>
8+
9+
<!-- Content -->
10+
<q-card-section class="q-pa-md">
11+
<div class="text-caption text-grey-7 q-mb-md">
12+
{{ t('exportFlow.optionalContact.description') }}
13+
</div>
14+
15+
<!-- Email Input -->
16+
<q-input
17+
v-model="formData.email"
18+
:label="t('exportFlow.contact.emailLabel')"
19+
:placeholder="t('exportFlow.contact.emailPlaceholder')"
20+
filled
21+
dense
22+
type="email"
23+
counter
24+
maxlength="200"
25+
@blur="validateEmail"
26+
/>
27+
<div v-if="emailError" class="text-caption text-negative q-mt-xs">
28+
{{ emailError }}
29+
</div>
30+
31+
<!-- Additional Contact Input -->
32+
<q-input
33+
v-model="formData.additional"
34+
:label="t('exportFlow.contact.additionalLabel')"
35+
:placeholder="t('exportFlow.contact.additionalPlaceholder')"
36+
filled
37+
dense
38+
type="textarea"
39+
autogrow
40+
counter
41+
maxlength="500"
42+
class="q-mt-sm"
43+
/>
44+
45+
<div class="text-caption text-grey q-mt-sm">
46+
{{ t('exportFlow.optionalContact.hint') }}
47+
</div>
48+
</q-card-section>
49+
50+
<!-- Actions -->
51+
<q-card-actions align="right" class="q-pa-sm q-gutter-xs">
52+
<q-btn flat :label="t('exportFlow.optionalContact.skip')" color="grey" size="sm" @click="onSkip" />
53+
<q-btn
54+
unelevated
55+
:label="t('exportFlow.optionalContact.continue')"
56+
color="teal"
57+
size="sm"
58+
:disable="!canContinue"
59+
@click="onSubmit"
60+
/>
61+
</q-card-actions>
62+
</q-card>
63+
</q-dialog>
64+
</template>
65+
66+
<script setup lang="ts">
67+
import { ref, computed, watch } from 'vue';
68+
import { useI18n } from 'vue-i18n';
69+
70+
interface OptionalContactDialogProps {
71+
visible: boolean;
72+
}
73+
74+
interface OptionalContactDialogEmits {
75+
(e: 'submit', data: { email?: string; additional?: string }): void;
76+
(e: 'skip'): void;
77+
(e: 'cancel'): void;
78+
}
79+
80+
const props = withDefaults(defineProps<OptionalContactDialogProps>(), {
81+
visible: false,
82+
});
83+
84+
const emit = defineEmits<OptionalContactDialogEmits>();
85+
const { t } = useI18n();
86+
87+
const isVisible = ref(false);
88+
const formData = ref({
89+
email: '',
90+
additional: '',
91+
});
92+
const emailError = ref('');
93+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
94+
95+
// Validate email format (only if email is not empty)
96+
const isValidEmail = (value: string) => {
97+
const trimmed = value.trim();
98+
if (!trimmed) return true; // Empty is valid (optional)
99+
return emailPattern.test(trimmed);
100+
};
101+
102+
// Computed: Can continue button be enabled?
103+
const canContinue = computed(() => {
104+
// If email is entered, it must be valid format
105+
return isValidEmail(formData.value.email);
106+
});
107+
108+
// Watch visible prop
109+
watch(
110+
() => props.visible,
111+
(newVal) => {
112+
isVisible.value = newVal;
113+
if (newVal) {
114+
// Reset form on open
115+
formData.value = {
116+
email: '',
117+
additional: '',
118+
};
119+
emailError.value = '';
120+
}
121+
}
122+
);
123+
124+
// Watch isVisible to sync with parent
125+
watch(isVisible, (newVal) => {
126+
if (!newVal) {
127+
emit('cancel');
128+
}
129+
});
130+
131+
// Validate email on blur
132+
const validateEmail = () => {
133+
const trimmed = formData.value.email.trim();
134+
if (!trimmed) {
135+
emailError.value = '';
136+
return;
137+
}
138+
139+
if (!isValidEmail(trimmed)) {
140+
emailError.value = t('exportFlow.contact.emailInvalid');
141+
return;
142+
}
143+
144+
emailError.value = '';
145+
};
146+
147+
// Skip - proceed without contact info
148+
const onSkip = () => {
149+
emit('skip');
150+
isVisible.value = false;
151+
};
152+
153+
// Submit - proceed with contact info
154+
const onSubmit = () => {
155+
if (!canContinue.value) {
156+
validateEmail();
157+
return;
158+
}
159+
160+
const email = formData.value.email.trim();
161+
const additional = formData.value.additional.trim();
162+
163+
emit('submit', {
164+
email: email || undefined,
165+
additional: additional || undefined,
166+
});
167+
168+
isVisible.value = false;
169+
};
170+
</script>

frontend/src/components/export-flow/useExportSignatureFlow.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ interface State {
4545
| 'auth-requirement' // 二次创作是否需要授权(推荐不需要)
4646
| 'auth-impact-confirm' // 选择需要授权后的二次确认弹窗
4747
| 'auth-contact' // 需要授权时的联系方式填写
48+
| 'optional-contact' // 无需授权时的可选联系方式填写
4849
| 'auth-gate' // 已有签名且原作者要求授权时的授权门控
4950
| 'auth-gate-from-picker' // 从签名选择页面打开的授权门控(用于导入授权文件)
5051
| 'auth-request' // 授权申请流程(用于申请签名授权)
@@ -90,6 +91,7 @@ export function useExportSignatureFlow() {
9091
const authRequirementDialogVisible = ref(false);
9192
const authImpactConfirmDialogVisible = ref(false);
9293
const authContactDialogVisible = ref(false);
94+
const optionalContactDialogVisible = ref(false);
9395
const authGateDialogVisible = ref(false);
9496
const authRequestDialogVisible = ref(false);
9597
const pickerDialogVisible = ref(false);
@@ -193,9 +195,9 @@ export function useExportSignatureFlow() {
193195
if (!payload.requireAuthorization) {
194196
const needSignature = state.value.flowData?.needSignature ?? true;
195197
if (needSignature) {
196-
// 仍需落地签名,继续选择
197-
state.value.step = 'picker';
198-
pickerDialogVisible.value = true;
198+
// 无需授权但需要签名 → 进入可选联系方式填写
199+
state.value.step = 'optional-contact';
200+
optionalContactDialogVisible.value = true;
199201
} else {
200202
// 无需签名且无需授权 → 就地完成
201203
state.value.step = 'done';
@@ -242,6 +244,29 @@ export function useExportSignatureFlow() {
242244
state.value.step = 'idle';
243245
};
244246

247+
// ========== Step: optional-contact ==========
248+
const handleOptionalContactSubmit = (payload: { email?: string; additional?: string }) => {
249+
state.value.flowData = {
250+
...(state.value.flowData ?? {}),
251+
contactEmail: payload.email,
252+
contactAdditional: payload.additional,
253+
};
254+
optionalContactDialogVisible.value = false;
255+
// 进入签名选择
256+
state.value.step = 'picker';
257+
pickerDialogVisible.value = true;
258+
};
259+
const handleOptionalContactSkip = () => {
260+
optionalContactDialogVisible.value = false;
261+
// 直接进入签名选择(不保存联系方式)
262+
state.value.step = 'picker';
263+
pickerDialogVisible.value = true;
264+
};
265+
const handleOptionalContactCancel = () => {
266+
optionalContactDialogVisible.value = false;
267+
state.value.step = 'idle';
268+
};
269+
245270
/**
246271
* Check if authorization is needed, and proceed accordingly.
247272
*/
@@ -363,6 +388,7 @@ export function useExportSignatureFlow() {
363388
authRequirementDialogVisible.value = false;
364389
authImpactConfirmDialogVisible.value = false;
365390
authContactDialogVisible.value = false;
391+
optionalContactDialogVisible.value = false;
366392
state.value.step = 'idle';
367393
};
368394

@@ -396,6 +422,7 @@ export function useExportSignatureFlow() {
396422
authRequirementDialogVisible.value = false;
397423
authImpactConfirmDialogVisible.value = false;
398424
authContactDialogVisible.value = false;
425+
optionalContactDialogVisible.value = false;
399426
authGateDialogVisible.value = false;
400427
authRequestDialogVisible.value = false;
401428
pickerDialogVisible.value = false;
@@ -412,6 +439,7 @@ export function useExportSignatureFlow() {
412439
authRequirementDialogVisible,
413440
authImpactConfirmDialogVisible,
414441
authContactDialogVisible,
442+
optionalContactDialogVisible,
415443
authGateDialogVisible,
416444
authRequestDialogVisible,
417445
pickerDialogVisible,
@@ -428,6 +456,9 @@ export function useExportSignatureFlow() {
428456
handleAuthImpactConfirm,
429457
handleAuthContactSubmit,
430458
handleAuthContactCancel,
459+
handleOptionalContactSubmit,
460+
handleOptionalContactSkip,
461+
handleOptionalContactCancel,
431462
handleAuthGateAuthorized,
432463
handleAuthGateCancel,
433464
openAuthGateFromPicker,

frontend/src/i18n/en-US/index.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,13 @@
371371
"additionalPlaceholder": "Add social media, website, or other contact info",
372372
"hint": "These details are stored with the signature so requesters can reach you"
373373
},
374+
"optionalContact": {
375+
"title": "Leave Contact Info (Optional)",
376+
"description": "You can optionally provide contact information for others to reach you. This step is optional and can be skipped.",
377+
"hint": "Contact info will be stored with the signature for others to view",
378+
"skip": "Skip",
379+
"continue": "Save & Continue"
380+
},
374381
"policyDialog": {
375382
"title": "Signature Policy & Requirements",
376383
"noSignatureTitle": "Set Signature Policy",

frontend/src/i18n/zh-CN/index.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,13 @@
365365
"additionalPlaceholder": "可补充社交媒体、官网等信息",
366366
"hint": "这些联系方式将随签名一同保存,供申请授权者查阅"
367367
},
368+
"optionalContact": {
369+
"title": "留下联系方式(可选)",
370+
"description": "您可以选择留下联系方式,便于他人与您取得联系。此步骤为可选,可直接跳过。",
371+
"hint": "联系方式将随签名一同保存,供他人查阅",
372+
"skip": "跳过",
373+
"continue": "保存并继续"
374+
},
368375
"policyDialog": {
369376
"title": "签名策略与要求",
370377
"noSignatureTitle": "设置签名策略",

frontend/src/pages/Keytone_album_page_new.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,13 @@
312312
@submit="exportFlow.handleAuthContactSubmit"
313313
@cancel="exportFlow.handleAuthContactCancel"
314314
/>
315+
<!-- 4.5) 可选联系方式(无需授权时) -->
316+
<optional-contact-dialog
317+
:visible="exportFlow.optionalContactDialogVisible.value"
318+
@submit="exportFlow.handleOptionalContactSubmit"
319+
@skip="exportFlow.handleOptionalContactSkip"
320+
@cancel="exportFlow.handleOptionalContactCancel"
321+
/>
315322
<!-- 5) 已有签名且需要授权 → 授权门控(导入授权文件) -->
316323
<export-authorization-gate-dialog
317324
:visible="exportFlow.authGateDialogVisible.value"
@@ -371,6 +378,7 @@ import ExportReexportWarningDialog from 'src/components/export-flow/ExportReexpo
371378
import ExportAuthorizationRequirementDialog from 'src/components/export-flow/ExportAuthorizationRequirementDialog.vue';
372379
import ExportAuthorizationImpactConfirmDialog from 'src/components/export-flow/ExportAuthorizationImpactConfirmDialog.vue';
373380
import ExportAuthorizationContactDialog from 'src/components/export-flow/ExportAuthorizationContactDialog.vue';
381+
import OptionalContactDialog from 'src/components/export-flow/OptionalContactDialog.vue';
374382
import ExportAuthorizationGateDialog from 'src/components/export-flow/ExportAuthorizationGateDialog.vue';
375383
import SignaturePickerDialog from 'src/components/export-flow/SignaturePickerDialog.vue';
376384
import AuthRequestDialog from 'src/components/export-flow/AuthRequestDialog.vue';

0 commit comments

Comments
 (0)