refactor: frontend TS migration + Tailwind4/daisyUI5 unify + BUILDPLATFORM docker build

- migrate frontend to TypeScript (vue-tsc strict in build), upgrade all deps to latest (Vite 8, Tailwind 4, daisyUI 5, Pinia 4, vue-router 5)
- restructure frontend dirs (api/components/common/layouts/styles/types/views)
- drop Element Plus, add daisyUI TagInput; main CSS 490KB->163KB, entry JS 618KB->1.3KB
- rewrite Dockerfile(.cn): frontend/backend stages pinned to $BUILDPLATFORM, CGO_ENABLED=0 cross-compile, no QEMU in multi-arch builds; add .dockerignore
- local dev: Vite /api proxy + make dev targets; go:embed all:dist with .gitkeep so backend runs without prior frontend build
- fix latent bugs: Keys.vue users ref, Settings.vue undefined userStore, Login.vue Ref-as-error display, res.error misuse
- add REFACTOR_PLAN.md (phased refactor log)
This commit is contained in:
Sakurasan
2026-08-29 23:27:31 +08:00
parent 4a68ff6162
commit ac0a6808ec
55 changed files with 2497 additions and 2318 deletions
+28 -28
View File
@@ -144,27 +144,29 @@
<!-- Pagination -->
<Pagination :currentPage="currentPage" :totalItems="totalItems" :pageSize="pageSize"
:pageSizeOptions="[10, 20, 50, 100]" @changePage="changePage" @changePageSize="changePageSize" />
:pageSizeOptions="[10, 20, 50, 100]" @changePage="changePage" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted, inject, computed } from 'vue';
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import BreadcrumbHeader from '@/components/dashboard/BreadcrumbHeader.vue';
import Pagination from '@/components/Pagination.vue';
import Pagination from '@/components/common/Pagination.vue';
import UserNew from '@/views/dashboard/UserNew.vue';
import { useUserStore } from '@/stores/user';
import { useToast } from '@/composables/toast';
import type { UserInfo } from '@/types';
import {
BadgeXIcon, BadgeCheckIcon, EyeIcon, PlusIcon, Settings2Icon,
TrashIcon, Infinity
} from 'lucide-vue-next';
} from '@lucide/vue';
const router = useRouter();
const userStore = useUserStore();
const users = computed(() => userStore.users);
const { setToast } = inject('toast');
const { setToast } = useToast();
// 用户数据
const currentPage = ref(1);
@@ -172,10 +174,10 @@ const pageSize = ref(10);
const totalItems = computed(() => userStore.totalUsers);
// 封装公共的用户列表获取方法
const listUsers = async (size = pageSize.value, page = currentPage.value, active = selectedStatuses.map(status => status.value)) => {
const listUsers = async (size?: number, page?: number, active?: boolean[] | boolean) => {
currentPage.value = page || currentPage.value;
// console.log('pagesize', pageSize.value, 'page', currentPage.value, 'active', selectedStatuses.map(status => status.value));
await userStore.listUser(size, page, active);
await userStore.listUser(size ?? pageSize.value, page ?? currentPage.value, active ?? selectedStatuses.map(status => status.value));
};
// 组件挂载时加载用户数据
@@ -184,7 +186,7 @@ onMounted(() => {
});
// 分页与页面大小变化
const changePage = async (page, size) => {
const changePage = async (page: number, size: number) => {
if (page == currentPage.value && size == pageSize.value) {
return
}
@@ -193,11 +195,9 @@ const changePage = async (page, size) => {
await listUsers();
};
const changePageSize = changePage;
// 复选框选择状态
const selectAll = ref(false)
const selectedUsers = ref([])
const selectedUsers = ref<UserInfo[]>([])
const toggleSelectAll = () => {
users.value.forEach(key => key.selected = selectAll.value)
@@ -212,7 +212,7 @@ const toggleSelectAll = () => {
}
const toggleUserSelection = (user) => {
const toggleUserSelection = (user: UserInfo) => {
if (selectedUsers.value.includes(user)) {
selectedUsers.value = selectedUsers.value.filter(selectedUser => selectedUser !== user);
} else {
@@ -223,9 +223,9 @@ const toggleUserSelection = (user) => {
// 状态筛选
const statusOptions = ['Active', 'Inactive'];
const selectedStatuses = reactive([]);
const selectedStatuses = reactive<{ status: string; value: boolean }[]>([]);
const toggleStatusFilter = async (status) => {
const toggleStatusFilter = async (status: string) => {
const statusValue = status === 'Active';
const index = selectedStatuses.findIndex(item => item.status === status);
@@ -239,12 +239,12 @@ const toggleStatusFilter = async (status) => {
};
// 处理批量操作
const handleBatchAction = async (action) => {
const handleBatchAction = async (action: string) => {
if (selectedUsers.value.length === 0) {
return setToast('请选择用户', 'error');
}
if (!['enable', 'disable', 'delete'].includes(action)) {
return setToast('无效的操作 ${action}', 'error');
return setToast(`无效的操作 ${action}`, 'error');
}
if (selectedUsers.value.length === 0) {
return setToast('请选择用户', 'error');
@@ -255,20 +255,20 @@ const handleBatchAction = async (action) => {
if (res.data?.code === 200) {
setToast(`Users ${action} Success`, 'success');
} else {
setToast(res.error || `${action} Failed`, 'error');
setToast(res.data?.error || `${action} Failed`, 'error');
}
selectedUsers.value = [];
selectAll.value = false;
await listUsers();
} catch (error) {
} catch (error: any) {
console.error(`批量操作 ${action} 失败:`, error);
setToast('批量操作失败', 'error');
}
};
// 更新用户状态
const updateStatus = async (user) => {
const updateStatus = async (user: UserInfo) => {
try {
const action = user.active ? 'enable' : 'disable';
const res = await userStore.userOption(action, [user.id]);
@@ -276,45 +276,45 @@ const updateStatus = async (user) => {
if (res.data?.code === 200) {
setToast(`User ${user.name} has been ${action}`, 'success');
} else {
setToast(res.error || `用户 ${user.id} ${action} 失败`, 'error');
setToast(res.data?.error || `用户 ${user.id} ${action} 失败`, 'error');
}
await listUsers();
} catch (error) {
} catch (error: any) {
console.error('状态更新失败:', error);
setToast('状态更新失败', 'error');
}
};
const viewUser = (user) => {
const viewUser = (user: UserInfo) => {
router.push({ name: 'UserView', query: { id: user.id } });
}
const confirmDeleteUser = (user) => {
const confirmDeleteUser = (user: UserInfo) => {
if (confirm(`确认删除 ${user.username}?`)) {
deleteUser(user);
}
};
// 删除用户
const deleteUser = async (user) => {
const deleteUser = async (user: UserInfo) => {
try {
const res = await userStore.userOption('delete', [user.id]);
if (res.data?.code === 200) {
setToast('用户删除成功', 'success');
} else {
setToast(res.error || '删除失败', 'error');
setToast(res.data?.error || '删除失败', 'error');
}
await listUsers();
} catch (error) {
} catch (error: any) {
console.error('删除失败:', error);
setToast('删除失败', 'error');
}
};
// 关闭模态框
const modalRef = ref(null);
const modalRef = ref<HTMLDialogElement | null>(null);
const closeModal = async () => {
if (modalRef.value) {
modalRef.value.close();