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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user