fix(mobile): recover direct channels after network failure

This commit is contained in:
key
2026-07-29 02:31:19 +08:00
parent 376a7c4b84
commit dc20b92061
6 changed files with 66 additions and 9 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "aihr-mobile-uni",
"version": "0.1.8",
"version": "0.1.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aihr-mobile-uni",
"version": "0.1.8",
"version": "0.1.10",
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-5020220260725001",
"@dcloudio/uni-app-plus": "3.0.0-alpha-5020220260725001",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "aihr-mobile-uni",
"version": "0.1.9",
"version": "0.1.10",
"private": true,
"type": "module",
"scripts": {
+2 -2
View File
@@ -3,8 +3,8 @@
"appid" : "__UNI__B36D8BE",
"description" : "帮道员工端",
"vueVersion" : "3",
"versionName" : "0.1.9",
"versionCode" : "109",
"versionName" : "0.1.10",
"versionCode" : "110",
"uniStatistics" : {
"enable" : false
},
+41 -4
View File
@@ -25,7 +25,16 @@
<text class="section-note">每次只提交给一个处理部门。</text>
</view>
</view>
<view class="channel-grid" role="radiogroup" aria-label="直达对象">
<view v-if="channelsLoading" class="channel-state" role="status" aria-live="polite">
<text>直达对象加载中…</text>
</view>
<view v-else-if="channelsFailed" class="channel-state error" role="alert">
<text>直达对象加载失败,请检查网络后重试。</text>
<button class="secondary-button channel-retry" :disabled="channelsLoading" @click="loadChannels">
重新加载直达对象
</button>
</view>
<view v-else class="channel-grid" role="radiogroup" aria-label="直达对象">
<button
v-for="channel in channels"
:key="channel.code"
@@ -173,6 +182,8 @@ const path = '/pages/user/direct/index';
const pageSize = 20;
const loggedIn = ref(false);
const channels = ref<DirectChannel[]>([]);
const channelsLoading = ref(false);
const channelsFailed = ref(false);
const items = ref<DirectFeedback[]>([]);
const total = ref(0);
const pageNum = ref(1);
@@ -204,7 +215,12 @@ let voiceTimer: ReturnType<typeof setInterval> | null = null;
let voiceStartedAt = 0;
const hasMore = computed(() => items.value.length < total.value);
const canSubmit = computed(() => Boolean(form.channelCode) && Boolean(form.content.trim()));
const canSubmit = computed(() =>
!channelsLoading.value
&& !channelsFailed.value
&& Boolean(form.channelCode)
&& Boolean(form.content.trim())
);
const voiceBusy = computed(() => voiceStarting.value || voiceRecording.value || voiceStopping.value || voiceTranscribing.value);
const voiceChipLabel = computed(() => {
if (voiceStopping.value) return '结束中…';
@@ -378,8 +394,28 @@ const changeAnonymous = (event: Event | { detail?: { value?: boolean } }) => {
};
const loadChannels = async () => {
channels.value = await listDirectChannels();
if (!form.channelCode && channels.value.length) chooseChannel(channels.value[0]);
if (channelsLoading.value) return;
channelsLoading.value = true;
channelsFailed.value = false;
try {
const rows = await listDirectChannels();
if (!rows.length) {
channels.value = [];
form.channelCode = '';
channelsFailed.value = true;
return;
}
channels.value = rows;
if (!form.channelCode || !rows.some((channel) => channel.code === form.channelCode)) {
chooseChannel(rows[0]);
}
} catch {
channels.value = [];
form.channelCode = '';
channelsFailed.value = true;
} finally {
channelsLoading.value = false;
}
};
const loadMine = async (reset: boolean) => {
@@ -463,6 +499,7 @@ onUnload(() => {
.section-head,.history-head,.feedback-head,.form-foot,.anonymous-control{display:flex;align-items:center;justify-content:space-between;gap:12px}
.section-title{display:block;color:var(--ink);font-size:18px;font-weight:900}.section-note,.muted{display:block;margin-top:4px;color:var(--muted);font-size:12px;line-height:1.6}
.channel-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.channel-button{display:grid;min-height:60px;align-content:center;justify-items:center;gap:3px;padding:10px;border:1px solid #e6e8ec;border-radius:14px;background:#fff;color:var(--ink);font-size:14px;font-weight:900;text-align:center;transition:border-color .2s ease,box-shadow .2s ease,transform .1s ease}.channel-button.active{border-color:var(--brand);background:#fff5f6;color:var(--brand);box-shadow:0 6px 16px rgba(230,0,18,.10)}.channel-button:active{transform:scale(.98)}.channel-button::after,.refresh-button::after{border:0}.channel-hint{font-size:11px;font-weight:700;opacity:.72}
.channel-state{display:grid;justify-items:start;gap:10px;padding:14px;border-radius:12px;background:#f7f8fa;color:var(--muted);font-size:13px;line-height:1.5}.channel-state.error{background:#fff0f1;color:#a9000d}.channel-retry{min-width:150px;margin:0}
.channel-icon{display:grid;width:34px;height:34px;place-items:center;border-radius:12px;background:#f4f5f7;transition:background .2s ease}
.channel-button.active .channel-icon{background:var(--employee-primary-soft)}
.channel-name{line-height:1.3}
+13
View File
@@ -36,6 +36,19 @@ test('App 平台编译器已声明且与其他 uni 包同版本,避免 build:a
assert.equal(appPlus, pkg.devDependencies['@dcloudio/vite-plugin-uni']);
});
test('App 版本在 package、lockfile 与原生 manifest 间保持一致', async () => {
const [pkg, lockfile, manifest] = await Promise.all([
readJson('../package.json'),
readJson('../package-lock.json'),
readJson('../src/manifest.json')
]);
assert.equal(lockfile.version, pkg.version);
assert.equal(lockfile.packages?.['']?.version, pkg.version);
assert.equal(manifest.versionName, pkg.version);
assert.match(String(manifest.versionCode || ''), /^\d+$/);
});
test('Android 自定义基座显式面向 Android 15 权限模型', async () => {
const manifest = await readJson('../src/manifest.json');
const android = manifest['app-plus']?.distribute?.android;
+7
View File
@@ -19,6 +19,13 @@ test('员工端直通车包含固定五通道、业务匿名和本人回复闭
assert.match(page, /匿名提交/);
assert.match(page, /正式回复/);
assert.match(page, /requestId/);
assert.match(page, /v-else-if="channelsFailed"/);
assert.match(page, /重新加载直达对象/);
assert.match(page, /@click="loadChannels"/);
assert.match(page, /const channelsLoading = ref\(false\)/);
assert.match(page, /const channelsFailed = ref\(false\)/);
assert.match(page, /const loadChannels = async \(\) => \{[\s\S]*if \(channelsLoading\.value\) return;[\s\S]*channelsFailed\.value = false;[\s\S]*await listDirectChannels\(\)[\s\S]*catch \{[\s\S]*form\.channelCode = '';[\s\S]*channelsFailed\.value = true;[\s\S]*finally \{[\s\S]*channelsLoading\.value = false;/);
assert.match(page, /const canSubmit = computed\(\(\) =>[\s\S]*!channelsLoading\.value[\s\S]*!channelsFailed\.value/);
assert.match(broadcast, /openDirect/);
assert.ok(pages.includes('pages/user/direct/index'));
});