下载安装包,有进度条,自动安装
This commit is contained in:
1 parent
181207fc7a
commit
d97a8884aa
14 files changed
+625
-180
No files matched your search
@@ -48,6 +48,38 @@ namespace CloudBuilder.Master.Service
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全系图下发瘦身(人员):只保留卡片渲染所需列(PersonId/Surname/GenealogyName/CommonName/Gender/AvatarUrl),
|
||||||
|
/// 其余可空列(履历/学历/O/OCR 临时字段等)连同已生成 AvatarUrl 后不再需要的 AvatarFilename 一律置空,
|
||||||
|
/// 由全局 NullValueHandling.Ignore 丢弃——大谱(数千族员)整谱响应体量得以大幅压缩,避免 H5 请求超时。
|
||||||
|
/// </summary>
|
||||||
|
private static void SlimPersonForChart(FamilyPersonDisplayData p)
|
||||||
|
{
|
||||||
|
if (p == null) return;
|
||||||
|
p.Alias = null; p.BindUserId = null; p.BirthDate = null; p.BirthOrder = null; p.BirthText = null;
|
||||||
|
p.DeathDate = null; p.DeathText = null; p.Duty = null; p.Education = null; p.FatherId = null;
|
||||||
|
p.GraduationStatus = null; p.Hao = null; p.Introduction = null; p.IsBindUser = null; p.Major = null;
|
||||||
|
p.MaritalStatus = null; p.MotherName = null; p.Privacy = null; p.School = null; p.Title = null; p.Zi = null;
|
||||||
|
p.Nation = null; p.Address = null; p.IdNumber = null; p.Issuer = null; p.Validity = null;
|
||||||
|
p.IdCardFilename = null; p.IdCardUrl = null;
|
||||||
|
p.AvatarFilename = null; p.IsCanAmend = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>全系图下发瘦身(配偶视图行):图只用双方姓名/序号,婚嫁/离异/备注等列置空丢弃(Gender 是拥有者本人性别,前端不用)</summary>
|
||||||
|
private static void SlimSpouseMapForChart(FamilyTreeSpouseMapViewEntity s)
|
||||||
|
{
|
||||||
|
if (s == null) return;
|
||||||
|
s.Gender = null; s.MarryDate = null; s.MarryDateText = null;
|
||||||
|
s.DivorceDate = null; s.DivorceDateText = null; s.MarryType = null; s.IsCurrent = null; s.Remark = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>全系图下发瘦身(亲子视图行):图只用于「父→子」拓扑与子女姓名,拥有者姓名/Gender/配偶 id/备注置空丢弃</summary>
|
||||||
|
private static void SlimChildMapForChart(FamilyTreeChildrenMapViewEntity c)
|
||||||
|
{
|
||||||
|
if (c == null) return;
|
||||||
|
c.Gender = null; c.CommonName = null; c.GenealogyName = null; c.SpouseId = null; c.Remark = null;
|
||||||
|
}
|
||||||
|
|
||||||
[PermissionOperate(name: MasterPermissionPolicy.FamilyTree, operate: Operate.Find)]
|
[PermissionOperate(name: MasterPermissionPolicy.FamilyTree, operate: Operate.Find)]
|
||||||
public FamilyTreeData FindAllByTreeId(string treeId)
|
public FamilyTreeData FindAllByTreeId(string treeId)
|
||||||
{
|
{
|
||||||
@@ -79,6 +111,10 @@ namespace CloudBuilder.Master.Service
|
|||||||
|
|
||||||
data.CurrentPerson.FamilyTreeChildrenMaps = repositoryChildrenMap.GetRepository<IRepository<FamilyTreeChildrenMapViewEntity>>().DetachedEntities.Where(x => x.TreeId == treeId).OrderBy(x => x.Orderby).ToArray();
|
data.CurrentPerson.FamilyTreeChildrenMaps = repositoryChildrenMap.GetRepository<IRepository<FamilyTreeChildrenMapViewEntity>>().DetachedEntities.Where(x => x.TreeId == treeId).OrderBy(x => x.Orderby).ToArray();
|
||||||
|
|
||||||
|
// 全系图一次性下发整谱两套关系视图,大谱体量惊人:只保留卡片渲染所需列,其余置空丢弃
|
||||||
|
foreach (var s in data.CurrentPerson.FamilyTreeSpouseMaps) SlimSpouseMapForChart(s);
|
||||||
|
foreach (var c in data.CurrentPerson.FamilyTreeChildrenMaps) SlimChildMapForChart(c);
|
||||||
|
|
||||||
List<string> ps = new List<string>();
|
List<string> ps = new List<string>();
|
||||||
ps.Add(d.AncestorPersonId);
|
ps.Add(d.AncestorPersonId);
|
||||||
if (data.CurrentPerson.FamilyTreeSpouseMaps != null && data.CurrentPerson.FamilyTreeSpouseMaps.Length > 0)
|
if (data.CurrentPerson.FamilyTreeSpouseMaps != null && data.CurrentPerson.FamilyTreeSpouseMaps.Length > 0)
|
||||||
@@ -96,6 +132,8 @@ namespace CloudBuilder.Master.Service
|
|||||||
{
|
{
|
||||||
data.CurrentPerson.FamilyTreePersons = families.Select(x => new FamilyPersonDisplayData(x)).ToArray();
|
data.CurrentPerson.FamilyTreePersons = families.Select(x => new FamilyPersonDisplayData(x)).ToArray();
|
||||||
FillAvatarUrl(data.CurrentPerson.FamilyTreePersons);
|
FillAvatarUrl(data.CurrentPerson.FamilyTreePersons);
|
||||||
|
// 头像 URL 已生成,整谱人员只留卡片渲染所需列,压掉其余可空列(在 FillAvatarUrl 之后做,勿丢 AvatarFilename 依赖)
|
||||||
|
foreach (var person in data.CurrentPerson.FamilyTreePersons) SlimPersonForChart(person);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_PublishTargetUrl>C:\inetpub\wwwroot\dockwebapi2</_PublishTargetUrl>
|
<_PublishTargetUrl>C:\inetpub\wwwroot\dockwebapi2</_PublishTargetUrl>
|
||||||
<History>True|2026-10-23T07:18:36.1507056Z||;True|2026-10-23T09:45:02.7814696+08:00||;True|2026-10-22T14:27:02.4892588+08:00||;True|2026-10-21T21:22:01.1456473+08:00||;True|2026-10-20T15:45:39.3393674+08:00||;True|2026-10-17T20:49:21.0401213+08:00||;True|2026-09-23T13:19:13.0148919+08:00||;True|2026-09-23T13:16:07.8584304+08:00||;True|2026-09-22T23:03:13.6509491+08:00||;True|2026-09-22T16:07:15.9177123+08:00||;True|2026-09-22T11:19:19.6087109+08:00||;True|2026-09-22T11:09:24.9165065+08:00||;True|2026-09-21T22:40:07.1232044+08:00||;True|2026-09-21T22:35:06.9446571+08:00||;True|2026-09-21T22:27:42.1146825+08:00||;False|2026-09-21T22:27:17.6066573+08:00||;True|2026-09-21T21:01:22.8888913+08:00||;True|2026-09-21T19:15:03.4753747+08:00||;True|2026-09-21T18:08:30.0866986+08:00||;True|2026-09-21T16:51:19.8137872+08:00||;True|2026-09-21T16:30:01.0950896+08:00||;True|2026-09-21T15:09:00.2326737+08:00||;True|2026-09-21T14:47:09.6120256+08:00||;True|2026-09-21T14:16:35.3194409+08:00||;True|2026-09-21T13:54:58.2829712+08:00||;True|2026-09-21T13:36:45.3134499+08:00||;True|2026-09-19T09:48:52.0085130+08:00||;False|2026-09-19T09:47:56.1658214+08:00||;True|2026-09-17T18:50:15.9493380+08:00||;True|2026-09-17T18:11:11.5364547+08:00||;True|2026-09-17T18:07:27.7830758+08:00||;True|2026-09-17T17:58:16.5798560+08:00||;True|2026-09-17T15:37:30.9025614+08:00||;True|2026-09-17T15:25:46.9116292+08:00||;True|2026-09-17T15:05:18.6592403+08:00||;True|2026-09-17T14:57:28.7997437+08:00||;True|2026-09-17T14:36:00.6339386+08:00||;True|2026-09-17T13:56:43.2376873+08:00||;True|2026-09-16T13:56:51.8724546+08:00||;True|2026-09-15T20:44:46.6559899+08:00||;True|2026-09-13T19:02:59.5654245+08:00||;True|2026-09-10T09:14:56.2914193+08:00||;True|2026-09-09T16:40:16.6461154+08:00||;True|2026-09-08T13:55:23.1502435+08:00||;True|2026-09-08T08:04:53.4279263+08:00||;True|2026-09-07T08:48:17.4682304+08:00||;True|2026-09-04T14:39:08.7878670+08:00||;True|2026-09-04T14:35:48.2937877+08:00||;True|2026-09-04T11:45:16.1372876+08:00||;True|2026-09-04T11:42:59.9356436+08:00||;True|2026-09-03T16:05:52.5270232+08:00||;True|2026-09-03T14:52:56.8559144+08:00||;True|2026-09-02T11:08:23.8720038+08:00||;True|2026-09-02T10:58:16.8081797+08:00||;True|2026-08-26T13:34:12.3805892+08:00||;True|2026-08-25T08:31:30.7787234+08:00||;True|2026-08-20T13:10:46.4725468+08:00||;True|2026-08-20T13:10:28.3142576+08:00||;True|2026-08-19T16:10:27.3362887+08:00||;True|2026-08-19T16:01:12.6234971+08:00||;True|2026-08-19T15:42:46.6576265+08:00||;True|2026-08-19T13:39:59.9363522+08:00||;True|2026-08-19T13:36:53.0379313+08:00||;True|2026-08-19T13:31:36.0841398+08:00||;True|2026-08-19T11:44:37.5319626+08:00||;True|2026-08-19T09:36:45.6000047+08:00||;True|2026-08-19T09:34:36.8468631+08:00||;True|2026-08-19T08:35:41.2202300+08:00||;True|2026-08-18T16:20:59.0170916+08:00||;</History>
|
<History>True|2026-10-26T01:48:34.0694643Z||;True|2026-10-23T16:26:07.0320450+08:00||;True|2026-10-23T15:18:36.1507056+08:00||;True|2026-10-23T09:45:02.7814696+08:00||;True|2026-10-22T14:27:02.4892588+08:00||;True|2026-10-21T21:22:01.1456473+08:00||;True|2026-10-20T15:45:39.3393674+08:00||;True|2026-10-17T20:49:21.0401213+08:00||;True|2026-09-23T13:19:13.0148919+08:00||;True|2026-09-23T13:16:07.8584304+08:00||;True|2026-09-22T23:03:13.6509491+08:00||;True|2026-09-22T16:07:15.9177123+08:00||;True|2026-09-22T11:19:19.6087109+08:00||;True|2026-09-22T11:09:24.9165065+08:00||;True|2026-09-21T22:40:07.1232044+08:00||;True|2026-09-21T22:35:06.9446571+08:00||;True|2026-09-21T22:27:42.1146825+08:00||;False|2026-09-21T22:27:17.6066573+08:00||;True|2026-09-21T21:01:22.8888913+08:00||;True|2026-09-21T19:15:03.4753747+08:00||;True|2026-09-21T18:08:30.0866986+08:00||;True|2026-09-21T16:51:19.8137872+08:00||;True|2026-09-21T16:30:01.0950896+08:00||;True|2026-09-21T15:09:00.2326737+08:00||;True|2026-09-21T14:47:09.6120256+08:00||;True|2026-09-21T14:16:35.3194409+08:00||;True|2026-09-21T13:54:58.2829712+08:00||;True|2026-09-21T13:36:45.3134499+08:00||;True|2026-09-19T09:48:52.0085130+08:00||;False|2026-09-19T09:47:56.1658214+08:00||;True|2026-09-17T18:50:15.9493380+08:00||;True|2026-09-17T18:11:11.5364547+08:00||;True|2026-09-17T18:07:27.7830758+08:00||;True|2026-09-17T17:58:16.5798560+08:00||;True|2026-09-17T15:37:30.9025614+08:00||;True|2026-09-17T15:25:46.9116292+08:00||;True|2026-09-17T15:05:18.6592403+08:00||;True|2026-09-17T14:57:28.7997437+08:00||;True|2026-09-17T14:36:00.6339386+08:00||;True|2026-09-17T13:56:43.2376873+08:00||;True|2026-09-16T13:56:51.8724546+08:00||;True|2026-09-15T20:44:46.6559899+08:00||;True|2026-09-13T19:02:59.5654245+08:00||;True|2026-09-10T09:14:56.2914193+08:00||;True|2026-09-09T16:40:16.6461154+08:00||;True|2026-09-08T13:55:23.1502435+08:00||;True|2026-09-08T08:04:53.4279263+08:00||;True|2026-09-07T08:48:17.4682304+08:00||;True|2026-09-04T14:39:08.7878670+08:00||;True|2026-09-04T14:35:48.2937877+08:00||;True|2026-09-04T11:45:16.1372876+08:00||;True|2026-09-04T11:42:59.9356436+08:00||;True|2026-09-03T16:05:52.5270232+08:00||;True|2026-09-03T14:52:56.8559144+08:00||;True|2026-09-02T11:08:23.8720038+08:00||;True|2026-09-02T10:58:16.8081797+08:00||;True|2026-08-26T13:34:12.3805892+08:00||;True|2026-08-25T08:31:30.7787234+08:00||;True|2026-08-20T13:10:46.4725468+08:00||;True|2026-08-20T13:10:28.3142576+08:00||;True|2026-08-19T16:10:27.3362887+08:00||;True|2026-08-19T16:01:12.6234971+08:00||;True|2026-08-19T15:42:46.6576265+08:00||;True|2026-08-19T13:39:59.9363522+08:00||;True|2026-08-19T13:36:53.0379313+08:00||;True|2026-08-19T13:31:36.0841398+08:00||;True|2026-08-19T11:44:37.5319626+08:00||;True|2026-08-19T09:36:45.6000047+08:00||;True|2026-08-19T09:34:36.8468631+08:00||;True|2026-08-19T08:35:41.2202300+08:00||;True|2026-08-18T16:20:59.0170916+08:00||;</History>
|
||||||
<LastFailureDetails />
|
<LastFailureDetails />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "youjiala",
|
"name": "youjiala",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"unconfig": "7.3.2"
|
"unconfig": "7.3.2"
|
||||||
|
|||||||
@@ -1,14 +1,145 @@
|
|||||||
<!--
|
<!--
|
||||||
根组件(由 vite.config.ts 的 UniRoot 插件包在所有页面外层)
|
根组件(由 vite.config.ts 的 UniRoot 插件包在所有页面外层)
|
||||||
只承担两件事:给 uview-pro 提供主题/多语言上下文,注册全局 u-toast。
|
承担三件事:给 uview-pro 提供主题/多语言上下文、注册全局 u-toast、
|
||||||
脚手架原有的 onLoad/onMounted 调试打印已移除:主题与语言的实际生效链路在
|
挂一个全局「App 自检更新下载进度条」遮罩(数据来自 utils/appUpdate 的 updateProgress,
|
||||||
main.ts(i18n + setTheme)与 theme.json 里,这里重复读取只会污染真机日志。
|
仅 App 下载整包时 visible=true,其它端/时机恒隐藏)。
|
||||||
-->
|
-->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { updateProgress } from "@/utils/appUpdate";
|
||||||
|
|
||||||
|
/** 无 Content-Length 时 percent<0:走不定量滑动动画,仍让用户看到「在下载」 */
|
||||||
|
const indeterminate = computed(() => updateProgress.percent < 0);
|
||||||
|
const fillWidth = computed(() =>
|
||||||
|
updateProgress.percent < 0 ? "40%" : `${updateProgress.percent}%`,
|
||||||
|
);
|
||||||
|
const percentLabel = computed(() =>
|
||||||
|
updateProgress.percent < 0 ? "" : `${updateProgress.percent}%`,
|
||||||
|
);
|
||||||
|
|
||||||
|
function toMB(bytes: number): string {
|
||||||
|
return (bytes / 1024 / 1024).toFixed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeLabel = computed(() => {
|
||||||
|
if (updateProgress.total > 0) {
|
||||||
|
return `${toMB(updateProgress.downloaded)} / ${toMB(updateProgress.total)} MB`;
|
||||||
|
}
|
||||||
|
if (updateProgress.downloaded > 0) {
|
||||||
|
return `${toMB(updateProgress.downloaded)} MB`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<u-config-provider>
|
<u-config-provider>
|
||||||
<slot />
|
<slot />
|
||||||
<u-toast global />
|
<u-toast global />
|
||||||
|
<!-- 下载/安装进度遮罩:仅在 updateProgress.visible 时出现(App 整包更新) -->
|
||||||
|
<view v-if="updateProgress.visible" class="upd-mask">
|
||||||
|
<view class="upd-card">
|
||||||
|
<text class="upd-title">
|
||||||
|
{{ updateProgress.title }}
|
||||||
|
</text>
|
||||||
|
<view class="upd-track">
|
||||||
|
<view
|
||||||
|
class="upd-fill"
|
||||||
|
:class="{ 'upd-fill--ind': indeterminate }"
|
||||||
|
:style="{ width: fillWidth }"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="upd-meta">
|
||||||
|
<text class="upd-size">
|
||||||
|
{{ sizeLabel }}
|
||||||
|
</text>
|
||||||
|
<text class="upd-percent">
|
||||||
|
{{ percentLabel }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</u-config-provider>
|
</u-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss"></style>
|
<style lang="scss" scoped>
|
||||||
|
.upd-mask {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10000;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upd-card {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 600rpx;
|
||||||
|
padding: 40rpx 40rpx 32rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upd-title {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upd-track {
|
||||||
|
position: relative;
|
||||||
|
height: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #eef0f3;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upd-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #16c3c9, #13c2c2);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
transition: width 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 不确定态:40% 宽的色块来回滑动,表示「正在下载但拿不到总大小」 */
|
||||||
|
.upd-fill--ind {
|
||||||
|
position: absolute;
|
||||||
|
animation: upd-ind 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upd-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upd-size {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #8f959e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upd-percent {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #13c2c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes upd-ind {
|
||||||
|
0% {
|
||||||
|
left: -40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,7 +5,7 @@ export const APP_INFO : {
|
|||||||
} = {
|
} = {
|
||||||
// 版本号需与 src/manifest.json 的 versionName / package.json 的 version 保持一致(改版本时三处同步)
|
// 版本号需与 src/manifest.json 的 versionName / package.json 的 version 保持一致(改版本时三处同步)
|
||||||
name: "有家相册",
|
name: "有家相册",
|
||||||
version: "1.0.1",
|
version: "1.0.2",
|
||||||
platform: getPlatform(),
|
platform: getPlatform(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,26 @@ function shouldNotify(text: string): boolean {
|
|||||||
/** 错误来源:运行时异常 / 未处理的 Promise 拒绝 */
|
/** 错误来源:运行时异常 / 未处理的 Promise 拒绝 */
|
||||||
export type GlobalErrorSource = "error" | "rejection";
|
export type GlobalErrorSource = "error" | "rejection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vue-router 导航竞态的可预期拒绝(非应用故障):H5 下 uni.reLaunch/navigateTo/redirectTo
|
||||||
|
* 返回的 promise,在「新导航取消旧导航 / 重复导航 / 重定向中止」时会 reject。
|
||||||
|
* 冷启动地址栏停在 "/" 时,守卫把它 reLaunch 去 /login 与首屏路由解析撞车最常见——
|
||||||
|
* 导航其实成功(人已落在登录页),只是被后一条导航抢先,前一条留下一个"cancelled"拒绝。
|
||||||
|
* 这类拒绝不该打扰用户:全局边界对它只留 console 痕迹、不弹「未捕获错误」。
|
||||||
|
* 文案取 uni 前缀后的 vue-router 原话,命中任一即判为良性。
|
||||||
|
*/
|
||||||
|
const BENIGN_ROUTER_PATTERNS = [
|
||||||
|
"Navigation cancelled",
|
||||||
|
"Navigation aborted",
|
||||||
|
"NavigationAborted",
|
||||||
|
"duplicated navigation",
|
||||||
|
"Redirected when going",
|
||||||
|
];
|
||||||
|
|
||||||
|
function isBenignRouterRejection(text: string): boolean {
|
||||||
|
return BENIGN_ROUTER_PATTERNS.some(pattern => text.includes(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
/** 开发期弹窗最长占用时间:过了这个点就允许下一条弹窗顶上 */
|
/** 开发期弹窗最长占用时间:过了这个点就允许下一条弹窗顶上 */
|
||||||
const MODAL_MAX_HOLD_MS = 5000;
|
const MODAL_MAX_HOLD_MS = 5000;
|
||||||
|
|
||||||
@@ -146,6 +166,10 @@ export function reportGlobalError(
|
|||||||
const text = describe(error);
|
const text = describe(error);
|
||||||
// 全局边界的职责就是留痕:console.error 在 no-console 配置里本就放行,无需 disable 注释
|
// 全局边界的职责就是留痕:console.error 在 no-console 配置里本就放行,无需 disable 注释
|
||||||
console.error(`[global:${source}]`, error);
|
console.error(`[global:${source}]`, error);
|
||||||
|
// vue-router 导航竞态的可预期拒绝:留了 console 痕迹即可,不打扰用户(详见 BENIGN_ROUTER_PATTERNS 注释)
|
||||||
|
if (source === "rejection" && isBenignRouterRejection(text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isNotified(error)) {
|
if (isNotified(error)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import type { FamilyTreeChildrenMapViewEntity } from "@/data/auto/familyTreeChil
|
|||||||
import type { FamilyTreeData } from "@/data/auto/familyTreeData";
|
import type { FamilyTreeData } from "@/data/auto/familyTreeData";
|
||||||
import type { FamilyTreeSpouseMapViewEntity } from "@/data/auto/familyTreeSpouseMapViewEntity";
|
import type { FamilyTreeSpouseMapViewEntity } from "@/data/auto/familyTreeSpouseMapViewEntity";
|
||||||
import { useLocale } from "uview-pro";
|
import { useLocale } from "uview-pro";
|
||||||
import { computed, provide, ref, watch } from "vue";
|
import { computed, onBeforeUnmount, provide, ref, watch } from "vue";
|
||||||
import { usePolicyOptions } from "@/composables/usePolicyOptions";
|
import { usePolicyOptions } from "@/composables/usePolicyOptions";
|
||||||
import familyTreeService from "@/service/master/familyTreeService";
|
import { request } from "@/utils/request";
|
||||||
import { FAMILY_TREE_CHART_API } from "./types";
|
import { FAMILY_TREE_CHART_API } from "./types";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -56,6 +56,10 @@ interface FullIndex {
|
|||||||
currentId: string;
|
currentId: string;
|
||||||
/** 主系链:父 PersonId -> 该父子女中位于「祖→当前族人」链上的那一个,排序时置顶(旧图主系居首同款) */
|
/** 主系链:父 PersonId -> 该父子女中位于「祖→当前族人」链上的那一个,排序时置顶(旧图主系居首同款) */
|
||||||
lineageChildOf: Map<string, string>;
|
lineageChildOf: Map<string, string>;
|
||||||
|
/** 主系脊(始祖→当前族人的直连祖先链,含两端):默认展开恒开,保证当前族人可达可见 */
|
||||||
|
spine: Set<string>;
|
||||||
|
/** 各人世代号(始祖=0,按亲子视图自顶向下 BFS):±5 代窗口据此判定 */
|
||||||
|
genOf: Map<string, number>;
|
||||||
}
|
}
|
||||||
const fullIndex = ref<FullIndex | null>(null);
|
const fullIndex = ref<FullIndex | null>(null);
|
||||||
|
|
||||||
@@ -250,6 +254,41 @@ function toggle(personId: string): void {
|
|||||||
expanded.value = { ...expanded.value, [personId]: !expanded.value[personId] };
|
expanded.value = { ...expanded.value, [personId]: !expanded.value[personId] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 全系图默认展开口径:主系脊恒开 + 当前族人 ±5 代窗口展开,其余折叠待点击懒展开 =====
|
||||||
|
const MAIN_LINE_UP = 5;
|
||||||
|
const MAIN_LINE_DOWN = 5;
|
||||||
|
/** 一键展开全部(大谱会变卡,由用户自担);关闭回到 ±5 代窗口默认 */
|
||||||
|
const expandAll = ref(false);
|
||||||
|
|
||||||
|
/** 依当前口径算展开集:expandAll=全展开;否则主系脊 ∪ 世代窗口内节点 */
|
||||||
|
function computeExpanded(): Record<string, boolean> {
|
||||||
|
const index = fullIndex.value;
|
||||||
|
const next: Record<string, boolean> = {};
|
||||||
|
if (!index) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
if (index.rootId) {
|
||||||
|
next[index.rootId] = true;
|
||||||
|
}
|
||||||
|
const curGen = index.genOf.get(index.currentId);
|
||||||
|
index.childrenOf.forEach((_list: any[], parentId: string) => {
|
||||||
|
if (expandAll.value || index.spine.has(parentId)) {
|
||||||
|
next[parentId] = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const g = index.genOf.get(parentId);
|
||||||
|
if (curGen !== undefined && g !== undefined && g >= curGen - MAIN_LINE_UP && g <= curGen + MAIN_LINE_DOWN) {
|
||||||
|
next[parentId] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleExpandAll(): void {
|
||||||
|
expandAll.value = !expandAll.value;
|
||||||
|
expanded.value = computeExpanded();
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 缩放与布局方向(弹窗右上控制条)=====
|
// ===== 缩放与布局方向(弹窗右上控制条)=====
|
||||||
const ZOOM_MIN = 0.5;
|
const ZOOM_MIN = 0.5;
|
||||||
const ZOOM_MAX = 2;
|
const ZOOM_MAX = 2;
|
||||||
@@ -295,7 +334,9 @@ function onCtrlTouchMove(e: TouchEvent): void {
|
|||||||
}
|
}
|
||||||
const sys = uni.getSystemInfoSync();
|
const sys = uni.getSystemInfoSync();
|
||||||
const barW = uni.upx2px(88);
|
const barW = uni.upx2px(88);
|
||||||
const barH = uni.upx2px(384); // 握把 + 4 动作钮各 76 + 分隔线
|
// 握把 + 动作钮各 76 + 分隔线;全系图多一枚「展开全部」钮,高度随之 +76
|
||||||
|
const btnCount = props.mode === "full" ? 5 : 4;
|
||||||
|
const barH = uni.upx2px(76 + btnCount * 76 + 4);
|
||||||
const edge = uni.upx2px(16);
|
const edge = uni.upx2px(16);
|
||||||
// 默认锚点(top/right 定位)换算成 left/top 基准,再按屏幕边界夹取
|
// 默认锚点(top/right 定位)换算成 left/top 基准,再按屏幕边界夹取
|
||||||
const baseLeft = sys.windowWidth - uni.upx2px(24) - barW;
|
const baseLeft = sys.windowWidth - uni.upx2px(24) - barW;
|
||||||
@@ -350,6 +391,93 @@ function onChartTouchEnd(e: TouchEvent): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== H5 鼠标拖拽平移:scroll-view 桌面端只能滚轮/触控板滚,按住拖动不动,补一套 mousedown→改 scroll-left/top =====
|
||||||
|
// 走受控 scroll-left/scroll-top(uni 官方属性,跨版本稳):拖动中改这两个 ref 让 scroll-view 滚,
|
||||||
|
// 非拖动的滚轮/触摸由 @scroll 回填 ref(panning 时不回填,避免和手打架)。
|
||||||
|
const scrollLeft = ref(0);
|
||||||
|
const scrollTop = ref(0);
|
||||||
|
const isPanning = ref(false);
|
||||||
|
let panning = false;
|
||||||
|
let panMoved = false;
|
||||||
|
let panStartX = 0;
|
||||||
|
let panStartY = 0;
|
||||||
|
let panStartLeft = 0;
|
||||||
|
let panStartTop = 0;
|
||||||
|
// 拖拽刚结束的时刻:node 用它丢弃「拖完松手那一下」误触发的 tap
|
||||||
|
let panEndedAt = 0;
|
||||||
|
|
||||||
|
function onPanStart(e: MouseEvent): void {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 只认鼠标左键;右键/中键交回浏览器,触摸平移走原生滚动
|
||||||
|
if (e.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
panning = true;
|
||||||
|
panMoved = false;
|
||||||
|
isPanning.value = true;
|
||||||
|
panStartX = e.clientX;
|
||||||
|
panStartY = e.clientY;
|
||||||
|
panStartLeft = scrollLeft.value;
|
||||||
|
panStartTop = scrollTop.value;
|
||||||
|
// 不能 preventDefault:它会取消后续原生 click,卡片 @tap 再也收不到(float-add-btn 同款教训);
|
||||||
|
// 文本选中与图片拖影改由 CSS user-select:none + -webkit-user-drag:none 拦
|
||||||
|
window.addEventListener("mousemove", onPanMove);
|
||||||
|
window.addEventListener("mouseup", onPanEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPanMove(e: MouseEvent): void {
|
||||||
|
if (!panning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dx = e.clientX - panStartX;
|
||||||
|
const dy = e.clientY - panStartY;
|
||||||
|
if (Math.abs(dx) > 4 || Math.abs(dy) > 4) {
|
||||||
|
panMoved = true;
|
||||||
|
}
|
||||||
|
// 抓住内容反向拖:鼠标右移→视口左移(scrollLeft 减小)
|
||||||
|
scrollLeft.value = Math.max(0, panStartLeft - dx);
|
||||||
|
scrollTop.value = Math.max(0, panStartTop - dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPanEnd(): void {
|
||||||
|
if (!panning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
panning = false;
|
||||||
|
isPanning.value = false;
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.removeEventListener("mousemove", onPanMove);
|
||||||
|
window.removeEventListener("mouseup", onPanEnd);
|
||||||
|
}
|
||||||
|
if (panMoved) {
|
||||||
|
panEndedAt = Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 滚轮/触摸滚动时把真实偏移同步回受控值(拖动中跳过,避免覆盖刚算出的目标位置) */
|
||||||
|
function onChartScroll(e: any): void {
|
||||||
|
if (panning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scrollLeft.value = e?.detail?.scrollLeft ?? scrollLeft.value;
|
||||||
|
scrollTop.value = e?.detail?.scrollTop ?? scrollTop.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 拖拽平移刚结束的一瞬为真:node 用它丢弃误触 tap */
|
||||||
|
function didPanDrag(): boolean {
|
||||||
|
return Date.now() - panEndedAt < 250;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽中途弹窗被关(组件卸载)也要摘掉 window 监听,避免泄漏与卸载后仍改 ref
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.removeEventListener("mousemove", onPanMove);
|
||||||
|
window.removeEventListener("mouseup", onPanEnd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function requestLoad(personId: string): void {
|
function requestLoad(personId: string): void {
|
||||||
emit("select-child", personId);
|
emit("select-child", personId);
|
||||||
}
|
}
|
||||||
@@ -366,6 +494,7 @@ provide(FAMILY_TREE_CHART_API, {
|
|||||||
requestPick,
|
requestPick,
|
||||||
isMale,
|
isMale,
|
||||||
vertical,
|
vertical,
|
||||||
|
didPanDrag,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 全树:从 FamilyTreeData 拍平三张索引表(照抄旧 buildIndex 的字段映射) */
|
/** 全树:从 FamilyTreeData 拍平三张索引表(照抄旧 buildIndex 的字段映射) */
|
||||||
@@ -450,7 +579,26 @@ function buildFullIndex(resp: FamilyTreeData): FullIndex {
|
|||||||
lineageChildOf.set(parentId, cursor);
|
lineageChildOf.set(parentId, cursor);
|
||||||
cursor = parentId;
|
cursor = parentId;
|
||||||
}
|
}
|
||||||
return { rootId, personMap, spousesOf, childrenOf, parentsOf, currentId, lineageChildOf };
|
// 主系脊:visited 即「当前族人 + 其历代父辈直至始祖」,默认展开据此恒开
|
||||||
|
const spine = new Set(visited);
|
||||||
|
|
||||||
|
// 世代号:自始祖按亲子视图 BFS(一人多父取首父世代,图以主承担父渲染子树,够用且稳)
|
||||||
|
const genOf = new Map<string, number>();
|
||||||
|
if (rootId) {
|
||||||
|
genOf.set(rootId, 0);
|
||||||
|
const queue: string[] = [rootId];
|
||||||
|
for (let i = 0; i < queue.length; i++) {
|
||||||
|
const id = queue[i];
|
||||||
|
const g = genOf.get(id) as number;
|
||||||
|
(childrenOf.get(id) ?? []).forEach((c: any) => {
|
||||||
|
if (!genOf.has(c.id)) {
|
||||||
|
genOf.set(c.id, g + 1);
|
||||||
|
queue.push(c.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { rootId, personMap, spousesOf, childrenOf, parentsOf, currentId, lineageChildOf, spine, genOf };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadFull(): Promise<void> {
|
async function loadFull(): Promise<void> {
|
||||||
@@ -461,7 +609,14 @@ async function loadFull(): Promise<void> {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const resp = await familyTreeService.FindAllByTreeId(treeId);
|
// 整谱一次性下发(大谱数千族员),体量远超常规接口,走生成 service 拿不到单次超时会被全局 20s 掐断
|
||||||
|
// 报「请求错误:未知」,故这里直连请求层并放宽到 90s(后端已同步瘦身,只下发卡片渲染所需列)
|
||||||
|
const resp = await request<FamilyTreeData>({
|
||||||
|
url: "/FamilyTree/FindAllByTreeId?treeId=" + treeId,
|
||||||
|
method: "post",
|
||||||
|
data: treeId,
|
||||||
|
timeout: 90000,
|
||||||
|
});
|
||||||
if (resp?.CurrentPerson) {
|
if (resp?.CurrentPerson) {
|
||||||
fullIndex.value = buildFullIndex(resp);
|
fullIndex.value = buildFullIndex(resp);
|
||||||
}
|
}
|
||||||
@@ -478,7 +633,7 @@ async function loadFull(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 弹窗打开:重置展开态到根节点;full 默认全树展开(旧祖谱系图同款),sub 仅展开根待懒加载 */
|
/** 弹窗打开:重置展开态;full 默认只铺主系脊+当前族人±5代窗口(旁系/更深世代折叠懒展开,可一键全展开),sub 仅展开根待懒加载 */
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(on) => {
|
(on) => {
|
||||||
@@ -487,16 +642,10 @@ watch(
|
|||||||
}
|
}
|
||||||
expanded.value = {};
|
expanded.value = {};
|
||||||
if (props.mode === "full") {
|
if (props.mode === "full") {
|
||||||
|
expandAll.value = false;
|
||||||
loadFull().then(() => {
|
loadFull().then(() => {
|
||||||
const next: Record<string, boolean> = {};
|
// 默认只铺主系脊与当前族人 ±5 代窗口,旁系/更深世代折叠,靠 node 点击懒展开(避免整谱全展开卡死 H5)
|
||||||
if (rootPersonId.value) {
|
expanded.value = computeExpanded();
|
||||||
next[rootPersonId.value] = true;
|
|
||||||
}
|
|
||||||
// 所有有子女的族员一律展开;非主承担父由 node 的 expandable 门控,不会重复渲染子树
|
|
||||||
fullIndex.value?.childrenOf.forEach((_list: any[], parentId: string) => {
|
|
||||||
next[parentId] = true;
|
|
||||||
});
|
|
||||||
expanded.value = next;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (rootPersonId.value) {
|
else if (rootPersonId.value) {
|
||||||
@@ -556,10 +705,17 @@ function close(): void {
|
|||||||
<view class="ftc__ctrl-btn" @tap="toggleDirection">
|
<view class="ftc__ctrl-btn" @tap="toggleDirection">
|
||||||
<text class="ftc__ctrl-glyph">⇅</text>
|
<text class="ftc__ctrl-glyph">⇅</text>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- 全系图专用:一键展开全部 / 回到 ±5 代窗口默认(sub 懒加载模式无整谱概念,不显示) -->
|
||||||
|
<view v-if="props.mode === 'full'" class="ftc__ctrl-btn"
|
||||||
|
:class="{ 'ftc__ctrl-btn--active': expandAll }" @tap="toggleExpandAll">
|
||||||
|
<text class="ftc__ctrl-glyph">{{ expandAll ? '⊟' : '⊞' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 画布:双指捏合缩放(touch 层只识别两指,单指滚动不受影响) -->
|
</view>
|
||||||
<scroll-view class="ftc__scroll" scroll-x scroll-y :show-scrollbar="true" @touchstart="onChartTouchStart"
|
<!-- 画布:双指捏合缩放(touch 层只识别两指,单指滚动不受影响);H5 另支持鼠标按住拖拽平移(@mousedown 走模板绑定,见 float-add-btn 同款) -->
|
||||||
@touchmove="onChartTouchMove" @touchend="onChartTouchEnd" @touchcancel="onChartTouchEnd">
|
<scroll-view class="ftc__scroll" :class="{ 'ftc__scroll--panning': isPanning }" scroll-x scroll-y
|
||||||
|
:show-scrollbar="true" :scroll-left="scrollLeft" :scroll-top="scrollTop" @scroll="onChartScroll"
|
||||||
|
@mousedown="onPanStart" @touchstart="onChartTouchStart" @touchmove="onChartTouchMove"
|
||||||
|
@touchend="onChartTouchEnd" @touchcancel="onChartTouchEnd">
|
||||||
<view v-if="hasRoot" class="ftc__tree" :style="{ zoom }">
|
<view v-if="hasRoot" class="ftc__tree" :style="{ zoom }">
|
||||||
<family-tree-node :person-id="rootPersonId" :preview="null" :expandable="true" :depth="0" />
|
<family-tree-node :person-id="rootPersonId" :preview="null" :expandable="true" :depth="0" />
|
||||||
</view>
|
</view>
|
||||||
@@ -664,6 +820,16 @@ function close(): void {
|
|||||||
border-bottom-color: #f2f3f5;
|
border-bottom-color: #f2f3f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 展开全部态:青底白字,与默认「±5 代窗口」折叠态区分(对齐 node 动作钮青配色) */
|
||||||
|
.ftc__ctrl-btn--active {
|
||||||
|
.ftc__ctrl-glyph {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: #13c2c2;
|
||||||
|
border-bottom-color: #13c2c2;
|
||||||
|
}
|
||||||
|
|
||||||
.ftc__ctrl-glyph {
|
.ftc__ctrl-glyph {
|
||||||
font-size: 34rpx;
|
font-size: 34rpx;
|
||||||
color: #1f2329;
|
color: #1f2329;
|
||||||
@@ -678,6 +844,21 @@ function close(): void {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
/* H5 鼠标拖拽平移:手型光标 + 禁选(图是示意不是文本,拖动时不选中卡内文字/不拖出图片幽灵) */
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ftc__scroll--panning {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拖拽平移时不把头像拖出原生图片幽灵(preventDefault 会误伤 @tap,改由 CSS 拦) */
|
||||||
|
.ftc__scroll :deep(img) {
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ftc__tree {
|
.ftc__tree {
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ const actionText = computed(() => {
|
|||||||
const actionClass = computed(() => (loaded.value ? "ftn__action--toggle" : "ftn__action--load"));
|
const actionClass = computed(() => (loaded.value ? "ftn__action--toggle" : "ftn__action--load"));
|
||||||
|
|
||||||
function onCardTap(): void {
|
function onCardTap(): void {
|
||||||
|
// 刚做过鼠标拖拽平移:松手这一下是拖拽副产物,不是点击,丢弃以免误展开/误选人
|
||||||
|
if (api.didPanDrag()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!loaded.value) {
|
if (!loaded.value) {
|
||||||
api.requestLoad(props.personId);
|
api.requestLoad(props.personId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ export interface FamilyTreeChartApi {
|
|||||||
isMale: (gender?: string) => boolean;
|
isMale: (gender?: string) => boolean;
|
||||||
/** 布局方向:false=世代向右(横向阶梯),true=世代向下(纵向排列),由容器 ⇅ 按钮切换 */
|
/** 布局方向:false=世代向右(横向阶梯),true=世代向下(纵向排列),由容器 ⇅ 按钮切换 */
|
||||||
vertical: Ref<boolean>;
|
vertical: Ref<boolean>;
|
||||||
|
/** H5 鼠标拖拽平移刚结束的一瞬返回 true:node 据此丢弃拖拽误触发的 tap,避免拖完手滑点到卡片 */
|
||||||
|
didPanDrag: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** sub 模式懒加载缓存的一项:某人的原始「人 + 配偶 + 子女」记录 */
|
/** sub 模式懒加载缓存的一项:某人的原始「人 + 配偶 + 子女」记录 */
|
||||||
|
|||||||
@@ -130,6 +130,8 @@
|
|||||||
"checkFailed": "Update check failed. Check your network or access line and retry",
|
"checkFailed": "Update check failed. Check your network or access line and retry",
|
||||||
"downloading": "Downloading installer…",
|
"downloading": "Downloading installer…",
|
||||||
"downloadFailed": "Download failed, please retry later",
|
"downloadFailed": "Download failed, please retry later",
|
||||||
|
"preparing": "Preparing installation…",
|
||||||
|
"installFailed": "Install failed. Allow installing unknown apps in Settings and retry",
|
||||||
"installHint": "Please finish installing in the system dialog, then reopen the app",
|
"installHint": "Please finish installing in the system dialog, then reopen the app",
|
||||||
"iosTip": "In-app install is not available on iOS. Please download the latest package from the website",
|
"iosTip": "In-app install is not available on iOS. Please download the latest package from the website",
|
||||||
"gotIt": "Got it"
|
"gotIt": "Got it"
|
||||||
|
|||||||
@@ -130,6 +130,8 @@
|
|||||||
"checkFailed": "检查更新失败,请检查网络或访问线路后重试",
|
"checkFailed": "检查更新失败,请检查网络或访问线路后重试",
|
||||||
"downloading": "下载安装包…",
|
"downloading": "下载安装包…",
|
||||||
"downloadFailed": "下载失败,请稍后重试",
|
"downloadFailed": "下载失败,请稍后重试",
|
||||||
|
"preparing": "正在准备安装…",
|
||||||
|
"installFailed": "安装失败,请在系统设置中允许「安装未知应用」后重试",
|
||||||
"installHint": "请在弹出的安装界面完成安装,装好后重开即为新版",
|
"installHint": "请在弹出的安装界面完成安装,装好后重开即为新版",
|
||||||
"iosTip": "iOS 暂不支持应用内安装,请前往网页版下载最新安装包",
|
"iosTip": "iOS 暂不支持应用内安装,请前往网页版下载最新安装包",
|
||||||
"gotIt": "知道了"
|
"gotIt": "知道了"
|
||||||
|
|||||||
+114
-114
@@ -1,51 +1,51 @@
|
|||||||
{
|
{
|
||||||
"name": "有家相册",
|
"name" : "有家相册",
|
||||||
"appid": "__UNI__F0B0BDF",
|
"appid" : "__UNI__F0B0BDF",
|
||||||
"description": "有家相册:家庭相册、家族树与文档共享的跨端应用",
|
"description" : "有家相册:家庭相册、家族树与文档共享的跨端应用",
|
||||||
"versionName": "1.0.1",
|
"versionName" : "1.0.2",
|
||||||
"versionCode": 2,
|
"versionCode" : 3,
|
||||||
"transformPx": false,
|
"transformPx" : false,
|
||||||
"uniStatistics": {
|
"uniStatistics" : {
|
||||||
"enable": false
|
"enable" : false
|
||||||
},
|
},
|
||||||
"app-plus": {
|
"app-plus" : {
|
||||||
"usingComponents": true,
|
"usingComponents" : true,
|
||||||
"nvueStyleCompiler": "uni-app",
|
"nvueStyleCompiler" : "uni-app",
|
||||||
"compilerVersion": 3,
|
"compilerVersion" : 3,
|
||||||
"splashscreen": {
|
"splashscreen" : {
|
||||||
"alwaysShowBeforeRender": true,
|
"alwaysShowBeforeRender" : true,
|
||||||
"waiting": true,
|
"waiting" : true,
|
||||||
"autoclose": true,
|
"autoclose" : true,
|
||||||
"delay": 0
|
"delay" : 0
|
||||||
},
|
},
|
||||||
"safearea": {
|
"safearea" : {
|
||||||
"background": "#ffffff",
|
"background" : "#ffffff",
|
||||||
"backgroundDark": "#2f0508",
|
"backgroundDark" : "#2f0508",
|
||||||
"bottom": {
|
"bottom" : {
|
||||||
"offset": "none"
|
"offset" : "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules" : {
|
||||||
"VideoPlayer": {}
|
"VideoPlayer" : {}
|
||||||
},
|
},
|
||||||
"privacy": {
|
"privacy" : {
|
||||||
"prompt": "template",
|
"prompt" : "template",
|
||||||
"template": {
|
"template" : {
|
||||||
"title": "服务协议和隐私政策",
|
"title" : "服务协议和隐私政策",
|
||||||
"message": "感谢你使用“有家相册”。我们非常重视你的个人信息保护,请你务必审慎阅读、充分理解《服务协议》和《隐私政策》各条款,包括但不限于:为向你提供账号注册登录、家庭相册、家族谱系与文件共享服务,我们会在你授权同意后收集你的手机号、头像、身份证照片(仅实名核验用)、你主动上传的照片/视频/文件以及必要的设备标识与操作日志等信息。<br/>你可阅读<a href=\"static/legal/service-agreement.html\">《服务协议》</a>和<a href=\"static/legal/privacy-policy.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
|
"message" : "感谢你使用“有家相册”。我们非常重视你的个人信息保护,请你务必审慎阅读、充分理解《服务协议》和《隐私政策》各条款,包括但不限于:为向你提供账号注册登录、家庭相册、家族谱系与文件共享服务,我们会在你授权同意后收集你的手机号、头像、身份证照片(仅实名核验用)、你主动上传的照片/视频/文件以及必要的设备标识与操作日志等信息。<br/>你可阅读<a href=\"static/legal/service-agreement.html\">《服务协议》</a>和<a href=\"static/legal/privacy-policy.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
|
||||||
"buttonAccept": "同意并接受",
|
"buttonAccept" : "同意并接受",
|
||||||
"buttonRefuse": "暂不同意",
|
"buttonRefuse" : "暂不同意",
|
||||||
"second": {
|
"second" : {
|
||||||
"title": "确认提示",
|
"title" : "确认提示",
|
||||||
"message": "进入应用前,你需先同意<a href=\"static/legal/service-agreement.html\">《服务协议》</a>和<a href=\"static/legal/privacy-policy.html\">《隐私政策》</a>,否则将退出应用。",
|
"message" : "进入应用前,你需先同意<a href=\"static/legal/service-agreement.html\">《服务协议》</a>和<a href=\"static/legal/privacy-policy.html\">《隐私政策》</a>,否则将退出应用。",
|
||||||
"buttonAccept": "同意并继续",
|
"buttonAccept" : "同意并继续",
|
||||||
"buttonRefuse": "退出应用"
|
"buttonRefuse" : "退出应用"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"distribute": {
|
"distribute" : {
|
||||||
"android": {
|
"android" : {
|
||||||
"permissions": [
|
"permissions" : [
|
||||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
@@ -59,102 +59,102 @@
|
|||||||
"<uses-feature android:name=\"android.hardware.camera\"/>"
|
"<uses-feature android:name=\"android.hardware.camera\"/>"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios" : {
|
||||||
"dSYMs": false,
|
"dSYMs" : false,
|
||||||
"privacyDescription": {
|
"privacyDescription" : {
|
||||||
"NSCameraUsageDescription": "用于拍摄头像、身份证照片及上传家庭相册中的照片和视频",
|
"NSCameraUsageDescription" : "用于拍摄头像、身份证照片及上传家庭相册中的照片和视频",
|
||||||
"NSPhotoLibraryUsageDescription": "用于从相册选择头像、身份证照片及上传照片和视频",
|
"NSPhotoLibraryUsageDescription" : "用于从相册选择头像、身份证照片及上传照片和视频",
|
||||||
"NSPhotoLibraryAddUsageDescription": "用于将照片或视频保存到系统相册"
|
"NSPhotoLibraryAddUsageDescription" : "用于将照片或视频保存到系统相册"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sdkConfigs": {},
|
"sdkConfigs" : {},
|
||||||
"icons": {
|
"icons" : {
|
||||||
"android": {
|
"android" : {
|
||||||
"hdpi": "unpackage/res/icons/72x72.png",
|
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||||
"xhdpi": "unpackage/res/icons/96x96.png",
|
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||||
"xxhdpi": "unpackage/res/icons/144x144.png",
|
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||||
"xxxhdpi": "unpackage/res/icons/192x192.png"
|
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios" : {
|
||||||
"appstore": "unpackage/res/icons/1024x1024.png",
|
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||||
"ipad": {
|
"ipad" : {
|
||||||
"app": "unpackage/res/icons/76x76.png",
|
"app" : "unpackage/res/icons/76x76.png",
|
||||||
"app@2x": "unpackage/res/icons/152x152.png",
|
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||||
"notification": "unpackage/res/icons/20x20.png",
|
"notification" : "unpackage/res/icons/20x20.png",
|
||||||
"notification@2x": "unpackage/res/icons/40x40.png",
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
"proapp@2x": "unpackage/res/icons/167x167.png",
|
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||||
"settings": "unpackage/res/icons/29x29.png",
|
"settings" : "unpackage/res/icons/29x29.png",
|
||||||
"settings@2x": "unpackage/res/icons/58x58.png",
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
"spotlight": "unpackage/res/icons/40x40.png",
|
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||||
"spotlight@2x": "unpackage/res/icons/80x80.png"
|
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||||
},
|
},
|
||||||
"iphone": {
|
"iphone" : {
|
||||||
"app@2x": "unpackage/res/icons/120x120.png",
|
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||||
"app@3x": "unpackage/res/icons/180x180.png",
|
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||||
"notification@2x": "unpackage/res/icons/40x40.png",
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
"notification@3x": "unpackage/res/icons/60x60.png",
|
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||||
"settings@2x": "unpackage/res/icons/58x58.png",
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
"settings@3x": "unpackage/res/icons/87x87.png",
|
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||||
"spotlight@2x": "unpackage/res/icons/80x80.png",
|
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||||
"spotlight@3x": "unpackage/res/icons/120x120.png"
|
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"h5": {
|
"h5" : {
|
||||||
"router": {
|
"router" : {
|
||||||
"mode": "history",
|
"mode" : "history",
|
||||||
"base": "/"
|
"base" : "/"
|
||||||
},
|
},
|
||||||
"devServer": {
|
"devServer" : {
|
||||||
"host": "::",
|
"host" : "::",
|
||||||
"port": 5173
|
"port" : 5173
|
||||||
},
|
},
|
||||||
"darkmode": true,
|
"darkmode" : true,
|
||||||
"themeLocation": "theme.json"
|
"themeLocation" : "theme.json"
|
||||||
},
|
},
|
||||||
"quickapp": {},
|
"quickapp" : {},
|
||||||
"mp-weixin": {
|
"mp-weixin" : {
|
||||||
"appid": "",
|
"appid" : "",
|
||||||
"setting": {
|
"setting" : {
|
||||||
"urlCheck": false
|
"urlCheck" : false
|
||||||
},
|
},
|
||||||
"usingComponents": true,
|
"usingComponents" : true,
|
||||||
"darkmode": true,
|
"darkmode" : true,
|
||||||
"themeLocation": "theme.json"
|
"themeLocation" : "theme.json"
|
||||||
},
|
},
|
||||||
"mp-alipay": {
|
"mp-alipay" : {
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"mp-baidu": {
|
"mp-baidu" : {
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"mp-toutiao": {
|
"mp-toutiao" : {
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"vueVersion": "3",
|
"vueVersion" : "3",
|
||||||
"app-harmony": {
|
"app-harmony" : {
|
||||||
"darkmode": true,
|
"darkmode" : true,
|
||||||
"themeLocation": "theme.json",
|
"themeLocation" : "theme.json",
|
||||||
"safearea": {
|
"safearea" : {
|
||||||
"background": "#ffffff",
|
"background" : "#ffffff",
|
||||||
"backgroundDark": "#2f0508",
|
"backgroundDark" : "#2f0508",
|
||||||
"bottom": {
|
"bottom" : {
|
||||||
"offset": "none"
|
"offset" : "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"distribute": {
|
"distribute" : {
|
||||||
"bundleName": "com.youjiala.app",
|
"bundleName" : "com.youjiala.app",
|
||||||
"icons": {
|
"icons" : {
|
||||||
"foreground": "unpackage/res/icons/1024x1024.png",
|
"foreground" : "unpackage/res/icons/1024x1024.png",
|
||||||
"background": "unpackage/res/icons/1024x1024.png"
|
"background" : "unpackage/res/icons/1024x1024.png"
|
||||||
},
|
},
|
||||||
"splashScreens": {
|
"splashScreens" : {
|
||||||
"startWindowBackground": "#FFFFFF",
|
"startWindowBackground" : "#FFFFFF",
|
||||||
"startWindowIcon": "unpackage/res/icons/1024x1024.png"
|
"startWindowIcon" : "unpackage/res/icons/1024x1024.png"
|
||||||
},
|
},
|
||||||
"signingConfigs": {}
|
"signingConfigs" : {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { reactive } from "vue";
|
||||||
import { currentUiLocale, UI_LOCALE_EN } from "@/common/locale-const";
|
import { currentUiLocale, UI_LOCALE_EN } from "@/common/locale-const";
|
||||||
import { enUS, zhCN } from "@/locale";
|
import { enUS, zhCN } from "@/locale";
|
||||||
import { absoluteFileUrl } from "@/utils/fileUrl";
|
import { absoluteFileUrl } from "@/utils/fileUrl";
|
||||||
@@ -45,6 +46,21 @@ const MANIFEST_PATH = "/FileServer/app-update.json";
|
|||||||
/** 「稍后再说」记账:记下被跳过的 versionCode,同一版本自动检查不再弹 */
|
/** 「稍后再说」记账:记下被跳过的 versionCode,同一版本自动检查不再弹 */
|
||||||
const SKIP_KEY = "app-update-skipped";
|
const SKIP_KEY = "app-update-skipped";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载/安装进度(供 App.root.vue 的全局遮罩进度条渲染)。
|
||||||
|
* percent<0 表示服务器未回报总大小(无 Content-Length)→ 走不定量动画;
|
||||||
|
* 文案 title 在本模块用 msg() 取好词(非组件上下文拿不到 useLocale),页面只读不算。
|
||||||
|
*/
|
||||||
|
export const updateProgress = reactive({
|
||||||
|
visible: false,
|
||||||
|
phase: "download" as "download" | "install",
|
||||||
|
title: "",
|
||||||
|
/** 0-100 已下载百分比;<0=总量未知的不确定态 */
|
||||||
|
percent: 0,
|
||||||
|
downloaded: 0,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
/** 非组件上下文的取词:AppUpdate.xxx → 当前语言包词条,缺失回落另一语言 */
|
/** 非组件上下文的取词:AppUpdate.xxx → 当前语言包词条,缺失回落另一语言 */
|
||||||
function msg(key: string, vars?: Record<string, string>): string {
|
function msg(key: string, vars?: Record<string, string>): string {
|
||||||
const primary = currentUiLocale() === UI_LOCALE_EN ? enUS : zhCN;
|
const primary = currentUiLocale() === UI_LOCALE_EN ? enUS : zhCN;
|
||||||
@@ -133,55 +149,56 @@ function promptUpdate(info: AppUpdateInfo): Promise<boolean> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 下整包并拉起系统安装器:showLoading 反复调用即刷进度文案;安装器弹出后由用户完成安装 */
|
/** 下整包:plus.downloader 拉 apk,statechange 里刷进度到 updateProgress;返回本地路径 */
|
||||||
function downloadAndInstall(info: AppUpdateInfo): Promise<void> {
|
function downloadApk(info: AppUpdateInfo): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const url = absoluteFileUrl(`/FileServer/${info.apk ?? "youjiala.apk"}`);
|
const url = absoluteFileUrl(`/FileServer/${info.apk ?? "youjiala.apk"}`);
|
||||||
uni.showLoading({ title: msg("AppUpdate.downloading"), mask: true });
|
// 保存路径自己定死并作为兜底:plus.downloader 成功后回的是 download.filename(不是 localPath)
|
||||||
|
const savePath = `_downloads/update-${info.versionCode}.apk`;
|
||||||
|
updateProgress.visible = true;
|
||||||
|
updateProgress.phase = "download";
|
||||||
|
updateProgress.title = msg("AppUpdate.downloading");
|
||||||
|
updateProgress.percent = 0;
|
||||||
|
updateProgress.downloaded = 0;
|
||||||
|
updateProgress.total = 0;
|
||||||
const task = plus.downloader.createDownload(
|
const task = plus.downloader.createDownload(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
filename: `_downloads/update-${info.versionCode}.apk`,
|
filename: savePath,
|
||||||
retry: 1,
|
retry: 2,
|
||||||
timeout: 300,
|
timeout: 600,
|
||||||
},
|
},
|
||||||
(download: { localPath?: string }, status: number) => {
|
(download: { filename?: string }, status: number) => {
|
||||||
uni.hideLoading();
|
// 判成功只看 HTTP 200;路径取 filename,缺失回落到我们指定的 savePath(旧代码错读 localPath 导致 200 也判失败)
|
||||||
if (status !== 200 || !download?.localPath) {
|
if (status !== 200) {
|
||||||
reject(new Error(`download HTTP ${status}`));
|
reject(new Error(`下载未完成(HTTP ${status})\n${url}`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plus.runtime.install(
|
updateProgress.percent = 100;
|
||||||
plus.io.convertLocalFileSystemURL(download.localPath),
|
resolve(download?.filename || savePath);
|
||||||
{},
|
|
||||||
() => {
|
|
||||||
// apk 分支走系统安装界面,装完重开即是新版;这里只兜底提示
|
|
||||||
uni.showToast({
|
|
||||||
title: msg("AppUpdate.installHint"),
|
|
||||||
icon: "none",
|
|
||||||
duration: 3000,
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
(error: unknown) => reject(error),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// 事件名是 statechanged(带 d),且进度字段挂在下载对象上(部分版本在 event.target),旧代码写错名/取错层→一直 0%
|
||||||
task.addEventListener(
|
task.addEventListener(
|
||||||
"statechange",
|
"statechanged",
|
||||||
(event: {
|
(event: any) => {
|
||||||
state: number;
|
const t = event?.target ?? event;
|
||||||
downloadedSize?: number;
|
updateProgress.downloaded = Number(t?.downloadedSize ?? 0);
|
||||||
totalSize?: number;
|
updateProgress.total = Number(t?.totalSize ?? 0);
|
||||||
}) => {
|
const state = Number(t?.state ?? 0);
|
||||||
const total = Number(event.totalSize ?? 0);
|
// state:3 开始接收数据 / 4 下载中 / 5 完成 / 6 失败
|
||||||
const done = Number(event.downloadedSize ?? 0);
|
if (state === 4 || state === 3) {
|
||||||
// state=4 下载中:apk 十几 MB,按百分比刷新比转圈更安心
|
updateProgress.percent =
|
||||||
if (event.state === 4 && total > 0) {
|
updateProgress.total > 0
|
||||||
uni.showLoading({
|
? Math.min(
|
||||||
title: `${msg("AppUpdate.downloading")} ${Math.min(99, Math.floor((done / total) * 100))}%`,
|
99,
|
||||||
mask: true,
|
Math.floor(
|
||||||
});
|
(updateProgress.downloaded / updateProgress.total) * 100,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: -1; // 无 Content-Length:不确定态动画
|
||||||
|
} else if (state === 6) {
|
||||||
|
reject(new Error(`下载中断\n${url}`));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -189,6 +206,26 @@ function downloadAndInstall(info: AppUpdateInfo): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 拉起系统安装器:装完由用户在系统界面确认;成功即 resolve,失败带出原因 */
|
||||||
|
function installApk(localPath: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
updateProgress.phase = "install";
|
||||||
|
updateProgress.title = msg("AppUpdate.preparing");
|
||||||
|
updateProgress.percent = 100;
|
||||||
|
plus.runtime.install(
|
||||||
|
plus.io.convertLocalFileSystemURL(localPath),
|
||||||
|
{},
|
||||||
|
() => resolve(),
|
||||||
|
(error: any) =>
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`拉起安装失败(${error?.message ?? error?.code ?? error})`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function readSkippedCode(): number {
|
function readSkippedCode(): number {
|
||||||
try {
|
try {
|
||||||
return Number(uni.getStorageSync(SKIP_KEY) ?? 0);
|
return Number(uni.getStorageSync(SKIP_KEY) ?? 0);
|
||||||
@@ -262,9 +299,31 @@ export async function checkAppUpdate(
|
|||||||
return "updated";
|
return "updated";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await downloadAndInstall(info);
|
const localPath = await downloadApk(info);
|
||||||
} catch {
|
await installApk(localPath);
|
||||||
uni.showToast({ title: msg("AppUpdate.downloadFailed"), icon: "none" });
|
// 安装器已拉起,遮罩交回系统界面;提示用户完成安装
|
||||||
|
updateProgress.visible = false;
|
||||||
|
uni.showToast({
|
||||||
|
title: msg("AppUpdate.installHint"),
|
||||||
|
icon: "none",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
return "updated";
|
||||||
|
} catch (error: any) {
|
||||||
|
// 下载/安装失败:区分阶段,把真实原因(HTTP 码/中断/安装被拒)摆到弹窗与日志,别再笼统报「下载失败」
|
||||||
|
const failedInstall = updateProgress.phase === "install";
|
||||||
|
updateProgress.visible = false;
|
||||||
|
const reason = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error("[app-update] 下载/安装失败", {
|
||||||
|
versionCode: info.versionCode,
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
uni.showModal({
|
||||||
|
title: msg(failedInstall ? "AppUpdate.installFailed" : "AppUpdate.downloadFailed"),
|
||||||
|
content: reason,
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: msg("AppUpdate.gotIt"),
|
||||||
|
});
|
||||||
return "failed";
|
return "failed";
|
||||||
}
|
}
|
||||||
return "updated";
|
return "updated";
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ export async function request<T = any>(config: RequestConfigLite): Promise<T> {
|
|||||||
data: config.data ?? {},
|
data: config.data ?? {},
|
||||||
header: config.header ?? {},
|
header: config.header ?? {},
|
||||||
meta: config.meta,
|
meta: config.meta,
|
||||||
|
// 整谱类重接口(如全系图 FindAllByTreeId)体量远超常规请求,允许调用方按单次覆盖全局 20s 超时
|
||||||
|
timeout: config.timeout,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user