可切換內網訪問
This commit is contained in:
1 parent
1ff69f3cb3
commit
fd67fa8f63
10 files changed
+493
-40
No files matched your search
@@ -4,6 +4,7 @@ using CloudBuilder.Core.DependencyInjection.Policy;
|
|||||||
using CloudBuilder.Core.DependencyInjection.Request;
|
using CloudBuilder.Core.DependencyInjection.Request;
|
||||||
using CloudBuilder.Core.Extensions;
|
using CloudBuilder.Core.Extensions;
|
||||||
using CloudBuilder.Core.Policy;
|
using CloudBuilder.Core.Policy;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System;
|
using System;
|
||||||
@@ -83,6 +84,30 @@ namespace CloudBuilder.Security.Policy
|
|||||||
if (!IsVideo(orderKey)) return null;
|
if (!IsVideo(orderKey)) return null;
|
||||||
return PackUrl(service, createdBy, orderKey, YesNoPolicy.YES, false);
|
return PackUrl(service, createdBy, orderKey, YesNoPolicy.YES, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 资源地址基座:优先按当前请求的 Scheme+Host 拼(App 切内网直连后,缩略图/原图/视频/头像
|
||||||
|
/// 返回的就是内网地址,资源自动跟随线路);无请求上下文(后台任务/启动阶段)回退 appsettings 的 ApplicationUrl。
|
||||||
|
/// 公网反代场景(nginx → Kestrel 是 http,对外是 https)不能直接用 request.Scheme:
|
||||||
|
/// 优先取 X-Forwarded-Proto,其次 Host 等于配置域名时沿用配置 scheme,其余(内网 IP/自定义域名)用请求 scheme。
|
||||||
|
/// </summary>
|
||||||
|
public static string BuildApplicationUrl(IApplicationService service)
|
||||||
|
{
|
||||||
|
string configured = service.Configuration[AppsettingsPolicy.ApplicationUrl] ?? "";
|
||||||
|
IHttpContextAccessor accessor = service.ServiceProvider.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
|
||||||
|
HttpRequest request = accessor?.HttpContext?.Request;
|
||||||
|
if (request == null || !request.Host.HasValue) return configured;
|
||||||
|
|
||||||
|
string scheme = request.Headers["X-Forwarded-Proto"].FirstOrDefault() ?? string.Empty;
|
||||||
|
if (string.IsNullOrEmpty(scheme))
|
||||||
|
{
|
||||||
|
bool sameAsConfigured = configured.StartsWith("https", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& Uri.TryCreate(configured, UriKind.Absolute, out Uri configuredUri)
|
||||||
|
&& string.Equals(configuredUri.Host, request.Host.Host, StringComparison.OrdinalIgnoreCase);
|
||||||
|
scheme = sameAsConfigured ? "https" : request.Scheme;
|
||||||
|
}
|
||||||
|
return scheme + "://" + request.Host;
|
||||||
|
}
|
||||||
public static string PackPicUrl(IApplicationService service, string createdBy, string orderKey)
|
public static string PackPicUrl(IApplicationService service, string createdBy, string orderKey)
|
||||||
{
|
{
|
||||||
return PackUrl(service, createdBy, orderKey, YesNoPolicy.YES, true);
|
return PackUrl(service, createdBy, orderKey, YesNoPolicy.YES, true);
|
||||||
@@ -107,14 +132,14 @@ namespace CloudBuilder.Security.Policy
|
|||||||
|
|
||||||
var filePathRel = Path.Combine(backup, createdBy, fileExtension.Replace(".", ""), "");
|
var filePathRel = Path.Combine(backup, createdBy, fileExtension.Replace(".", ""), "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
url = service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + orderKey;
|
url = BuildApplicationUrl(service) + webRootPath + filePath + orderKey;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var scale = (ServiceSettingsPolicy.GetDmsFileScaleDirectory(service) ?? "").Replace(phy ?? "", "");
|
var scale = (ServiceSettingsPolicy.GetDmsFileScaleDirectory(service) ?? "").Replace(phy ?? "", "");
|
||||||
var filePathRel = Path.Combine(scale, createdBy, fileExtension.Replace(".", ""), "");
|
var filePathRel = Path.Combine(scale, createdBy, fileExtension.Replace(".", ""), "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
url = service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + orderKey;
|
url = BuildApplicationUrl(service) + webRootPath + filePath + orderKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (GetType(orderKey!) == AUDIO)
|
else if (GetType(orderKey!) == AUDIO)
|
||||||
@@ -122,7 +147,7 @@ namespace CloudBuilder.Security.Policy
|
|||||||
var backup = (ServiceSettingsPolicy.GetDmsFileBackupDirectory(service) ?? "").Replace(phy ?? "", "");
|
var backup = (ServiceSettingsPolicy.GetDmsFileBackupDirectory(service) ?? "").Replace(phy ?? "", "");
|
||||||
var filePathRel = Path.Combine(backup, createdBy, fileExtension.Replace(".", ""), "");
|
var filePathRel = Path.Combine(backup, createdBy, fileExtension.Replace(".", ""), "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
url = service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + orderKey;
|
url = BuildApplicationUrl(service) + webRootPath + filePath + orderKey;
|
||||||
}
|
}
|
||||||
else if (GetType(orderKey!) == VIDEO)
|
else if (GetType(orderKey!) == VIDEO)
|
||||||
{
|
{
|
||||||
@@ -134,14 +159,14 @@ namespace CloudBuilder.Security.Policy
|
|||||||
var filePathRel = Path.Combine(upload, createdBy, fileExtension.Replace(".", ""), "");
|
var filePathRel = Path.Combine(upload, createdBy, fileExtension.Replace(".", ""), "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
// HH 才是 24 小时制:写成 hh 会让上午/下午的时间重复,缓存穿透失效
|
// HH 才是 24 小时制:写成 hh 会让上午/下午的时间重复,缓存穿透失效
|
||||||
url = service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + Path.GetFileNameWithoutExtension(orderKey) + JPG + "?v=" + DateTime.Now.ToString("yyyyMMddHHmmss");
|
url = BuildApplicationUrl(service) + webRootPath + filePath + Path.GetFileNameWithoutExtension(orderKey) + JPG + "?v=" + DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var backup = (ServiceSettingsPolicy.GetDmsFileBackupDirectory(service) ?? "").Replace(phy ?? "", "");
|
var backup = (ServiceSettingsPolicy.GetDmsFileBackupDirectory(service) ?? "").Replace(phy ?? "", "");
|
||||||
var filePathRel = Path.Combine(backup, createdBy, M3U8.Replace(".", ""), "");
|
var filePathRel = Path.Combine(backup, createdBy, M3U8.Replace(".", ""), "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
url = service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + Path.GetFileNameWithoutExtension(orderKey) + M3U8;
|
url = BuildApplicationUrl(service) + webRootPath + filePath + Path.GetFileNameWithoutExtension(orderKey) + M3U8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -149,7 +174,7 @@ namespace CloudBuilder.Security.Policy
|
|||||||
var backup = (ServiceSettingsPolicy.GetDmsFileBackupDirectory(service) ?? "").Replace(phy ?? "", "");
|
var backup = (ServiceSettingsPolicy.GetDmsFileBackupDirectory(service) ?? "").Replace(phy ?? "", "");
|
||||||
var filePathRel = Path.Combine(backup, createdBy, fileExtension.Replace(".", ""), "");
|
var filePathRel = Path.Combine(backup, createdBy, fileExtension.Replace(".", ""), "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
url = service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + orderKey;
|
url = BuildApplicationUrl(service) + webRootPath + filePath + orderKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (GetType(orderKey!) == LYRIC)
|
else if (GetType(orderKey!) == LYRIC)
|
||||||
@@ -157,7 +182,7 @@ namespace CloudBuilder.Security.Policy
|
|||||||
var upload = ServiceSettingsPolicy.GetDmsFileBackupDirectory(service).Replace(phy, "");
|
var upload = ServiceSettingsPolicy.GetDmsFileBackupDirectory(service).Replace(phy, "");
|
||||||
var filePathRel = Path.Combine(upload, createdBy, fileExtension.Replace(".", ""), "");
|
var filePathRel = Path.Combine(upload, createdBy, fileExtension.Replace(".", ""), "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
url = service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + orderKey;
|
url = BuildApplicationUrl(service) + webRootPath + filePath + orderKey;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@@ -178,9 +203,9 @@ namespace CloudBuilder.Security.Policy
|
|||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
if (YesNoPolicy.IsYes(transcodeIndc))
|
if (YesNoPolicy.IsYes(transcodeIndc))
|
||||||
{
|
{
|
||||||
return (service.Configuration[AppsettingsPolicy.ApplicationUrl] ?? "") + webRootPath + filePath + Path.GetFileNameWithoutExtension(orderKey) + M3U8;
|
return BuildApplicationUrl(service) + webRootPath + filePath + Path.GetFileNameWithoutExtension(orderKey) + M3U8;
|
||||||
}
|
}
|
||||||
return (service.Configuration[AppsettingsPolicy.ApplicationUrl] ?? "") + webRootPath + filePath + orderKey;
|
return BuildApplicationUrl(service) + webRootPath + filePath + orderKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string PackFilePath(IApplicationService service, string createdBy, string orderKey)
|
public static string PackFilePath(IApplicationService service, string createdBy, string orderKey)
|
||||||
@@ -208,7 +233,7 @@ namespace CloudBuilder.Security.Policy
|
|||||||
|
|
||||||
var filePathRel = Path.Combine(backup, folderName ?? "", fileExtension, "");
|
var filePathRel = Path.Combine(backup, folderName ?? "", fileExtension, "");
|
||||||
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
var filePath = filePathRel.Replace(Path.DirectorySeparatorChar, '/') + "/";
|
||||||
return service.Configuration[AppsettingsPolicy.ApplicationUrl] + webRootPath + filePath + fileName;
|
return BuildApplicationUrl(service) + webRootPath + filePath + fileName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"applicationUrl": "http://localhost:5235"
|
"applicationUrl": "http://172.16.253.53:5235"
|
||||||
},
|
},
|
||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"LogFileDirectory": "D:\\Net8\\FileServer\\Log",
|
"LogFileDirectory": "D:\\Net8\\FileServer\\Log",
|
||||||
"MaxFileSizeMB": 20
|
"MaxFileSizeMB": 20
|
||||||
},
|
},
|
||||||
"UrlOrigins": "http://192.168.195.68,http://localhost,http://localhost:8088,http://localhost:5173,http://localhost:5235", //这个参数是反向指向的地址,也是跨域访问的地址,包含Vue内WebSitePolicy设置WebSite
|
"UrlOrigins": "http://192.168.195.68,http://172.16.253.53:5235,http://localhost,http://localhost:8088,http://localhost:5173,http://localhost:5235", //这个参数是反向指向的地址,也是跨域访问的地址,包含Vue内WebSitePolicy设置WebSite
|
||||||
"ApplicationUrl": "http://localhost:5235", //這個地址是最外层用户访问的,不是反向指向的地址https://uat.youjiala.com
|
"ApplicationUrl": "http://localhost:5235", //這個地址是最外层用户访问的,不是反向指向的地址https://uat.youjiala.com
|
||||||
"CloudBuilder": {
|
"CloudBuilder": {
|
||||||
"Culture": "zh-cn",
|
"Culture": "zh-cn",
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { http } from "uview-pro";
|
||||||
|
import { WebSitePolicy } from "@/policy/custom/webSitePolicy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问线路(公网 / 内网直连)——App 手动切换,运行时即时生效。
|
||||||
|
*
|
||||||
|
* 背景:手机与服务器在同一局域网时,内网地址直连比公网域名快一个量级(飞牛 NAS 同款体验);
|
||||||
|
* 本项目只做「手动切换」:用户在「我的 → 访问线路」里选线路并填写内网地址,不做自动探测,
|
||||||
|
* 零探测开销、零误判。
|
||||||
|
*
|
||||||
|
* 生效范围:
|
||||||
|
* - 接口请求:uview-pro 每次请求时读 http 实例的 config.baseUrl,切换后新请求立即走新线路;
|
||||||
|
* - 资源地址:缩略图/原图/视频/头像由后端按请求 Host 动态拼(FileTypePolicy.BuildApplicationUrl),
|
||||||
|
* 切到内网后后端返回的 URL 自动变成内网地址,前端无需改写;
|
||||||
|
* - 进行中的上传:URL 在发起时已固定,切换不影响在途任务,后续任务走新线路。
|
||||||
|
*/
|
||||||
|
|
||||||
|
const STORAGE_KEY = "api-endpoint-setting";
|
||||||
|
|
||||||
|
/** 线路设置:mode=wan 走公网(默认);lan 走内网直连(lanUrl 必须是合法 http(s) 地址) */
|
||||||
|
export interface EndpointSetting {
|
||||||
|
mode: "wan" | "lan";
|
||||||
|
lanUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公网地址沿用原 baseUrl 口径:开发构建区分 App 真机连不上宿主机 localhost 的问题,发布构建用正式域名
|
||||||
|
let devBaseUrl = WebSitePolicy.WebApiRootDev;
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
devBaseUrl = WebSitePolicy.WebApiRootDevApp;
|
||||||
|
// #endif
|
||||||
|
/** 公网(默认)地址 */
|
||||||
|
const WAN_BASE_URL = import.meta.env.DEV
|
||||||
|
? devBaseUrl
|
||||||
|
: WebSitePolicy.WebApiRoot;
|
||||||
|
|
||||||
|
/** 规范化内网地址:允许省略 http://,去掉末尾斜杠;非法返回空串(调用方据此判无效) */
|
||||||
|
export function normalizeLanUrl(raw: string): string {
|
||||||
|
const text = String(raw ?? "")
|
||||||
|
.trim()
|
||||||
|
.replace(/\/+$/, "");
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const withScheme = /^https?:\/\//i.test(text) ? text : `http://${text}`;
|
||||||
|
return /^https?:\/\/[^\s/]+$/i.test(withScheme) ? withScheme : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function readSetting(): EndpointSetting {
|
||||||
|
try {
|
||||||
|
const raw = uni.getStorageSync(STORAGE_KEY) as
|
||||||
|
| Partial<EndpointSetting>
|
||||||
|
| "";
|
||||||
|
if (raw && typeof raw === "object") {
|
||||||
|
const lanUrl = normalizeLanUrl(String(raw.lanUrl ?? ""));
|
||||||
|
// 存储里有脏数据(lan 模式但地址非法)时按公网兜底,避免全站请求打到空地址
|
||||||
|
return { mode: raw.mode === "lan" && lanUrl ? "lan" : "wan", lanUrl };
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 存储读取失败按默认公网处理,不阻塞启动
|
||||||
|
}
|
||||||
|
return { mode: "wan", lanUrl: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模块级缓存:请求层高频读地址,不每次都走同步 storage
|
||||||
|
let cached: EndpointSetting = readSetting();
|
||||||
|
|
||||||
|
/** 当前线路设置(只读快照) */
|
||||||
|
export function currentEndpoint(): EndpointSetting {
|
||||||
|
return { ...cached };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前生效的后端地址:内网模式且地址合法时返回内网地址,否则公网 */
|
||||||
|
export function apiBaseUrl(): string {
|
||||||
|
return cached.mode === "lan" ? cached.lanUrl : WAN_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把线路写入请求层并落盘,返回生效后的地址 */
|
||||||
|
export function saveEndpoint(setting: EndpointSetting): string {
|
||||||
|
const lanUrl = normalizeLanUrl(setting.lanUrl);
|
||||||
|
cached = {
|
||||||
|
mode: setting.mode === "lan" && lanUrl ? "lan" : "wan",
|
||||||
|
lanUrl,
|
||||||
|
};
|
||||||
|
uni.setStorageSync(STORAGE_KEY, cached);
|
||||||
|
// uview-pro 的 http 实例每次请求读 config.baseUrl,改这里即对新请求生效
|
||||||
|
http.setConfig({ baseUrl: apiBaseUrl() });
|
||||||
|
return apiBaseUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 测试内网地址连通性:用免鉴权的 RSA 公钥接口探活(与登录首调同源,最接近真实可用性) */
|
||||||
|
export function testEndpoint(rawUrl: string): Promise<boolean> {
|
||||||
|
const url = normalizeLanUrl(rawUrl);
|
||||||
|
if (!url) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
uni.request({
|
||||||
|
url: `${url}/Auth/GetRsaPublicKey`,
|
||||||
|
method: "GET",
|
||||||
|
timeout: 3000,
|
||||||
|
success: (res) => resolve(res.statusCode >= 200 && res.statusCode < 300),
|
||||||
|
fail: () => resolve(false),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
} from "uview-pro";
|
} from "uview-pro";
|
||||||
import type { JwtInfo } from "@/data/auto/jwtInfo";
|
import type { JwtInfo } from "@/data/auto/jwtInfo";
|
||||||
import { http } from "uview-pro";
|
import { http } from "uview-pro";
|
||||||
|
import { apiBaseUrl } from "@/common/apiEndpoint";
|
||||||
import { markErrorNotified } from "@/common/global-error";
|
import { markErrorNotified } from "@/common/global-error";
|
||||||
import {
|
import {
|
||||||
currentUiLocale,
|
currentUiLocale,
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
} from "@/common/locale-const";
|
} from "@/common/locale-const";
|
||||||
import enUS from "@/locale/lang/en-US.json";
|
import enUS from "@/locale/lang/en-US.json";
|
||||||
import zhCN from "@/locale/lang/zh-CN.json";
|
import zhCN from "@/locale/lang/zh-CN.json";
|
||||||
import { WebSitePolicy } from "@/policy/custom/webSitePolicy";
|
|
||||||
import {
|
import {
|
||||||
currentRealPath,
|
currentRealPath,
|
||||||
errorPageUrl,
|
errorPageUrl,
|
||||||
@@ -37,18 +37,10 @@ import { copyIntoAppUploadDir } from "@/utils/appUploadFile";
|
|||||||
|
|
||||||
// ==================== 全局配置 ====================
|
// ==================== 全局配置 ====================
|
||||||
|
|
||||||
// 后端地址唯一来源:policy/custom/webSitePolicy(值可被项目根 .env 覆盖,缺失时回落该文件常量)
|
// 后端地址唯一来源:common/apiEndpoint(公网/内网线路可运行时切换,见「我的 → 访问线路」)。
|
||||||
// 与旧系统 WebVue 同逻辑:开发构建用 WebApiRootDev,发布构建用 WebApiRoot
|
// 原 baseUrl 常量已改为 apiBaseUrl():uview-pro 每次请求读 http 实例的 config.baseUrl,
|
||||||
// App 端开发构建(真机/模拟器)连不上宿主机的 localhost,改用 WebApiRootDevApp
|
// 切换线路后新请求立即生效;直接拼 URL 的调用方(登录页/上传队列)同步改用 apiBaseUrl()。
|
||||||
// 小程序端需在平台配置 https 合法域名;本地 H5 联调如遇 CORS 需后端放行开发端口
|
// 换环境只改 .env(详见 src/md/environment.md),本文件不允许写死地址
|
||||||
// 换环境只改 .env(详见 src/md/environment.md),本文件不允许再写死地址
|
|
||||||
let devBaseUrl = WebSitePolicy.WebApiRootDev;
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
devBaseUrl = WebSitePolicy.WebApiRootDevApp;
|
|
||||||
// #endif
|
|
||||||
export const baseUrl = import.meta.env.DEV
|
|
||||||
? devBaseUrl
|
|
||||||
: WebSitePolicy.WebApiRoot;
|
|
||||||
// 登录页真实路径:getCurrentPages() 拿到的 route 永远是 pages.json 里的 path,
|
// 登录页真实路径:getCurrentPages() 拿到的 route 永远是 pages.json 里的 path,
|
||||||
// 不含 H5 运行时补的短地址别名,故路径比较只能用真实路径,跳转 URL 才走 pageUrl()
|
// 不含 H5 运行时补的短地址别名,故路径比较只能用真实路径,跳转 URL 才走 pageUrl()
|
||||||
const loginPage = toRealPath("login");
|
const loginPage = toRealPath("login");
|
||||||
@@ -144,7 +136,7 @@ function doRefreshToken(): Promise<JwtInfo> {
|
|||||||
|
|
||||||
return new Promise<JwtInfo>((resolve, reject) => {
|
return new Promise<JwtInfo>((resolve, reject) => {
|
||||||
uni.request({
|
uni.request({
|
||||||
url: `${baseUrl}/Auth/RefreshToken`,
|
url: `${apiBaseUrl()}/Auth/RefreshToken`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data,
|
data,
|
||||||
timeout,
|
timeout,
|
||||||
@@ -293,7 +285,8 @@ export function extractBusinessError(data: any): string {
|
|||||||
// ==================== 全局配置(httpPlugin 注册用) ====================
|
// ==================== 全局配置(httpPlugin 注册用) ====================
|
||||||
|
|
||||||
const httpRequestConfig: RequestConfig = {
|
const httpRequestConfig: RequestConfig = {
|
||||||
baseUrl,
|
// 启动时的当前线路;运行中切换由 apiEndpoint.saveEndpoint 调 http.setConfig 覆盖
|
||||||
|
baseUrl: apiBaseUrl(),
|
||||||
header: {
|
header: {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
@@ -531,7 +524,7 @@ function sendByUniUpload(
|
|||||||
* 协议物理名再交给 uni.uploadFile(basename 即 filename,头像/身份证/网盘共用同一落盘口径)。 */
|
* 协议物理名再交给 uni.uploadFile(basename 即 filename,头像/身份证/网盘共用同一落盘口径)。 */
|
||||||
export function uploadFile(options: UploadOptions): Promise<any> {
|
export function uploadFile(options: UploadOptions): Promise<any> {
|
||||||
const { url, filePath, name = "file", fileName, formData, header = {} } = options;
|
const { url, filePath, name = "file", fileName, formData, header = {} } = options;
|
||||||
const fullUrl = url.indexOf("http") === 0 ? url : baseUrl + url;
|
const fullUrl = url.indexOf("http") === 0 ? url : apiBaseUrl() + url;
|
||||||
const auth = bearerAuth();
|
const auth = bearerAuth();
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
@@ -618,7 +611,7 @@ export async function requestBuffer(options: {
|
|||||||
data?: any;
|
data?: any;
|
||||||
}): Promise<ArrayBuffer> {
|
}): Promise<ArrayBuffer> {
|
||||||
const { url, method = "POST", data } = options;
|
const { url, method = "POST", data } = options;
|
||||||
const fullUrl = url.indexOf("http") === 0 ? url : baseUrl + url;
|
const fullUrl = url.indexOf("http") === 0 ? url : apiBaseUrl() + url;
|
||||||
const auth = bearerAuth();
|
const auth = bearerAuth();
|
||||||
|
|
||||||
const res = await new Promise<UniApp.RequestSuccessCallbackResult>(
|
const res = await new Promise<UniApp.RequestSuccessCallbackResult>(
|
||||||
|
|||||||
@@ -50,6 +50,21 @@
|
|||||||
"logoutConfirmContent": "Are you sure you want to log out?",
|
"logoutConfirmContent": "Are you sure you want to log out?",
|
||||||
"logoutSuccess": "Logged out",
|
"logoutSuccess": "Logged out",
|
||||||
"currentTheme": "Current Theme",
|
"currentTheme": "Current Theme",
|
||||||
|
"accessLine": "Access Line",
|
||||||
|
"lineHint": "Choose LAN Direct when your device is on the same network as the server for faster transfers",
|
||||||
|
"lineWan": "WAN",
|
||||||
|
"lineWanDesc": "Access via the public domain, available anywhere",
|
||||||
|
"lineLan": "LAN Direct",
|
||||||
|
"lineLanDesc": "Connect directly over the same Wi-Fi for faster speed",
|
||||||
|
"lineLanPlaceholder": "LAN address, e.g. http://192.168.1.10:5235",
|
||||||
|
"lineTest": "Test",
|
||||||
|
"lineTesting": "Testing…",
|
||||||
|
"lineTestOk": "Connected",
|
||||||
|
"lineTestFail": "Connection failed. Check the address and network",
|
||||||
|
"lineInvalid": "Invalid address format",
|
||||||
|
"lineCancel": "Cancel",
|
||||||
|
"lineSave": "Save",
|
||||||
|
"lineSaved": "Line switched",
|
||||||
"aboutTitle": "About",
|
"aboutTitle": "About",
|
||||||
"appName": "App",
|
"appName": "App",
|
||||||
"versionLabel": "Version",
|
"versionLabel": "Version",
|
||||||
|
|||||||
@@ -50,6 +50,21 @@
|
|||||||
"logoutConfirmContent": "确定要退出当前账号吗?",
|
"logoutConfirmContent": "确定要退出当前账号吗?",
|
||||||
"logoutSuccess": "已退出登录",
|
"logoutSuccess": "已退出登录",
|
||||||
"currentTheme": "当前主题",
|
"currentTheme": "当前主题",
|
||||||
|
"accessLine": "访问线路",
|
||||||
|
"lineHint": "与服务器在同一局域网时选内网直连,上传更快速",
|
||||||
|
"lineWan": "公网",
|
||||||
|
"lineWanDesc": "通过公网域名访问,随时随地可用",
|
||||||
|
"lineLan": "内网直连",
|
||||||
|
"lineLanDesc": "同一 Wi-Fi 下直连服务器内网地址,速度更快",
|
||||||
|
"lineLanPlaceholder": "内网地址,如 http://192.168.1.10:5235",
|
||||||
|
"lineTest": "测试连接",
|
||||||
|
"lineTesting": "测试中…",
|
||||||
|
"lineTestOk": "连接成功",
|
||||||
|
"lineTestFail": "连接失败,请检查地址与网络",
|
||||||
|
"lineInvalid": "地址格式不正确",
|
||||||
|
"lineCancel": "取消",
|
||||||
|
"lineSave": "保存",
|
||||||
|
"lineSaved": "已切换线路",
|
||||||
"aboutTitle": "关于",
|
"aboutTitle": "关于",
|
||||||
"appName": "应用名称",
|
"appName": "应用名称",
|
||||||
"versionLabel": "当前版本",
|
"versionLabel": "当前版本",
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import type { LoginProfile } from "@/data/auto/loginProfile";
|
|||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import { useLocale } from "uview-pro";
|
import { useLocale } from "uview-pro";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
import { apiBaseUrl } from "@/common/apiEndpoint";
|
||||||
import { brandGlyph, veinGlyph } from "@/common/brand";
|
import { brandGlyph, veinGlyph } from "@/common/brand";
|
||||||
import { backendLocale, baseUrl } from "@/common/http.interceptor";
|
import { backendLocale } from "@/common/http.interceptor";
|
||||||
import { DEFAULT_LOCALE, setCurrentUiLocale } from "@/common/locale-const";
|
import { DEFAULT_LOCALE, setCurrentUiLocale } from "@/common/locale-const";
|
||||||
import { encryptLoginPassword } from "@/common/pwd-crypto";
|
import { encryptLoginPassword } from "@/common/pwd-crypto";
|
||||||
import { useThemeGradient, useThemeTokens } from "@/composables";
|
import { useThemeGradient, useThemeTokens } from "@/composables";
|
||||||
@@ -65,6 +66,8 @@ setCurrentUiLocale(currentLocale.value?.name ?? DEFAULT_LOCALE);
|
|||||||
/** 非发布构建才显示「当前环境 + 实际调用的后端地址」:发布版 import.meta.env.PROD 为 true,整行编译期即被剔除 */
|
/** 非发布构建才显示「当前环境 + 实际调用的后端地址」:发布版 import.meta.env.PROD 为 true,整行编译期即被剔除 */
|
||||||
const isProd = import.meta.env.PROD;
|
const isProd = import.meta.env.PROD;
|
||||||
const envMode = import.meta.env.MODE;
|
const envMode = import.meta.env.MODE;
|
||||||
|
/** 当前后端地址:跟随「我的 → 访问线路」切换(内网直连时显示内网地址) */
|
||||||
|
const currentApiUrl = apiBaseUrl();
|
||||||
function toggleLang() {
|
function toggleLang() {
|
||||||
const next = isZh.value ? "en-US" : "zh-CN";
|
const next = isZh.value ? "en-US" : "zh-CN";
|
||||||
setLocale(next);
|
setLocale(next);
|
||||||
@@ -191,7 +194,7 @@ async function handleLogin() {
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!isProd" class="login-env">
|
<view v-if="!isProd" class="login-env">
|
||||||
{{ envMode }} · {{ baseUrl }}
|
{{ envMode }} · {{ currentApiUrl }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
* 版本号取 common/constant 的 APP_INFO(与 manifest/package 对齐);H5 无 plus.runtime.version。
|
* 版本号取 common/constant 的 APP_INFO(与 manifest/package 对齐);H5 无 plus.runtime.version。
|
||||||
*/
|
*/
|
||||||
import { useLocale } from "uview-pro";
|
import { useLocale } from "uview-pro";
|
||||||
import { computed } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
import { currentEndpoint, normalizeLanUrl, saveEndpoint, testEndpoint } from "@/common/apiEndpoint";
|
||||||
import { APP_INFO } from "@/common/constant";
|
import { APP_INFO } from "@/common/constant";
|
||||||
import { useAuthGuard, useThemeGradient, useThemeTokens } from "@/composables";
|
import { useAuthGuard, useThemeGradient, useThemeTokens } from "@/composables";
|
||||||
import { usePermission } from "@/permission/permission-context";
|
import { usePermission } from "@/permission/permission-context";
|
||||||
@@ -46,6 +47,63 @@ function goTheme(): void {
|
|||||||
uni.navigateTo({ url: pageUrl("themePage") });
|
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 {
|
function goSecurity(): void {
|
||||||
uni.navigateTo({ url: pageUrl("securityPage") });
|
uni.navigateTo({ url: pageUrl("securityPage") });
|
||||||
@@ -126,6 +184,22 @@ async function handleLogout(): Promise<void> {
|
|||||||
<u-icon name="arrow-right" size="32" color="#9aa0a6" />
|
<u-icon name="arrow-right" size="32" color="#9aa0a6" />
|
||||||
</view>
|
</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) -->
|
<!-- 安全管理:进入权限体系基础设定聚合页(/security) -->
|
||||||
<view class="mine-cell" @tap="goSecurity">
|
<view class="mine-cell" @tap="goSecurity">
|
||||||
<view class="mine-cell__icon">
|
<view class="mine-cell__icon">
|
||||||
@@ -240,6 +314,79 @@ async function handleLogout(): Promise<void> {
|
|||||||
</view>
|
</view>
|
||||||
</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>
|
</app-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -356,4 +503,157 @@ async function handleLogout(): Promise<void> {
|
|||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #1f2329;
|
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>
|
</style>
|
||||||
@@ -2,11 +2,8 @@ import type { PathNode } from "@/data/custom/fileBrowser";
|
|||||||
import type { PickedFile } from "@/utils/chooseFiles";
|
import type { PickedFile } from "@/utils/chooseFiles";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import {
|
import { apiBaseUrl } from "@/common/apiEndpoint";
|
||||||
baseUrl,
|
import { bearerAuth, extractBusinessError } from "@/common/http.interceptor";
|
||||||
bearerAuth,
|
|
||||||
extractBusinessError,
|
|
||||||
} from "@/common/http.interceptor";
|
|
||||||
import { copyIntoAppUploadDir } from "@/utils/appUploadFile";
|
import { copyIntoAppUploadDir } from "@/utils/appUploadFile";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,7 +84,7 @@ function uniqueUploadTag(): string {
|
|||||||
|
|
||||||
/** 分片大小:内网 3MB / 公网 1MB(旧系统 FileUpload 同口径;开发构建视为内网) */
|
/** 分片大小:内网 3MB / 公网 1MB(旧系统 FileUpload 同口径;开发构建视为内网) */
|
||||||
function resolveChunkSize(): number {
|
function resolveChunkSize(): number {
|
||||||
const publicNet = /localhost|127\.|192\.168\.|10\./.test(baseUrl);
|
const publicNet = /localhost|127\.|192\.168\.|10\./.test(apiBaseUrl());
|
||||||
const intranet = import.meta.env.DEV || publicNet;
|
const intranet = import.meta.env.DEV || publicNet;
|
||||||
return (intranet ? 3 : 1) * 1024 * 1024;
|
return (intranet ? 3 : 1) * 1024 * 1024;
|
||||||
}
|
}
|
||||||
@@ -233,7 +230,7 @@ export const useUploadQueueStore = defineStore(
|
|||||||
chunk,
|
chunk,
|
||||||
`${uid}-${index}-${total}${extOf(item.name)}`,
|
`${uid}-${index}-${total}${extOf(item.name)}`,
|
||||||
);
|
);
|
||||||
xhr.open("POST", `${baseUrl}/DmsFile/UploadDmsFile`);
|
xhr.open("POST", `${apiBaseUrl()}/DmsFile/UploadDmsFile`);
|
||||||
xhr.setRequestHeader("Authorization", bearerAuth());
|
xhr.setRequestHeader("Authorization", bearerAuth());
|
||||||
xhr.setRequestHeader("FilePath", item.targetPath);
|
xhr.setRequestHeader("FilePath", item.targetPath);
|
||||||
xhr.upload.onprogress = (e: ProgressEvent) => {
|
xhr.upload.onprogress = (e: ProgressEvent) => {
|
||||||
@@ -293,7 +290,7 @@ export const useUploadQueueStore = defineStore(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
task = uni.uploadFile({
|
task = uni.uploadFile({
|
||||||
url: `${baseUrl}/DmsFile/UploadDmsFile`,
|
url: `${apiBaseUrl()}/DmsFile/UploadDmsFile`,
|
||||||
filePath,
|
filePath,
|
||||||
// 后端约定:form 字段名 = 原始文件名
|
// 后端约定:form 字段名 = 原始文件名
|
||||||
name: item.name,
|
name: item.name,
|
||||||
|
|||||||
Reference in new issue
Block a user