7 Commits

Author SHA1 Message Date
weli beda8b5de7 Merge branch 'master' of https://ci.moicen.com/weli/huike-front 2026-05-03 20:16:58 +08:00
weli 994cf0c892 fix(daka): separate pull-refresh and list loading states
van-pull-refresh and van-list shared the same state.loading, causing
the loading spinner to never disappear when API calls completed.
Split into state.refreshing (pull-refresh) and state.loading (list).
Add try/finally to guarantee both states always reset.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 14:33:30 +08:00
weli c6788d210b feat(dept): add department switcher component
Add DeptSwitcher that shows department name + switch button when
user is in a multi-department org. Uses Van ActionSheet for
department selection. Integrated into user-profile.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 13:41:01 +08:00
weli 73f9741a88 chore: deploy_ver 改为 YYYYMMDD.NNN 格式支持每日多次部署
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 11:32:13 +08:00
weli 9f39a822f7 chore: 永久保留 deploy_ver debug 日志 + CLAUDE.md 记录更新规则
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 11:31:01 +08:00
weli 2fb5ffadc8 chore: 移除临时 debug 日志
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 11:29:38 +08:00
weli 08571869b5 debug: App.vue onMounted 添加版本号日志用于验证缓存刷新
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 11:27:15 +08:00
5 changed files with 94 additions and 8 deletions
+6
View File
@@ -5,3 +5,9 @@
vConsole 仅在当前用户有 `SYS_CAN_SUDO` tag 时显示。判断逻辑在 `src/App.vue:updateVConsoleVisibility()`
Tags 通过 `get_all_tags_of_the_user` API 异步加载(`src/store/user.ts:getTags()`),在 `setCurrentUser()` 之后执行。vConsole 的 watch 需要 `{ deep: true }` 以及独立的 `store.current.tags` watch 来覆盖异步加载完成后的触发。
## 版本号 debug 日志
`src/App.vue:onMounted()` 中有一条 `console.debug('[App.vue] deploy_ver=YYYYMMDD.NNN')`(如 `20260503.001`)。
**每次部署前必须递增这个版本号**:当天第一次部署用 `.001`,第二次用 `.002`,依此类推。跨天重置为 `.001`。这样在 vConsole 中就能精确确认小程序加载的是哪一次部署的 JS。
+1
View File
@@ -70,6 +70,7 @@ export default defineComponent({
const badge_props: Partial<BadgeProps> = { showZero: false, max: 99 }
onMounted(() => {
console.debug('[App.vue] deploy_ver=20260503.003');
let vconsole = document.getElementById("__vconsole");
if (vconsole) {
vconsole.hidden = true;
+66
View File
@@ -0,0 +1,66 @@
<template>
<div v-if="hasMultipleDepts" class="current-dept">
<span>当前部门{{ currentDeptName || '未选择' }}</span>
<van-button size="mini" plain type="primary" @click="showPicker = true">切换部门</van-button>
<van-action-sheet
v-model:show="showPicker"
:actions="deptActions"
@select="onSelect"
cancel-text="取消"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
import { ActionSheet, Button } from 'vant';
import useOrg from '~/store/org';
export default defineComponent({
name: 'dept-switcher',
components: {
[ActionSheet.name!]: ActionSheet,
[Button.name!]: Button,
},
setup() {
const { store, switchDepartment } = useOrg();
const showPicker = ref(false);
const hasMultipleDepts = computed(() => store.departments.length > 1);
const currentDeptName = computed(() => {
if (!store.currentDepartmentId) return '';
const dept = store.departments.find((d) => d.id === store.currentDepartmentId);
return dept?.dept_name || '';
});
const deptActions = computed(() =>
store.departments.map((d) => ({
name: d.dept_name,
id: d.id,
}))
);
const onSelect = async (action: { name: string; id: string }) => {
showPicker.value = false;
if (action.id !== store.currentDepartmentId) {
await switchDepartment(action.id);
window.location.reload();
}
};
return { hasMultipleDepts, currentDeptName, showPicker, deptActions, onSelect };
},
});
</script>
<style lang="less" scoped>
.current-dept {
display: flex;
justify-content: center;
align-items: center;
gap: 0.12rem;
color: #666;
margin-bottom: 0.12rem;
}
</style>
+8 -2
View File
@@ -20,6 +20,7 @@
<span>当前机构{{ currentOrgName || '未选择机构' }}</span>
<van-button size="mini" plain type="primary" @click="goOrgSelect">切换机构</van-button>
</div>
<dept-switcher />
<div v-if="isStudent" class="current-teacher">
<span>当前老师{{ currentTeacherName || '未选择老师' }}</span>
<van-button size="mini" plain type="primary" @click="goTeacherSelect">切换老师</van-button>
@@ -45,6 +46,7 @@ import { useRouter } from "vue-router";
import {UpyunAccess} from "~/utils/upyun";
import {CurrentUserRole, HtyAuthToken, HtySudoToken} from "~/utils";
import useOrg from "~/store/org";
import DeptSwitcher from "~/components/dept-switcher.vue";
import useTeacher from "~/store/teacher";
export default defineComponent({
@@ -54,13 +56,14 @@ export default defineComponent({
[Icon.name!]: Icon,
[Tag.name!]: Tag,
[ActionSheet.name!]: ActionSheet,
[Button.name!]: Button
[Button.name!]: Button,
DeptSwitcher,
},
setup() {
const switching = ref(false);
const router = useRouter();
const { store, chooseRole, update, logout } = useUser(router);
const { store: orgStore, loadMyOrgs } = useOrg();
const { store: orgStore, loadMyOrgs, loadMyDepartments } = useOrg();
const { store: teacherStore, switchTeacher } = useTeacher();
const user = computed(() => store.current)
const state = reactive({
@@ -167,6 +170,9 @@ export default defineComponent({
if (!orgStore.orgs.length) {
await loadMyOrgs();
}
if (!orgStore.departments.length) {
await loadMyDepartments();
}
});
return { switching, hasMultiRoles, roles, user, currentRole, switchRole, editAvatar, avatarUrl, editName, state, logoutUser, currentOrgName, goOrgSelect, isStudent, currentTeacherName, goTeacherSelect }
+13 -6
View File
@@ -11,7 +11,7 @@
<van-search shape="round" v-model.trim="state.keyword" />
</div>
<div class="list">
<van-pull-refresh v-model="state.loading" @refresh="onRefresh">
<van-pull-refresh v-model="state.refreshing" @refresh="onRefresh">
<van-list v-model:loading="state.loading"
:finished="state.finished" finished-text="没有更多数据了"
@load="onPage">
@@ -102,7 +102,9 @@ export default defineComponent({
const state = reactive({
keyword: '',
loading: false, finished: true,
refreshing: false,
loading: false,
finished: true,
start_date: '',
page: 1,
page_size: 10,
@@ -123,14 +125,19 @@ export default defineComponent({
if (start_date) {
params.start_date = start_date + " 00:00:00";
}
await query(params);
if (store.pages <= state.page) {
state.finished = true;
try {
await query(params);
if (store.pages <= state.page) {
state.finished = true;
}
} finally {
state.loading = false;
state.refreshing = false;
}
state.loading = false;
}
const onRefresh = async () => {
state.refreshing = true;
await search({page: 1, page_size: state.page_size});
}