648 lines
21 KiB
Vue
648 lines
21 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 底部导航「我的」页:概览入口 +「关于」(应用名/当前版本/运行平台)。
|
||
* 主题配色与深色模式的具体设置已拆到 pages/theme/theme.vue(短地址 /theme),本页只做跳入入口。
|
||
* 权限沿用 DmsFile(见 routes.ts 的 PAGE_PERMISSIONS);useAuthGuard 兜冷启动直达。
|
||
* 版本号取 common/constant 的 APP_INFO(与 manifest/package 对齐);H5 无 plus.runtime.version。
|
||
*/
|
||
import { useLocale } from "uview-pro";
|
||
import { computed, ref } from "vue";
|
||
import { currentEndpoint, normalizeLanUrl, saveEndpoint, testEndpoint } from "@/common/apiEndpoint";
|
||
import { APP_INFO } from "@/common/constant";
|
||
import { useAuthGuard, useThemeGradient, useThemeTokens } from "@/composables";
|
||
import { usePermission } from "@/permission/permission-context";
|
||
import { pageUrl } from "@/router/routes";
|
||
import authService from "@/service/security/authService";
|
||
import { useLoginProfileStore } from "@/stores";
|
||
|
||
const { t } = useLocale();
|
||
|
||
useAuthGuard();
|
||
|
||
const loginProfileStore = useLoginProfileStore();
|
||
|
||
/* 代理登录入口显隐,口径必须与后端 /Auth/LoginAgent 的 [PermissionOperate(name:"SecurityUser", operate:"LoginAgent")] 完全一致:
|
||
后端 PermissionHandler 对非标准 operate 走 default 分支 → CanNone("SecurityUser","LoginAgent"),
|
||
即查「数据级授权」里有没有一条 PermissionName=SecurityUser 且 DependentDetailName=LoginAgent 的行(只看挂没挂,不看 Y/N)。
|
||
前端镜像就是 can("none","SecurityUser","LoginAgent")。之前误用 canNone("LoginAgent") 查的是功能级同名行,口径错位——
|
||
会出现「入口看得见、一点就 403」。改成同口径后:见得到 == 用得了。 */
|
||
const { can, canNone } = usePermission();
|
||
const canSeeLoginAgent = computed(() => can("none", "SecurityUser", "LoginAgent"));
|
||
|
||
/* 「安全管理」入口显隐:需 SecurityActor 功能权限(与 PAGE_PERMISSIONS.actorEnquiry 同一权限名)。
|
||
没授这个权限的用户看不到入口,也就不存在“进得去但里面全 403”的空壳页 */
|
||
const canSeeSecurity = computed(() => canNone("SecurityActor"));
|
||
|
||
/* 「已认证」判定:与后端 UpdateSecurityUserIdCard 口径一致——UserType=NORMAL_USERS("N") 即视为已认证(身份验证成功后本地快照同步) */
|
||
const isVerified = computed(() => loginProfileStore.userType === "N");
|
||
|
||
/* 统一风格:主题渐变底 + 主题色变量(与 family-person-enquiry / 族谱首页同一接线口径) */
|
||
const { themeBackground } = useThemeGradient();
|
||
const { primary, accentGradient, accentSoft, accentShadow } = useThemeTokens();
|
||
const themeVars = computed(() => ({
|
||
"--fg-primary": primary.value,
|
||
"--fg-gradient": accentGradient.value,
|
||
"--fg-soft": accentSoft.value,
|
||
"--fg-shadow": accentShadow.value,
|
||
}));
|
||
|
||
/** 进入「我的主题」页:theme 非 tab 页,用 navigateTo 保留历史以便返回 */
|
||
function goTheme(): void {
|
||
uni.navigateTo({ url: pageUrl("themePage") });
|
||
}
|
||
|
||
// ===== 访问线路(App 手动切换公网/内网直连;仅影响后续请求,不中断在途传输) =====
|
||
const linePopupVisible = ref(false);
|
||
/** 当前生效线路(保存后刷新展示) */
|
||
const lineSetting = ref(currentEndpoint());
|
||
/** 弹层草稿:确认前不动真实线路,取消即作废 */
|
||
const lineModeDraft = ref<"wan" | "lan">("wan");
|
||
const lineLanDraft = ref("");
|
||
const lineTesting = ref(false);
|
||
const lineTestResult = ref<"" | "ok" | "fail">("");
|
||
|
||
/** 当前线路展示文案:公网 / 内网直连(附地址) */
|
||
const currentLineLabel = computed(() => {
|
||
const setting = lineSetting.value;
|
||
return setting.mode === "lan" ? `${t("MinePage.lineLan")} · ${setting.lanUrl}` : t("MinePage.lineWan");
|
||
});
|
||
|
||
/** 打开线路弹层:草稿回填当前设置,清掉上次测活结果 */
|
||
function openLinePopup(): void {
|
||
const setting = currentEndpoint();
|
||
lineModeDraft.value = setting.mode;
|
||
lineLanDraft.value = setting.lanUrl;
|
||
lineTestResult.value = "";
|
||
linePopupVisible.value = true;
|
||
}
|
||
|
||
/** 测试内网地址连通性:用 RSA 公钥接口探活(与登录首调同源) */
|
||
async function handleTestLine(): Promise<void> {
|
||
if (lineTesting.value) {
|
||
return;
|
||
}
|
||
const url = normalizeLanUrl(lineLanDraft.value);
|
||
if (!url) {
|
||
uni.showToast({ title: t("MinePage.lineInvalid"), icon: "none" });
|
||
return;
|
||
}
|
||
lineTesting.value = true;
|
||
lineTestResult.value = "";
|
||
const ok = await testEndpoint(url);
|
||
lineTesting.value = false;
|
||
lineTestResult.value = ok ? "ok" : "fail";
|
||
if (!ok) {
|
||
uni.showToast({ title: t("MinePage.lineTestFail"), icon: "none" });
|
||
}
|
||
}
|
||
|
||
/** 保存线路:内网模式地址必填且合法;生效只影响后续请求,各页切回时自然按新线路拉数据 */
|
||
function handleSaveLine(): void {
|
||
if (lineModeDraft.value === "lan" && !normalizeLanUrl(lineLanDraft.value)) {
|
||
uni.showToast({ title: t("MinePage.lineInvalid"), icon: "none" });
|
||
return;
|
||
}
|
||
saveEndpoint({ mode: lineModeDraft.value, lanUrl: lineLanDraft.value });
|
||
lineSetting.value = currentEndpoint();
|
||
linePopupVisible.value = false;
|
||
uni.showToast({ title: t("MinePage.lineSaved"), icon: "none" });
|
||
}
|
||
|
||
/** 进入「安全管理」枢纽页:权限体系等基础设定的聚合入口 */
|
||
function goSecurity(): void {
|
||
uni.navigateTo({ url: pageUrl("securityPage") });
|
||
}
|
||
|
||
/** 进入「代理登录」页(/login-agent):管理员以被代理号身份切换会话 */
|
||
function goLoginAgent(): void {
|
||
uni.navigateTo({ url: pageUrl("loginAgent") });
|
||
}
|
||
|
||
/** 进入「修改密码」页(/change-password):当前登录用户改密,成功后更新档案保持登录 */
|
||
function goChangePassword(): void {
|
||
uni.navigateTo({ url: pageUrl("changePassword") });
|
||
}
|
||
|
||
/** 进入「修改昵称」页(/change-local-name):改用户名,成功后按旧系统约定退出重登刷新快照;已认证用户昵称锁定为身份证姓名,单击无效(改由右侧直接展示昵称) */
|
||
function goChangeLocalName(): void {
|
||
if (isVerified.value) {
|
||
return;
|
||
}
|
||
uni.navigateTo({ url: pageUrl("changeLocalName") });
|
||
}
|
||
|
||
/** 进入「身份验证」页(/identity-verification):上传身份证 OCR 后把信息写入备注并置认证态;已认证后不再重复认证,单击无效 */
|
||
function goIdentityVerification(): void {
|
||
if (isVerified.value) {
|
||
return;
|
||
}
|
||
uni.navigateTo({ url: pageUrl("identityVerification") });
|
||
}
|
||
|
||
/** 退出登录:确认后调后端 Logout、清本地档案,回登录页 */
|
||
async function handleLogout(): Promise<void> {
|
||
const confirmed = await new Promise<boolean>((resolve) => {
|
||
uni.showModal({
|
||
title: t("MinePage.logoutConfirmTitle"),
|
||
content: t("MinePage.logoutConfirmContent"),
|
||
success: res => resolve(!!res.confirm),
|
||
fail: () => resolve(false),
|
||
});
|
||
});
|
||
if (!confirmed) {
|
||
return;
|
||
}
|
||
try {
|
||
await authService.Logout();
|
||
}
|
||
catch {
|
||
// 后端退出失败也要清本地态,避免卡在已登录的假象
|
||
}
|
||
loginProfileStore.clearStorage();
|
||
uni.showToast({ title: t("MinePage.logoutSuccess"), icon: "none" });
|
||
// mine 是 tab 页,用 reLaunch 清栈回登录页,避免返回键又回到「我的」
|
||
setTimeout(() => uni.reLaunch({ url: pageUrl("login") }), 800);
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 统一风格:隐藏旧 u-navbar,改用与其他 tab 页同款透明 page-navbar 坐在主题渐变底上 -->
|
||
<app-page :hide-nav="true" :show-tabbar="true">
|
||
<view class="mine-page" :style="[{ background: themeBackground }, themeVars]">
|
||
<page-navbar :title="t('MinePage.title')" :show-back="false" :show-transfer="false" :show-quick-toggle="false"
|
||
:show-search-toggle="false" />
|
||
<view class="mine-page__body">
|
||
<!-- 我的主题:进入主题配色 / 深色模式设置页(/theme) -->
|
||
<view class="mine-cell" @tap="goTheme">
|
||
<view class="mine-cell__icon">
|
||
<u-icon name="setting-fill" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ t("MinePage.myTheme") }}
|
||
</text>
|
||
<u-icon name="arrow-right" size="32" color="#9aa0a6" />
|
||
</view>
|
||
|
||
<!-- 访问线路:App 手动切换公网/内网直连(资源地址由后端按请求 Host 自适应,仅 App 展示) -->
|
||
<!-- #ifdef APP-PLUS -->
|
||
<view class="mine-cell" @tap="openLinePopup">
|
||
<view class="mine-cell__icon">
|
||
<u-icon name="server-man" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ t("MinePage.accessLine") }}
|
||
</text>
|
||
<text class="mine-cell__value">
|
||
{{ currentLineLabel }}
|
||
</text>
|
||
<u-icon name="arrow-right" size="32" color="#9aa0a6" />
|
||
</view>
|
||
<!-- #endif -->
|
||
|
||
<!-- 安全管理:进入权限体系基础设定聚合页(/security);仅授 SecurityActor 功能者可见 -->
|
||
<view v-if="canSeeSecurity" class="mine-cell" @tap="goSecurity">
|
||
<view class="mine-cell__icon">
|
||
<u-icon name="lock-fill" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ t("MinePage.mySecurity") }}
|
||
</text>
|
||
<u-icon name="arrow-right" size="32" color="#9aa0a6" />
|
||
</view>
|
||
|
||
<!-- 代理登录:管理员以被代理号身份切换会话(/login-agent);仅授予 LoginAgent 功能者可见 -->
|
||
<view v-if="canSeeLoginAgent" class="mine-cell" @tap="goLoginAgent">
|
||
<view class="mine-cell__icon">
|
||
<u-icon name="account-fill" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ t("MinePage.loginAgent") }}
|
||
</text>
|
||
<u-icon name="arrow-right" size="32" color="#9aa0a6" />
|
||
</view>
|
||
|
||
<!-- 修改密码:当前登录用户改密(/change-password) -->
|
||
<view class="mine-cell" @tap="goChangePassword">
|
||
<view class="mine-cell__icon">
|
||
<u-icon name="lock-fill" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ t("MinePage.changePassword") }}
|
||
</text>
|
||
<u-icon name="arrow-right" size="32" color="#9aa0a6" />
|
||
</view>
|
||
|
||
<!-- 修改昵称:改用户名(/change-local-name);已认证则锁定、改名为「认证昵称」并直接展示昵称 -->
|
||
<view class="mine-cell" @tap="goChangeLocalName">
|
||
<view class="mine-cell__icon">
|
||
<u-icon name="edit-pen-fill" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ isVerified ? t("MinePage.verifiedLocalName") : t("MinePage.changeLocalName") }}
|
||
</text>
|
||
<text v-if="isVerified" class="mine-cell__value">
|
||
{{ loginProfileStore.userLocalName }}
|
||
</text>
|
||
<u-icon v-else name="arrow-right" size="32" color="#9aa0a6" />
|
||
</view>
|
||
|
||
<!-- 身份验证:上传身份证 OCR 后写入备注并置认证态(/identity-verification);已认证则右侧换为徽标 -->
|
||
<view class="mine-cell" @tap="goIdentityVerification">
|
||
<view class="mine-cell__icon">
|
||
<u-icon name="checkmark-circle-fill" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ t("MinePage.identityVerification") }}
|
||
</text>
|
||
<text v-if="isVerified" class="mine-cell__tag">
|
||
{{ t("MinePage.verified") }}
|
||
</text>
|
||
<u-icon v-else name="arrow-right" size="32" color="#9aa0a6" />
|
||
</view>
|
||
|
||
<!-- 退出登录:确认后清会话回登录页 -->
|
||
<view class="mine-cell mine-cell--logout" @tap="handleLogout">
|
||
<view class="mine-cell__icon mine-cell__icon--logout">
|
||
<u-icon name="close-circle-fill" size="30" color="#ffffff" />
|
||
</view>
|
||
<text class="mine-cell__title">
|
||
{{ t("MinePage.logout") }}
|
||
</text>
|
||
</view>
|
||
|
||
<!-- 关于:应用名 + 当前版本 + 运行平台 -->
|
||
<view class="mine-card">
|
||
<text class="mine-card__title">
|
||
{{ t("MinePage.aboutTitle") }}
|
||
</text>
|
||
<view class="mine-about__row">
|
||
<text class="mine-about__label">
|
||
{{ t("MinePage.appName") }}
|
||
</text>
|
||
<text class="mine-about__value">
|
||
{{ APP_INFO.name }}
|
||
</text>
|
||
</view>
|
||
<view class="mine-about__row">
|
||
<text class="mine-about__label">
|
||
{{ t("MinePage.versionLabel") }}
|
||
</text>
|
||
<text class="mine-about__value">
|
||
v{{ APP_INFO.version }}
|
||
</text>
|
||
</view>
|
||
<view class="mine-about__row">
|
||
<text class="mine-about__label">
|
||
{{ t("MinePage.platformLabel") }}
|
||
</text>
|
||
<text class="mine-about__value">
|
||
{{ APP_INFO.platform }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 访问线路弹层:公网/内网单选 + 内网地址手填 + 测连通(仅 App 有入口) -->
|
||
<!-- #ifdef APP-PLUS -->
|
||
<u-popup :model-value="linePopupVisible" mode="bottom" round="24" @close="linePopupVisible = false">
|
||
<view class="line-sheet">
|
||
<text class="line-sheet__title">
|
||
{{ t("MinePage.accessLine") }}
|
||
</text>
|
||
<text class="line-sheet__hint">
|
||
{{ t("MinePage.lineHint") }}
|
||
</text>
|
||
|
||
<!-- 公网:默认线路,随时随地可用 -->
|
||
<view class="line-option" :class="{ 'line-option--active': lineModeDraft === 'wan' }"
|
||
@tap="lineModeDraft = 'wan'">
|
||
<view class="line-option__body">
|
||
<text class="line-option__name">
|
||
{{ t("MinePage.lineWan") }}
|
||
</text>
|
||
<text class="line-option__desc">
|
||
{{ t("MinePage.lineWanDesc") }}
|
||
</text>
|
||
</view>
|
||
<view class="line-option__radio" :class="{ 'line-option__radio--on': lineModeDraft === 'wan' }" />
|
||
</view>
|
||
|
||
<!-- 内网直连:需与服务器同局域网,地址手填 -->
|
||
<view class="line-option" :class="{ 'line-option--active': lineModeDraft === 'lan' }"
|
||
@tap="lineModeDraft = 'lan'">
|
||
<view class="line-option__body">
|
||
<text class="line-option__name">
|
||
{{ t("MinePage.lineLan") }}
|
||
</text>
|
||
<text class="line-option__desc">
|
||
{{ t("MinePage.lineLanDesc") }}
|
||
</text>
|
||
</view>
|
||
<view class="line-option__radio" :class="{ 'line-option__radio--on': lineModeDraft === 'lan' }" />
|
||
</view>
|
||
|
||
<!-- 内网地址:选中内网才展开;测连通用免鉴权的 RSA 公钥接口 -->
|
||
<view v-if="lineModeDraft === 'lan'" class="line-lan">
|
||
<input v-model="lineLanDraft" class="line-lan__input" :placeholder="t('MinePage.lineLanPlaceholder')"
|
||
placeholder-class="line-lan__ph">
|
||
<view class="line-lan__test" @tap="handleTestLine">
|
||
<text class="line-lan__test-text">
|
||
{{ lineTesting ? t("MinePage.lineTesting") : t("MinePage.lineTest") }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<text v-if="lineModeDraft === 'lan' && lineTestResult === 'ok'" class="line-lan__result line-lan__result--ok">
|
||
{{ t("MinePage.lineTestOk") }}
|
||
</text>
|
||
<text v-else-if="lineModeDraft === 'lan' && lineTestResult === 'fail'"
|
||
class="line-lan__result line-lan__result--fail">
|
||
{{ t("MinePage.lineTestFail") }}
|
||
</text>
|
||
|
||
<view class="line-sheet__actions">
|
||
<view class="line-sheet__btn line-sheet__btn--ghost" @tap="linePopupVisible = false">
|
||
<text class="line-sheet__btn-text line-sheet__btn-text--ghost">
|
||
{{ t("MinePage.lineCancel") }}
|
||
</text>
|
||
</view>
|
||
<view class="line-sheet__btn line-sheet__btn--primary" @tap="handleSaveLine">
|
||
<text class="line-sheet__btn-text">
|
||
{{ t("MinePage.lineSave") }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
<!-- #endif -->
|
||
</app-page>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.mine-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* 内容区:page-navbar 之下,卡片间距与页边距统一 */
|
||
.mine-page__body {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
padding: 0 24rpx 24rpx;
|
||
}
|
||
|
||
/* 白底圆角卡片:与文件页分区卡片同一风格语言(渐变底上轻阴影浮起) */
|
||
.mine-card {
|
||
padding: 32rpx;
|
||
border-radius: 24rpx;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 4rpx 16rpx rgba(31, 35, 41, 0.06);
|
||
}
|
||
|
||
.mine-card__title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #1f2329;
|
||
}
|
||
|
||
/* 可点的列表行:主题渐变图标块 + 标题左、箭头右,整行白底卡片 */
|
||
.mine-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 28rpx 32rpx;
|
||
border-radius: 24rpx;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 4rpx 16rpx rgba(31, 35, 41, 0.06);
|
||
}
|
||
|
||
.mine-cell__icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 18rpx;
|
||
background: var(--fg-gradient, linear-gradient(135deg, #ff9a3d, #ff7d1f));
|
||
box-shadow: var(--fg-shadow, 0 6rpx 16rpx rgba(255, 125, 31, 0.35));
|
||
}
|
||
|
||
.mine-cell__title {
|
||
flex: 1;
|
||
margin-left: 24rpx;
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #1f2329;
|
||
}
|
||
|
||
/* 已认证徽标:替换右箭头,绿色描边胶囊,与主题橙入口区分状态语义 */
|
||
.mine-cell__tag {
|
||
padding: 6rpx 20rpx;
|
||
border: 2rpx solid #52c41a;
|
||
border-radius: 999rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #52c41a;
|
||
}
|
||
|
||
/* 已认证锁定的展示值(如昵称):右对齐灰字省略,替代不可点的箭头 */
|
||
.mine-cell__value {
|
||
max-width: 60%;
|
||
overflow: hidden;
|
||
font-size: 28rpx;
|
||
color: #8f959e;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* 退出登录:降权为描边行,标题居中、危险色,与普通入口区分 */
|
||
.mine-cell--logout {
|
||
justify-content: center;
|
||
}
|
||
|
||
.mine-cell__icon--logout {
|
||
background: linear-gradient(135deg, #ff7a7a, #fa3534);
|
||
box-shadow: 0 6rpx 16rpx rgba(250, 53, 52, 0.35);
|
||
}
|
||
|
||
.mine-cell--logout .mine-cell__title {
|
||
flex: none;
|
||
margin-left: 24rpx;
|
||
color: #fa3534;
|
||
}
|
||
|
||
/* 关于页行:标签左灰、值右深,横向两端对齐 */
|
||
.mine-about__row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-top: 24rpx;
|
||
}
|
||
|
||
.mine-about__label {
|
||
font-size: 26rpx;
|
||
color: #9aa0a6;
|
||
}
|
||
|
||
.mine-about__value {
|
||
font-size: 26rpx;
|
||
color: #1f2329;
|
||
}
|
||
|
||
/* ===== 访问线路弹层(底部抽屉,同 file-action-sheet 风格) ===== */
|
||
.line-sheet {
|
||
padding: 32rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
|
||
background-color: #f5f6f7;
|
||
}
|
||
|
||
.line-sheet__title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #1f2329;
|
||
}
|
||
|
||
.line-sheet__hint {
|
||
display: block;
|
||
margin-top: 8rpx;
|
||
font-size: 24rpx;
|
||
color: #9aa0a6;
|
||
}
|
||
|
||
/* 单选行:白卡;选中态用内阴影描边(不改变布局尺寸) */
|
||
.line-option {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-top: 20rpx;
|
||
padding: 24rpx;
|
||
border-radius: 20rpx;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.line-option--active {
|
||
box-shadow: 0 0 0 2rpx var(--fg-primary, #ff7d1f) inset;
|
||
}
|
||
|
||
.line-option__body {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
}
|
||
|
||
.line-option__name {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #1f2329;
|
||
}
|
||
|
||
.line-option__desc {
|
||
font-size: 24rpx;
|
||
color: #9aa0a6;
|
||
}
|
||
|
||
.line-option__radio {
|
||
box-sizing: border-box;
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
margin-left: 16rpx;
|
||
border: 2rpx solid #d4d7dc;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.line-option__radio--on {
|
||
border-color: var(--fg-primary, #ff7d1f);
|
||
background: radial-gradient(circle, var(--fg-primary, #ff7d1f) 0 42%, transparent 48%);
|
||
}
|
||
|
||
/* 内网地址:输入框 + 测试连接一行 */
|
||
.line-lan {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.line-lan__input {
|
||
flex: 1;
|
||
height: 76rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 16rpx;
|
||
font-size: 26rpx;
|
||
color: #1f2329;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.line-lan__ph {
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.line-lan__test {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 76rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 16rpx;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.line-lan__test-text {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: var(--fg-primary, #ff7d1f);
|
||
}
|
||
|
||
.line-lan__result {
|
||
display: block;
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.line-lan__result--ok {
|
||
color: #52c41a;
|
||
}
|
||
|
||
.line-lan__result--fail {
|
||
color: #fa3534;
|
||
}
|
||
|
||
/* 底部双按钮:取消(描边)/ 保存(主题渐变) */
|
||
.line-sheet__actions {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: 32rpx;
|
||
}
|
||
|
||
.line-sheet__btn {
|
||
display: flex;
|
||
flex: 1;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 88rpx;
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
.line-sheet__btn--ghost {
|
||
border: 2rpx solid #d4d7dc;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.line-sheet__btn--primary {
|
||
background: var(--fg-gradient, linear-gradient(135deg, #ff9a3d, #ff7d1f));
|
||
box-shadow: var(--fg-shadow, 0 6rpx 16rpx rgba(255, 125, 31, 0.35));
|
||
}
|
||
|
||
.line-sheet__btn-text {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.line-sheet__btn-text--ghost {
|
||
color: #4e5969;
|
||
}
|
||
</style>
|