mirror of
https://github.com/kuaifan/dootask.git
synced 2026-07-03 04:45:09 +00:00
feat(license): 在线异常浮告警卡+离线SN/MAC标签化,统一两Tab排版
- 在线:正常态只显示核心信息,提醒/过期/设备不匹配时浮出告警卡,SN/MAC 收进折叠诊断;frozen 状态文案统一为「已过期」 - 离线:SN/MAC 改行尾标签+失配整行标红,IP/域名/创建时间折叠进「更多」 - 统一在线/离线信息行的字号/行高/间距/label - 清理死样式 .information 与死变量 onlineIng - 同步 ai-kb online.md 状态措辞并登记新增文案
This commit is contained in:
parent
f45f86601e
commit
c9f5296b73
@ -2499,3 +2499,26 @@ AI任务分析
|
||||
(*)秒后重发
|
||||
请输入邮箱和验证码
|
||||
刷新中
|
||||
诊断详情
|
||||
授权 SN
|
||||
当前 SN
|
||||
授权 MAC
|
||||
当前 MAC
|
||||
授权与当前设备不匹配
|
||||
检测到设备标识(SN)已变更,在线授权可能已失效。请重新登录授权,或先在原设备退出以释放座位。
|
||||
在线授权已过期
|
||||
新增用户已受限,请尽快联网以自动续期恢复。
|
||||
检测到网卡(MAC)变化
|
||||
系统会在下次续期时自动恢复授权,通常无需处理。
|
||||
续期失败,请检查网络
|
||||
授权仍然有效,联网后会自动续期恢复。
|
||||
授权即将到期
|
||||
请保持联网,系统会自动为你续期。
|
||||
重新登录授权
|
||||
匹配
|
||||
与本机不一致
|
||||
收起
|
||||
更多信息
|
||||
提示
|
||||
将释放当前设备占用的授权座位并回到登录,确定继续?
|
||||
已过期
|
||||
|
||||
@ -19,6 +19,11 @@ aliases:
|
||||
- 在线授权冻结
|
||||
- 在线授权被吊销
|
||||
- 换机 deactivate
|
||||
- SN 与 License 不匹配
|
||||
- MAC 与 License 不匹配
|
||||
- 终端SN与License不匹配
|
||||
- 终端MAC与License不匹配
|
||||
- 换机后在线授权失效
|
||||
related_tools: []
|
||||
related_pages: []
|
||||
prerequisites:
|
||||
@ -30,7 +35,7 @@ negative:
|
||||
- 离线授权(粘贴 License 原文)完全不受影响,没有自动续期
|
||||
- 一个账号同一时刻只占用一个实例座位,换机需先在原实例「退出在线授权」释放
|
||||
- 试用每个账号仅一次,时长由 App Store 管理员配置且硬上限 60 天
|
||||
last_verified: v1.7.91
|
||||
last_verified: v1.8.45
|
||||
---
|
||||
|
||||
# 在线授权(邮箱验证码登录 / 申请试用 / 自动续期)
|
||||
@ -53,15 +58,14 @@ DooTask 的 License 页提供两种授权方式,可在页面顶部 Tab 切换
|
||||
### 已有授权 → 登录授权
|
||||
- 填好邮箱与验证码后点击「登录授权」
|
||||
- 终端把自身指纹(doo_sn、网卡 MAC、版本)上报授权中心,签发一张租约 License 并落地
|
||||
- 成功后页面显示套餐、使用人数、租约到期、当前状态
|
||||
- 成功后页面显示账号、套餐、使用人数、授权有效期、SN/MAC(与本机是否匹配)、当前状态
|
||||
|
||||
### 没有正式授权 → 申请试用
|
||||
- 填好邮箱与验证码后点击「申请试用」即开通试用授权并签发
|
||||
- 试用默认 14 天 / 不限人数(具体以 App Store 管理员配置为准,时长硬上限 60 天),每个账号仅能申请一次
|
||||
|
||||
### 日常维护
|
||||
- **自动续期**:终端每小时检查,租约将尽时自动向授权中心续期,无需人工干预(需保持联网)
|
||||
- **立即续期**:状态页「立即续期」按钮可手动触发一次
|
||||
- **自动续期**:终端每小时检查,租约将尽时自动向授权中心续期,无需人工干预(需保持联网);进入授权页时也会静默刷新一次
|
||||
- **换机 / 退出**:「退出在线授权」会释放该账号在授权中心占用的座位,并把终端回落到基础版;换机时先在原实例退出,再到新实例登录
|
||||
|
||||
## 状态与提醒(断网/欠费时的分级降级)
|
||||
@ -71,11 +75,21 @@ DooTask 的 License 页提供两种授权方式,可在页面顶部 Tab 切换
|
||||
| --- | --- | --- |
|
||||
| 生效中 | 续期正常 | 无 |
|
||||
| 即将到期 | 续期失败或租约临近 | 仅提醒 |
|
||||
| 已冻结 | 租约已过期 | 限制新增用户(沿用离线过期的既有行为) |
|
||||
| 已吊销 | 冻结超过宽限期或授权被收回 | 回落基础版(最多 3 人,超出的账号按既有规则禁用) |
|
||||
| 已过期 | 租约已到期 | 限制新增用户(沿用离线过期的既有行为) |
|
||||
| 已吊销 | 过期超过宽限期或授权被收回 | 回落基础版(最多 3 人,超出的账号按既有规则禁用) |
|
||||
|
||||
只要在租约窗口内恢复联网并成功续期一次,即可回到「生效中」。
|
||||
|
||||
## SN / MAC 与本机匹配
|
||||
在线授权签发的 License 内嵌了签发时的终端 SN 与网卡 MAC。正常情况下二者与本机一致,「在线授权」Tab 保持极简,只显示账号、套餐、使用人数、有效期与状态(带颜色圆点),不常驻展示 SN/MAC 这类技术细节。
|
||||
|
||||
一旦 SN 或 MAC 与本机不一致,页面顶部会浮出告警卡,并展开「诊断详情」逐项列出「授权 SN / 当前 SN / 授权 MAC / 当前 MAC」及匹配标记(✓/✕);同一批告警也会出现在仪表盘顶部横幅。「离线授权」Tab(手动粘贴场景)则始终以列表展示 SN/MAC,用行尾标签直陈匹配与否、不一致时整行标红,IP/域名/创建时间等低频字段收纳在「更多信息」里。
|
||||
|
||||
授权成功后若终端的 SN 或 MAC 发生变化,行为不同:
|
||||
|
||||
- **MAC 变化(SN 不变)**:属于同一实例,下一次自动续期会用新的 MAC 重新签发 License 自动恢复,通常无感;重签前的短暂窗口可能出现 MAC 不匹配告警。
|
||||
- **SN 变化**:视为换机。续期会被授权中心判为座位被占用而拒绝重签,原 License 因 SN 对不上而失效,终端将按状态机走向「已过期 → 已吊销」并回落基础版。需在原实例(或让管理员在 App Store 授权中心)释放座位后,在新实例重新登录授权。
|
||||
|
||||
## 怎么自检
|
||||
- 接口 `POST api/license/status` 返回当前在线授权状态(mode/plan/people/lease_expired_at/status)
|
||||
- 提醒文案同样并入 `POST api/system/license` 的 `error` 数组,便于脚本巡检
|
||||
|
||||
@ -1,173 +1,166 @@
|
||||
<template>
|
||||
<div class="setting-item submit license-setting">
|
||||
<Tabs v-model="mode">
|
||||
<TabPane :label="$L('在线授权')" name="online">
|
||||
<div class="setting-component-item">
|
||||
<div class="setting-scroll">
|
||||
<!-- 首次进入且无缓存:骨架占位 + 加载中(有缓存则直接渲染下方真实数据) -->
|
||||
<div v-if="firstLoading" class="license-box">
|
||||
<div class="online-refreshing"><i class="online-spin"></i>{{$L('加载中...')}}</div>
|
||||
<ul class="online-info">
|
||||
<li><em>{{$L('账号')}}:</em><span class="online-skeleton"></span></li>
|
||||
<li><em>{{$L('套餐')}}:</em><span class="online-skeleton sk-sm"></span></li>
|
||||
<li><em>{{$L('使用人数')}}:</em><span class="online-skeleton sk-xs"></span></li>
|
||||
<li><em>{{$L('授权有效期')}}:</em><span class="online-skeleton"></span></li>
|
||||
<li><em>{{$L('当前状态')}}:</em><span class="online-skeleton sk-sm"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="onlineActive" class="license-box">
|
||||
<!-- 后台刷新中指示(缓存秒开后仍刷新最新数据) -->
|
||||
<div v-if="onlineRefreshing" class="online-refreshing"><i class="online-spin"></i>{{$L('刷新中')}}</div>
|
||||
<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>
|
||||
<TabPane :label="$L('在线授权')" name="online">
|
||||
<div class="setting-component-item">
|
||||
<div class="setting-scroll">
|
||||
<!-- 首次进入且无缓存:骨架占位 + 加载中(有缓存则直接渲染下方真实数据) -->
|
||||
<div v-if="firstLoading" class="license-box">
|
||||
<div class="online-refreshing"><i class="online-spin"></i>{{$L('加载中...')}}</div>
|
||||
<ul class="online-info">
|
||||
<li><em>{{$L('账号')}}:</em><span class="online-skeleton"></span></li>
|
||||
<li><em>{{$L('套餐')}}:</em><span class="online-skeleton sk-sm"></span></li>
|
||||
<li><em>{{$L('使用人数')}}:</em><span class="online-skeleton sk-xs"></span></li>
|
||||
<li><em>{{$L('授权有效期')}}:</em><span class="online-skeleton"></span></li>
|
||||
<li><em>{{$L('当前状态')}}:</em><span class="online-skeleton sk-sm"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="onlineActive" class="license-box">
|
||||
<!-- 后台刷新中指示(缓存秒开后仍刷新最新数据) -->
|
||||
<div v-if="onlineRefreshing" class="online-refreshing"><i class="online-spin"></i>{{$L('刷新中')}}</div>
|
||||
<!-- 异常告警卡:仅提醒/冻结/设备不匹配时浮现,正常态不显示,避免噪音 -->
|
||||
<div v-if="onlineAlert" class="online-alert" :class="onlineAlert.type">
|
||||
<i class="online-alert-ico">{{onlineAlert.type === 'error' ? '✕' : '!'}}</i>
|
||||
<div class="online-alert-main">
|
||||
<div class="online-alert-title">{{onlineAlert.title}}</div>
|
||||
<div class="online-alert-desc">{{onlineAlert.desc}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 核心信息:正常态只看结果,不常驻 SN/MAC -->
|
||||
<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="online-status" :class="'is-' + onlineHealth"><i class="online-status-dot"></i>{{stageText(online.status)}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- 诊断详情:仅设备(SN/MAC)不匹配时展开,普通用户不受打扰 -->
|
||||
<div v-if="onlineMismatch" class="online-diag">
|
||||
<div class="online-diag-title">{{$L('诊断详情')}}</div>
|
||||
<div class="online-diag-row" :class="{bad: !snMatch}"><em>{{$L('授权 SN')}}:</em><span>{{formData.info.sn}}</span></div>
|
||||
<div class="online-diag-row" :class="{bad: !snMatch}"><em>{{$L('当前 SN')}}:</em><span>{{formData.doo_sn}}<b>{{snMatch ? ' ✓' : ' ✕'}}</b></span></div>
|
||||
<div class="online-diag-row" :class="{bad: !macMatch}"><em>{{$L('授权 MAC')}}:</em><span>{{infoJoin(formData.info.mac)}}</span></div>
|
||||
<div class="online-diag-row" :class="{bad: !macMatch}"><em>{{$L('当前 MAC')}}:</em><span>{{infoJoin(formData.macs)}}<b>{{macMatch ? ' ✓' : ' ✕'}}</b></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<Form v-else :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="sendBtnText"
|
||||
:disabled="onlineBusy"
|
||||
: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>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="!firstLoading" class="setting-footer">
|
||||
<template v-if="onlineActive">
|
||||
<Button v-if="onlineAlert && onlineAlert.relogin" :loading="onlineAction === 'relogin'" :disabled="onlineBusy && onlineAction !== 'relogin'" type="primary" @click="onlineRelogin">{{$L('重新登录授权')}}</Button>
|
||||
<Button :loading="onlineAction === 'logout'" :disabled="onlineBusy && onlineAction !== 'logout'" :type="onlineAlert && onlineAlert.relogin ? 'default' : 'primary'" @click="onlineLogout">{{$L('退出在线授权')}}</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button :loading="onlineAction === 'login'" :disabled="onlineBusy && onlineAction !== 'login'" type="primary" @click="onlineLogin">{{$L('登录授权')}}</Button>
|
||||
<Button :loading="onlineAction === 'trial'" :disabled="onlineBusy && onlineAction !== 'trial'" @click="trialSubmit">{{$L('申请试用')}}</Button>
|
||||
</template>
|
||||
</div>
|
||||
<Form v-else :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="sendBtnText"
|
||||
:disabled="onlineBusy"
|
||||
: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>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="!firstLoading" class="setting-footer">
|
||||
<template v-if="onlineActive">
|
||||
<Button :loading="onlineAction === 'logout'" :disabled="onlineBusy && onlineAction !== 'logout'" type="primary" @click="onlineLogout">{{$L('退出在线授权')}}</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button :loading="onlineAction === 'login'" :disabled="onlineBusy && onlineAction !== 'login'" type="primary" @click="onlineLogin">{{$L('登录授权')}}</Button>
|
||||
<Button :loading="onlineAction === 'trial'" :disabled="onlineBusy && onlineAction !== 'trial'" @click="trialSubmit">{{$L('申请试用')}}</Button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane :label="$L('离线授权')" name="offline">
|
||||
<div class="setting-component-item">
|
||||
<div class="setting-scroll">
|
||||
<template v-if="onlineActive">
|
||||
<div class="license-box">
|
||||
<ul class="online-info">
|
||||
<li><em>{{$L('当前状态')}}:</em><span class="online-link" @click="mode = 'online'">{{$L('已绑定在线授权')}}</span></li>
|
||||
<li><em>SN:</em><span>{{formData.doo_sn}}</span></li>
|
||||
<li><em>MAC:</em><span>{{infoJoin(formData.macs)}}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<Form v-if="offlineRebindShow" :model="formData" v-bind="formOptions" @submit.native.prevent>
|
||||
<FormItem label="License">
|
||||
<Input v-model="offlineRebindLicense" type="textarea" :autosize="{minRows: 2,maxRows: 5}" :placeholder="$L('请输入License...')" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Form ref="formData" :model="formData" v-bind="formOptions" @submit.native.prevent>
|
||||
<FormItem label="License" prop="license">
|
||||
<Input v-model="formData.license" type="textarea" :autosize="{minRows: 2,maxRows: 5}" :placeholder="$L('请输入License...')" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<div class="license-box">
|
||||
<ul v-if="formData.info.sn">
|
||||
<li>
|
||||
<em>SN:</em>
|
||||
<span>{{formData.info.sn}}</span>
|
||||
<ETooltip max-width="auto" placement="right">
|
||||
<div slot="content">{{$L('当前环境')}}: {{formData.doo_sn}}</div>
|
||||
<Icon class="information" :class="{error: !existIntersection(formData.doo_sn, formData.info.sn)}" type="ios-information-circle-outline" />
|
||||
</ETooltip>
|
||||
</li>
|
||||
<li>
|
||||
<em>IP:</em>
|
||||
<span>{{infoJoin(formData.info.ip)}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<em>{{$L('域名')}}:</em>
|
||||
<span>{{infoJoin(formData.info.domain)}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<em>MAC:</em>
|
||||
<span>{{infoJoin(formData.info.mac)}}</span>
|
||||
<ETooltip max-width="auto" placement="right">
|
||||
<div slot="content">{{$L('当前环境')}}: {{infoJoin(formData.macs, '-')}}</div>
|
||||
<Icon class="information" :class="{error: !existIntersection(formData.macs, formData.info.mac)}" type="ios-information-circle-outline" />
|
||||
</ETooltip>
|
||||
</li>
|
||||
<li>
|
||||
<em>{{$L('使用人数')}}:</em>
|
||||
<span>{{formData.info.people || $L('无限制')}} ({{$L('已使用')}}: {{formData.user_count}})</span>
|
||||
<ETooltip max-width="auto" placement="right">
|
||||
<div slot="content">{{$L('限制注册人数')}}</div>
|
||||
<Icon class="information" type="ios-information-circle-outline" />
|
||||
</ETooltip>
|
||||
</li>
|
||||
<li>
|
||||
<em>{{$L('创建时间')}}:</em>
|
||||
<span>{{formData.info.created_at}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<em>{{$L('到期时间')}}:</em>
|
||||
<span>{{formData.info.expired_at || $L('永久')}}</span>
|
||||
<ETooltip v-if="formData.info.expired_at" max-width="auto" placement="right">
|
||||
<div slot="content">{{$L('到期后限制注册帐号')}}</div>
|
||||
<Icon class="information" type="ios-information-circle-outline" />
|
||||
</ETooltip>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-else>
|
||||
<li>
|
||||
{{$L('加载中...')}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('当前环境')" v-if="formData.error?.length > 0">
|
||||
<div class="license-box">
|
||||
<ul>
|
||||
<li>
|
||||
<em>SN:</em>
|
||||
<span>{{formData.doo_sn}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<em>MAC:</em>
|
||||
<span>{{infoJoin(formData.macs)}}</span>
|
||||
</li>
|
||||
<li v-for="(tip, ti) in formData.error" :key="ti" class="warning">{{tip}}</li>
|
||||
</ul>
|
||||
</TabPane>
|
||||
<TabPane :label="$L('离线授权')" name="offline">
|
||||
<div class="setting-component-item">
|
||||
<div class="setting-scroll">
|
||||
<template v-if="onlineActive">
|
||||
<div class="license-box">
|
||||
<ul class="online-info">
|
||||
<li><em>{{$L('当前状态')}}:</em><span class="online-link" @click="mode = 'online'">{{$L('已绑定在线授权')}}</span></li>
|
||||
<li><em>SN:</em><span>{{formData.doo_sn}}</span></li>
|
||||
<li><em>MAC:</em><span>{{infoJoin(formData.macs)}}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<Form v-if="offlineRebindShow" :model="formData" v-bind="formOptions" @submit.native.prevent>
|
||||
<FormItem label="License">
|
||||
<Input v-model="offlineRebindLicense" type="textarea" :autosize="{minRows: 2,maxRows: 5}" :placeholder="$L('请输入License...')" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Form ref="formData" :model="formData" v-bind="formOptions" @submit.native.prevent class="license-form">
|
||||
<FormItem label="License" prop="license">
|
||||
<Input v-model="formData.license" type="textarea" :autosize="{minRows: 2,maxRows: 5}" :placeholder="$L('请输入License...')" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<div class="license-box">
|
||||
<ul v-if="formData.info.sn" class="offline-detail">
|
||||
<!-- SN/MAC:行尾标签直陈匹配与否,失配整行标红,无需悬停 -->
|
||||
<li class="offline-row" :class="{bad: !snMatch}">
|
||||
<em>SN:</em>
|
||||
<span class="v">{{formData.info.sn}}</span>
|
||||
<span class="offline-flag">{{snMatch ? $L('匹配') : $L('与本机不一致')}}</span>
|
||||
</li>
|
||||
<li class="offline-row" :class="{bad: !macMatch}">
|
||||
<em>MAC:</em>
|
||||
<span class="v">{{infoJoin(formData.info.mac)}}</span>
|
||||
<span class="offline-flag">{{macMatch ? $L('匹配') : $L('与本机不一致')}}</span>
|
||||
</li>
|
||||
<li class="offline-row">
|
||||
<em>{{$L('使用人数')}}:</em>
|
||||
<span class="v">{{formData.info.people || $L('无限制')}}({{$L('已使用')}} {{formData.user_count}})</span>
|
||||
</li>
|
||||
<li class="offline-row">
|
||||
<em>{{$L('到期时间')}}:</em>
|
||||
<span class="v">{{formData.info.expired_at || $L('永久')}}</span>
|
||||
</li>
|
||||
<!-- 低频字段折叠,默认更干净 -->
|
||||
<template v-if="offlineMore">
|
||||
<li class="offline-row"><em>IP:</em><span class="v">{{infoJoin(formData.info.ip)}}</span></li>
|
||||
<li class="offline-row"><em>{{$L('域名')}}:</em><span class="v">{{infoJoin(formData.info.domain)}}</span></li>
|
||||
<li class="offline-row"><em>{{$L('创建时间')}}:</em><span class="v">{{formData.info.created_at}}</span></li>
|
||||
<li class="offline-row"><em>{{$L('当前环境')}}:</em><span class="v">SN: {{formData.doo_sn}}, MAC: {{infoJoin(formData.macs)}}</span></li>
|
||||
</template>
|
||||
<li class="offline-more"><a @click="offlineMore = !offlineMore">{{offlineMore ? $L('收起') : $L('更多信息')}}</a></li>
|
||||
</ul>
|
||||
<ul v-else>
|
||||
<li>
|
||||
{{$L('加载中...')}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('提示')" v-if="formData.error?.length > 0">
|
||||
<div class="license-box">
|
||||
<ul>
|
||||
<li v-for="(tip, ti) in formData.error" :key="ti" class="warning">{{tip}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
</div>
|
||||
<div class="setting-footer">
|
||||
<template v-if="onlineActive">
|
||||
<Button v-if="!offlineRebindShow" type="primary" @click="offlineRebindShow = true">{{$L('绑定离线 License')}}</Button>
|
||||
<template v-else>
|
||||
<Button :loading="loadIng > 0" type="primary" @click="offlineRebindSubmit">{{$L('提交')}}</Button>
|
||||
<Button :loading="loadIng > 0" @click="offlineRebindCancel">{{$L('取消')}}</Button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
|
||||
<Button :loading="loadIng > 0" @click="resetForm">{{$L('重置')}}</Button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
</div>
|
||||
<div class="setting-footer">
|
||||
<template v-if="onlineActive">
|
||||
<Button v-if="!offlineRebindShow" type="primary" @click="offlineRebindShow = true">{{$L('绑定离线 License')}}</Button>
|
||||
<template v-else>
|
||||
<Button :loading="loadIng > 0" type="primary" @click="offlineRebindSubmit">{{$L('提交')}}</Button>
|
||||
<Button :loading="loadIng > 0" @click="offlineRebindCancel">{{$L('取消')}}</Button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
|
||||
<Button :loading="loadIng > 0" @click="resetForm">{{$L('重置')}}</Button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</TabPane>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
@ -177,6 +170,11 @@
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.license-form {
|
||||
.ivu-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
.license-box {
|
||||
position: relative;
|
||||
padding-top: 6px;
|
||||
@ -194,8 +192,8 @@
|
||||
> li {
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
padding-bottom: 6px;
|
||||
line-height: 24px;
|
||||
padding-bottom: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@ -208,15 +206,6 @@
|
||||
font-style: normal;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.information {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 6px;
|
||||
&.error {
|
||||
color: #ed4014;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -264,6 +253,105 @@
|
||||
margin-top: 4px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
/* 在线授权:异常告警卡(正常态不渲染) */
|
||||
.online-alert {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
max-width: calc(100vw - 20px);
|
||||
margin: 0 auto 14px;
|
||||
padding: 11px 13px;
|
||||
border-radius: 6px;
|
||||
&.warning { background: #fdf6ec; border: 1px solid #faecd8; }
|
||||
&.error { background: #fef0ef; border: 1px solid #fcd7d3; }
|
||||
.online-alert-ico {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
&.warning .online-alert-ico { background: #ff9900; }
|
||||
&.error .online-alert-ico { background: #ed4014; }
|
||||
.online-alert-title { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
|
||||
&.warning .online-alert-title { color: #b8791b; }
|
||||
&.error .online-alert-title { color: #c0341a; }
|
||||
.online-alert-desc { font-size: 12px; line-height: 18px; }
|
||||
&.warning .online-alert-desc { color: #8a7455; }
|
||||
&.error .online-alert-desc { color: #97544a; }
|
||||
}
|
||||
/* 在线授权:状态圆点 */
|
||||
.online-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.online-status-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
background: #c5c8ce;
|
||||
}
|
||||
&.is-ok { color: #19be6b; .online-status-dot { background: #19be6b; } }
|
||||
&.is-warning { color: #ff9900; .online-status-dot { background: #ff9900; } }
|
||||
&.is-error { color: #ed4014; font-weight: 500; .online-status-dot { background: #ed4014; } }
|
||||
}
|
||||
/* 在线授权:诊断详情(仅 SN/MAC 不匹配时出现) */
|
||||
.online-diag {
|
||||
max-width: calc(100vw - 20px);
|
||||
margin: 10px auto 0;
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
background: #f8f9fb;
|
||||
border-radius: 6px;
|
||||
.online-diag-title { font-size: 12px; color: #808695; margin-bottom: 6px; }
|
||||
.online-diag-row {
|
||||
font-size: 12.5px;
|
||||
line-height: 20px;
|
||||
display: flex;
|
||||
> em { font-style: normal; opacity: 0.55; width: 72px; flex-shrink: 0; }
|
||||
b { font-weight: 500; }
|
||||
&.bad { color: #ed4014; > em { opacity: 0.7; } }
|
||||
}
|
||||
}
|
||||
/* 离线授权:详情行 + 匹配标签 */
|
||||
.offline-detail {
|
||||
.offline-row {
|
||||
/* 文字排版(字号/行高/label/gap)沿用默认信息行,只保留标红块所需的内边距与圆角 */
|
||||
padding: 2px 0;
|
||||
border-radius: 5px;
|
||||
> .v { flex: 1; word-break: break-all; }
|
||||
.offline-flag {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 9px;
|
||||
background: #e8f7f0;
|
||||
color: #19be6b;
|
||||
}
|
||||
&.bad {
|
||||
color: #ed4014;
|
||||
.offline-flag { background: #fdecea; color: #ed4014; }
|
||||
}
|
||||
}
|
||||
.offline-more {
|
||||
padding: 4px 0;
|
||||
a {
|
||||
font-size: 13px;
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
&:hover { text-decoration: underline; }
|
||||
}
|
||||
}
|
||||
}
|
||||
body.window-portrait {
|
||||
.license-box {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
@ -287,7 +375,7 @@ export default {
|
||||
tabInited: false,
|
||||
offlineRebindShow: false,
|
||||
offlineRebindLicense: '',
|
||||
onlineIng: 0,
|
||||
offlineMore: false, // 离线详情:是否展开 IP/域名/创建时间/当前环境等低频字段
|
||||
onlineAction: '', // 当前进行中的在线操作:'' | 'login' | 'trial' | 'logout',用于按钮级 loading/禁用互斥
|
||||
onlineForm: {
|
||||
email: '',
|
||||
@ -351,6 +439,82 @@ export default {
|
||||
const macOk = this.existIntersection(this.formData.macs, info.mac);
|
||||
return !isTrialThree && snOk && macOk;
|
||||
},
|
||||
|
||||
// license 内嵌 SN/MAC 与本机是否匹配(在线/离线同源判断);info 未加载时视为匹配,避免误报
|
||||
snMatch() {
|
||||
const info = this.formData.info || {};
|
||||
return !info.sn || this.existIntersection(this.formData.doo_sn, info.sn);
|
||||
},
|
||||
macMatch() {
|
||||
const info = this.formData.info || {};
|
||||
return !info.mac || this.existIntersection(this.formData.macs, info.mac);
|
||||
},
|
||||
// 在线授权:license 已加载且 SN 或 MAC 与本机不一致(换机 / 网卡变化)
|
||||
onlineMismatch() {
|
||||
const info = this.formData.info || {};
|
||||
return !!info.sn && (!this.snMatch || !this.macMatch);
|
||||
},
|
||||
// 在线授权健康度:ok(绿)/ warning(黄)/ error(红),驱动状态点与告警卡配色
|
||||
onlineHealth() {
|
||||
const s = this.online.status;
|
||||
if (s === 'revoked' || s === 'frozen' || !this.snMatch) {
|
||||
return 'error';
|
||||
}
|
||||
if (s === 'reminder' || !this.macMatch) {
|
||||
return 'warning';
|
||||
}
|
||||
return 'ok';
|
||||
},
|
||||
// 在线授权异常告警卡内容:正常态返回 null(不显示)
|
||||
onlineAlert() {
|
||||
if (!this.onlineActive) {
|
||||
return null;
|
||||
}
|
||||
const s = this.online.status;
|
||||
// 设备标识(SN)变更:换机,必须重新登录
|
||||
if (this.onlineMismatch && !this.snMatch) {
|
||||
return {
|
||||
type: 'error',
|
||||
title: this.$L('授权与当前设备不匹配'),
|
||||
desc: this.$L('检测到设备标识(SN)已变更,在线授权可能已失效。请重新登录授权,或先在原设备退出以释放座位。'),
|
||||
relogin: true,
|
||||
};
|
||||
}
|
||||
if (s === 'frozen') {
|
||||
return {
|
||||
type: 'error',
|
||||
title: this.$L('在线授权已过期'),
|
||||
desc: this.$L('新增用户已受限,请尽快联网以自动续期恢复。'),
|
||||
relogin: false,
|
||||
};
|
||||
}
|
||||
// 仅网卡(MAC)变化:会随下次续期自动恢复
|
||||
if (this.onlineMismatch && !this.macMatch) {
|
||||
return {
|
||||
type: 'warning',
|
||||
title: this.$L('检测到网卡(MAC)变化'),
|
||||
desc: this.$L('系统会在下次续期时自动恢复授权,通常无需处理。'),
|
||||
relogin: false,
|
||||
};
|
||||
}
|
||||
if (s === 'reminder') {
|
||||
if (this.online.error_count > 0) {
|
||||
return {
|
||||
type: 'warning',
|
||||
title: this.$L('续期失败,请检查网络'),
|
||||
desc: this.$L('授权仍然有效,联网后会自动续期恢复。'),
|
||||
relogin: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: 'warning',
|
||||
title: this.$L('授权即将到期'),
|
||||
desc: this.$L('请保持联网,系统会自动为你续期。'),
|
||||
relogin: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submitForm() {
|
||||
@ -510,13 +674,12 @@ export default {
|
||||
return {
|
||||
active: this.$L('生效中'),
|
||||
reminder: this.$L('即将到期'),
|
||||
frozen: this.$L('已冻结'),
|
||||
frozen: this.$L('已过期'),
|
||||
revoked: this.$L('已吊销'),
|
||||
}[status] || status || '-';
|
||||
},
|
||||
|
||||
onlineCall(url, data, successMsg) {
|
||||
this.onlineIng++;
|
||||
return this.$store.dispatch("call", {
|
||||
url,
|
||||
data,
|
||||
@ -529,8 +692,6 @@ export default {
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg);
|
||||
return Promise.reject(msg);
|
||||
}).finally(_ => {
|
||||
this.onlineIng--;
|
||||
});
|
||||
},
|
||||
|
||||
@ -636,6 +797,24 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 重新登录授权(换机 / 设备变更后):释放当前座位并回到登录表单
|
||||
onlineRelogin() {
|
||||
$A.modalConfirm({
|
||||
title: '重新登录授权',
|
||||
content: '将释放当前设备占用的授权座位并回到登录,确定继续?',
|
||||
onOk: () => {
|
||||
this.onlineAction = 'relogin';
|
||||
this.onlineCall('license/logout', {}, '').then(_ => {
|
||||
this.systemSetting();
|
||||
}).catch(() => {
|
||||
// 失败提示已由 onlineCall 弹出
|
||||
}).finally(() => {
|
||||
this.onlineAction = '';
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
startCodeCountdown() {
|
||||
this.clearCodeTimer();
|
||||
this.codeCountdown = 60;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user