fix(mobile): replace App-Plus unsupported date and query APIs
`URLSearchParams` is absent from the App-Plus JS core, and
`toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' })` depends on full
ICU data that the App runtime does not ship, so the work-result pages built
either a broken query string or the device-local date.
Build query strings with `encodeURIComponent`, and centralise the business day
in `currentShanghaiDate()`, which shifts the epoch by a fixed UTC+8 offset
before slicing the ISO date. Asia/Shanghai has no DST, so the fixed offset is
exact rather than an approximation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { currentShanghaiDate } from '@/utils/date';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: string;
|
||||
@@ -33,8 +34,7 @@ const emit = defineEmits<{
|
||||
change: [value: string];
|
||||
}>();
|
||||
|
||||
const currentDate = () => new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
||||
const today = computed(() => props.maxDate || currentDate());
|
||||
const today = computed(() => props.maxDate || currentShanghaiDate());
|
||||
|
||||
const displayDate = computed(() => {
|
||||
if (props.modelValue === today.value) return `今天 · ${props.modelValue}`;
|
||||
|
||||
@@ -528,9 +528,9 @@ const markReviewed = async () => {
|
||||
nextReason: nextScenarioId.value ? nextReason.value.trim() : undefined,
|
||||
confirmedGrowthLevel: confirmedGrowthLevel.value || undefined
|
||||
});
|
||||
const params = new URLSearchParams({ id: String(detail.value.id) });
|
||||
if (assignedScenario) params.set('assigned', assignedScenario);
|
||||
uni.redirectTo({ url: `/pages/supervisor/review/done?${params.toString()}` });
|
||||
const params = [`id=${encodeURIComponent(String(detail.value.id))}`];
|
||||
if (assignedScenario) params.push(`assigned=${encodeURIComponent(assignedScenario)}`);
|
||||
uni.redirectTo({ url: `/pages/supervisor/review/done?${params.join('&')}` });
|
||||
} catch (error) {
|
||||
handleRequestError(error, '标记失败');
|
||||
} finally {
|
||||
|
||||
@@ -55,6 +55,7 @@ import { fetchMobileIdentity } from '@/services/position';
|
||||
import { updateAssistantCaptureStatus } from '@/services/personal-assistant';
|
||||
import { openProtectedSource } from '@/services/source-preview';
|
||||
import { getProjectWorkResult } from '@/services/work-results';
|
||||
import { currentShanghaiDate } from '@/utils/date';
|
||||
import SupervisorTabBar from '@/components/SupervisorTabBar.vue';
|
||||
import WorkDateSelector from '@/components/WorkDateSelector.vue';
|
||||
|
||||
@@ -62,8 +63,7 @@ const result = ref<ProjectWorkResult | null>(null);
|
||||
const currentProject = ref<MobileProject | null>(getCurrentProject());
|
||||
const message = ref('');
|
||||
const loading = ref(false);
|
||||
const currentDate = () => new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
||||
const today = ref(currentDate());
|
||||
const today = ref(currentShanghaiDate());
|
||||
const workDate = ref(today.value);
|
||||
const pageNum = ref(1);
|
||||
const pageSize = 20;
|
||||
@@ -116,7 +116,7 @@ const openSource = (item: WorkResultItem) => openProtectedSource(item.sourceUrl,
|
||||
const sourceLabel = (value: string) => ({ TEXT: '文字来源', VOICE: '原始语音', IMAGE: '现场照片', VIDEO: '现场视频' }[value] || '原始来源');
|
||||
const goBack = () => uni.navigateBack({ fail: () => uni.redirectTo({ url: '/pages/supervisor/index/index' }) });
|
||||
onShow(() => {
|
||||
today.value = currentDate();
|
||||
today.value = currentShanghaiDate();
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -73,14 +73,14 @@ import { fetchMobileIdentity } from '@/services/position';
|
||||
import { updateAssistantCaptureStatus } from '@/services/personal-assistant';
|
||||
import { openProtectedSource } from '@/services/source-preview';
|
||||
import { generateMyWorkResult } from '@/services/work-results';
|
||||
import { currentShanghaiDate } from '@/utils/date';
|
||||
import WorkDateSelector from '@/components/WorkDateSelector.vue';
|
||||
|
||||
const result = ref<WorkResult | null>(null);
|
||||
const currentProject = ref<MobileProject | null>(getCurrentProject());
|
||||
const loading = ref(false);
|
||||
const message = ref('');
|
||||
const currentDate = () => new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Shanghai' });
|
||||
const today = ref(currentDate());
|
||||
const today = ref(currentShanghaiDate());
|
||||
const workDate = ref(today.value);
|
||||
const isToday = computed(() => workDate.value === today.value);
|
||||
const emptyTitle = computed(() => isToday.value ? '今天还没有已确认的工作记录' : '这一天没有已确认的工作记录');
|
||||
@@ -147,7 +147,7 @@ const handleEmptyAction = () => isToday.value ? goAssistant() : changeWorkDate(t
|
||||
const goBack = () => uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/user/today/index' }) });
|
||||
|
||||
onShow(() => {
|
||||
today.value = currentDate();
|
||||
today.value = currentShanghaiDate();
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { ProjectWorkResult, WorkResult } from '@/types/api';
|
||||
import { apiRequest } from './api';
|
||||
|
||||
const params = (projectCode: string, workDate?: string, pageNum?: number, pageSize?: number) => {
|
||||
const query = new URLSearchParams({ projectCode });
|
||||
if (workDate) query.set('workDate', workDate);
|
||||
if (pageNum) query.set('pageNum', String(pageNum));
|
||||
if (pageSize) query.set('pageSize', String(pageSize));
|
||||
return query.toString();
|
||||
const query = [`projectCode=${encodeURIComponent(projectCode)}`];
|
||||
if (workDate) query.push(`workDate=${encodeURIComponent(workDate)}`);
|
||||
if (pageNum) query.push(`pageNum=${pageNum}`);
|
||||
if (pageSize) query.push(`pageSize=${pageSize}`);
|
||||
return query.join('&');
|
||||
};
|
||||
|
||||
export const generateMyWorkResult = (projectCode: string, workDate?: string) =>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const currentShanghaiDate = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
||||
@@ -32,6 +32,33 @@ test('员工成果页与主管成果页复用真实项目成果接口', async ()
|
||||
assert.match(service, /pageSize/);
|
||||
});
|
||||
|
||||
test('App-Plus 查询参数不依赖 URLSearchParams', async () => {
|
||||
const [service, supervisorReview] = await Promise.all([
|
||||
read('../src/services/work-results.ts'),
|
||||
read('../src/pages/supervisor/review/index.vue')
|
||||
]);
|
||||
|
||||
assert.doesNotMatch(`${service}\n${supervisorReview}`, /URLSearchParams/);
|
||||
assert.match(service, /encodeURIComponent\(projectCode\)/);
|
||||
assert.match(supervisorReview, /encodeURIComponent\(assignedScenario\)/);
|
||||
});
|
||||
|
||||
test('App-Plus 工作成果日期固定为上海 yyyy-MM-dd', async () => {
|
||||
const [dateUtil, selector, employeePage, supervisorPage] = await Promise.all([
|
||||
read('../src/utils/date.ts'),
|
||||
read('../src/components/WorkDateSelector.vue'),
|
||||
read('../src/pages/user/work-results/index.vue'),
|
||||
read('../src/pages/supervisor/work-results/index.vue')
|
||||
]);
|
||||
|
||||
assert.match(dateUtil, /Date\.now\(\) \+ 8 \* 60 \* 60 \* 1000/);
|
||||
assert.match(dateUtil, /toISOString\(\)\.slice\(0, 10\)/);
|
||||
assert.doesNotMatch(`${selector}\n${employeePage}\n${supervisorPage}`, /toLocaleDateString/);
|
||||
for (const source of [selector, employeePage, supervisorPage]) {
|
||||
assert.match(source, /currentShanghaiDate/);
|
||||
}
|
||||
});
|
||||
|
||||
test('主管成果页显式分页,避免把接口上限误当作完整团队', async () => {
|
||||
const [supervisorPage, service, types] = await Promise.all([
|
||||
read('../src/pages/supervisor/work-results/index.vue'),
|
||||
@@ -80,7 +107,7 @@ test('员工与主管成果页复用日期选择器查看历史成果且禁止
|
||||
assert.match(supervisorPage, /getProjectWorkResult\(currentProject\.value\.projectCode, workDate\.value, pageNum\.value, pageSize\)/);
|
||||
assert.match(dateSelector, /mode="date"/);
|
||||
assert.match(dateSelector, /:end="today"/);
|
||||
assert.match(dateSelector, /props\.maxDate \|\| currentDate\(\)/);
|
||||
assert.match(dateSelector, /props\.maxDate \|\| currentShanghaiDate\(\)/);
|
||||
assert.match(dateSelector, /nextDate > today\.value/);
|
||||
assert.match(employeePage, /这一天没有已确认的工作记录/);
|
||||
assert.match(employeePage, /回到今天/);
|
||||
|
||||
Reference in New Issue
Block a user