feat(license): 在线授权 UI 优化 + 邮件语言透传

- license.vue:在线授权 Tab 移到离线前,默认 Tab 智能选择(未绑在线但已设
  离线→离线,其余在线优先);按钮级 loading 互斥(onlineAction:发码/登录/
  试用/退出各自 loading、其余禁用);登录/试用失败清空验证码并解除重发倒计时;
  邮箱行改「Input + 独立发送按钮」承载 loading
- OnlineLicense.php:新增 lang() 透传请求语言到 appstore,邮件按语言渲染
This commit is contained in:
kuaifan 2026-06-23 06:15:47 +00:00
parent 87e05ef9c9
commit fa9e56944a
2 changed files with 99 additions and 41 deletions

View File

@ -79,6 +79,15 @@ class OnlineLicense
}
}
/**
* 当前请求语言,透传给 appstore 用于邮件按语言渲染(中文/繁体→中文,其余→英文)。
* 非请求上下文(如定时续期)返回空串,由 appstore 回落默认语言。
*/
protected static function lang(): string
{
return (string)Base::headerOrInput('language');
}
protected static function fingerprint(): array
{
return [
@ -158,7 +167,7 @@ class OnlineLicense
*/
public static function emailSend(string $email): string
{
$r = self::call('email/send', ['email' => $email]);
$r = self::call('email/send', ['email' => $email, 'lang' => self::lang()]);
if (!$r['ok']) {
throw new ApiException($r['message']);
}
@ -170,7 +179,7 @@ class OnlineLicense
*/
public static function login(string $email, string $code): array
{
$r = self::call('login', array_merge(['email' => $email, 'code' => $code], self::fingerprint()));
$r = self::call('login', array_merge(['email' => $email, 'code' => $code, 'lang' => self::lang()], self::fingerprint()));
if (!$r['ok']) {
throw new ApiException($r['message']);
}
@ -186,7 +195,7 @@ class OnlineLicense
*/
public static function trial(string $email, string $code): array
{
$payload = array_merge(['email' => $email, 'code' => $code], self::fingerprint());
$payload = array_merge(['email' => $email, 'code' => $code, 'lang' => self::lang()], self::fingerprint());
$r = self::call('trial', $payload);
if (!$r['ok']) {
throw new ApiException($r['message']);

View File

@ -1,6 +1,45 @@
<template>
<div class="setting-item submit license-setting">
<Tabs v-model="mode">
<TabPane :label="$L('在线授权')" name="online">
<div v-if="onlineActive" class="license-box">
<ul class="online-info">
<li><em>{{$L('账号')}}:</em><span>{{online.account}}</span></li>
<li><em>{{$L('套餐')}}:</em><span>{{online.plan || '-'}}</span></li>
<li><em>{{$L('使用人数')}}:</em><span>{{online.people || $L('无限制')}}</span></li>
<li><em>{{$L('授权有效期')}}:</em><span>{{online.valid_until ? fmt(online.valid_until) : $L('永久')}}</span></li>
<li>
<em>{{$L('当前状态')}}:</em>
<span :class="{warning: online.status !== 'active'}">{{stageText(online.status)}}</span>
</li>
</ul>
<div class="setting-footer">
<Button :loading="onlineAction === 'logout'" :disabled="onlineBusy && onlineAction !== 'logout'" @click="onlineLogout">{{$L('退出在线授权')}}</Button>
</div>
</div>
<template v-else>
<Form :model="onlineForm" v-bind="formOptions" @submit.native.prevent>
<FormItem :label="$L('邮箱')">
<div class="online-email-row">
<Input v-model="onlineForm.email" class="online-email-input" :placeholder="$L('请输入邮箱')" @on-enter="emailSend"/>
<Button :loading="onlineAction === 'send'"
:disabled="codeCountdown > 0 || (onlineBusy && onlineAction !== 'send')"
@click="emailSend">
{{codeCountdown > 0 ? $L('(*)秒后重发', codeCountdown) : $L('发送验证码')}}
</Button>
</div>
</FormItem>
<FormItem v-if="codeSent" :label="$L('邮箱验证码')">
<Input v-model="onlineForm.code" class="setting-input" :placeholder="$L('请输入验证码')"/>
<div class="online-tip">{{$L('验证码已发送至(*)', maskedEmail)}}</div>
</FormItem>
</Form>
<div class="setting-footer">
<Button :loading="onlineAction === 'login'" :disabled="onlineBusy && onlineAction !== 'login'" type="primary" @click="onlineLogin">{{$L('登录授权')}}</Button>
<Button :loading="onlineAction === 'trial'" :disabled="onlineBusy && onlineAction !== 'trial'" type="success" @click="trialSubmit">{{$L('申请试用')}}</Button>
</div>
</template>
</TabPane>
<TabPane :label="$L('离线授权')" name="offline">
<template v-if="onlineActive">
<div class="license-box">
@ -107,42 +146,6 @@
</div>
</template>
</TabPane>
<TabPane :label="$L('在线授权')" name="online">
<div v-if="onlineActive" class="license-box">
<ul class="online-info">
<li><em>{{$L('账号')}}:</em><span>{{online.account}}</span></li>
<li><em>{{$L('套餐')}}:</em><span>{{online.plan || '-'}}</span></li>
<li><em>{{$L('使用人数')}}:</em><span>{{online.people || $L('无限制')}}</span></li>
<li><em>{{$L('授权有效期')}}:</em><span>{{online.valid_until ? fmt(online.valid_until) : $L('永久')}}</span></li>
<li>
<em>{{$L('当前状态')}}:</em>
<span :class="{warning: online.status !== 'active'}">{{stageText(online.status)}}</span>
</li>
</ul>
<div class="setting-footer">
<Button :loading="onlineIng > 0" @click="onlineLogout">{{$L('退出在线授权')}}</Button>
</div>
</div>
<template v-else>
<Form :model="onlineForm" v-bind="formOptions" @submit.native.prevent>
<FormItem :label="$L('邮箱')">
<Input v-model="onlineForm.email"
:class="codeCountdown > 0 ? 'setting-send-input' : 'setting-input'"
search @on-search="emailSend"
:enter-button="codeCountdown > 0 ? $L('(*)秒后重发', codeCountdown) : $L('发送验证码')"
:placeholder="$L('请输入邮箱')"/>
</FormItem>
<FormItem v-if="codeSent" :label="$L('邮箱验证码')">
<Input v-model="onlineForm.code" class="setting-input" :placeholder="$L('请输入验证码')"/>
<div class="online-tip">{{$L('验证码已发送至(*)', maskedEmail)}}</div>
</FormItem>
</Form>
<div class="setting-footer">
<Button :loading="onlineIng > 0" type="primary" @click="onlineLogin">{{$L('登录授权')}}</Button>
<Button :loading="onlineIng > 0" type="success" @click="trialSubmit">{{$L('申请试用')}}</Button>
</div>
</template>
</TabPane>
</Tabs>
</div>
</template>
@ -191,6 +194,18 @@
}
}
}
.online-email-row {
display: flex;
align-items: center;
max-width: 460px;
.online-email-input {
flex: 1;
margin-right: 8px;
}
.ivu-btn {
flex-shrink: 0;
}
}
.online-tip {
font-size: 12px;
line-height: 20px;
@ -216,11 +231,12 @@ export default {
online: {},
},
mode: 'offline',
mode: 'online',
tabInited: false,
offlineRebindShow: false,
offlineRebindLicense: '',
onlineIng: 0,
onlineAction: '', // 线'' | 'send' | 'login' | 'trial' | 'logout' loading/
onlineForm: {
email: '',
code: '',
@ -248,6 +264,11 @@ export default {
return this.online.mode === 'online';
},
// 线///退
onlineBusy() {
return this.onlineAction !== '';
},
// 线 = license 线
offlineBound() {
return !this.onlineActive && !!String(this.formData.license || '').trim();
@ -329,11 +350,13 @@ export default {
}
this.formData = data;
this.formData_bak = $A.cloneJSON(this.formData);
// 线线 Tab
// Tab线 线线线 线 线线
if (!this.tabInited) {
this.tabInited = true;
if (data.online && data.online.mode === 'online') {
this.mode = 'online';
} else if (String(data.license || '').trim()) {
this.mode = 'offline';
}
}
}).catch(({msg}) => {
@ -431,12 +454,17 @@ export default {
$A.messageError('请输入邮箱');
return;
}
this.onlineAction = 'send';
this.onlineCall('license/email/send', {
email: this.onlineForm.email,
}).then(({data}) => {
this.codeSent = true;
this.maskedEmail = data?.email || '';
this.startCodeCountdown();
}).catch(() => {
// onlineCall
}).finally(() => {
this.onlineAction = '';
});
},
@ -446,12 +474,20 @@ export default {
return;
}
this.confirmReplaceOffline(() => {
this.onlineAction = 'login';
this.onlineCall('license/login', {
email: this.onlineForm.email,
code: this.onlineForm.code,
}, '授权成功').then(_ => {
this.resetOnlineForm();
this.systemSetting();
}).catch(() => {
//
//
this.onlineForm.code = '';
this.clearCodeTimer();
}).finally(() => {
this.onlineAction = '';
});
});
},
@ -462,12 +498,20 @@ export default {
return;
}
this.confirmReplaceOffline(() => {
this.onlineAction = 'trial';
this.onlineCall('license/trial', {
email: this.onlineForm.email,
code: this.onlineForm.code,
}, '试用已开通').then(_ => {
this.resetOnlineForm();
this.systemSetting();
}).catch(() => {
//
//
this.onlineForm.code = '';
this.clearCodeTimer();
}).finally(() => {
this.onlineAction = '';
});
});
},
@ -476,8 +520,13 @@ export default {
$A.modalConfirm({
content: '确定退出在线授权?',
onOk: () => {
this.onlineAction = 'logout';
this.onlineCall('license/logout', {}, '已退出在线授权').then(_ => {
this.systemSetting();
}).catch(() => {
// onlineCall
}).finally(() => {
this.onlineAction = '';
});
}
});