新增表单和对话框组件,优化头像处理逻辑,更新相关工具函数,提升用户界面和交互体验。

This commit is contained in:
Chuck1sn
2025-06-28 09:16:36 +08:00
parent f9c8e3808b
commit 3e1d7e6fee
10 changed files with 115 additions and 40 deletions

View File

@@ -0,0 +1,7 @@
import FormInput from './FormInput.vue';
import FormSelect from './FormSelect.vue';
export {
FormInput,
FormSelect
};

View File

@@ -29,7 +29,7 @@
import { Modal, initFlowbite } from "flowbite";
import { computed, onMounted } from "vue";
export type ModalSize = "xs" | "sm" | "md" | "lg" | "xl";
export type ModalSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "7xl";
const props = defineProps<{
/** 对话框标题 */
@@ -50,6 +50,12 @@ const maxWidthClass = computed(() => {
md: "max-w-md",
lg: "max-w-lg",
xl: "max-w-xl",
"2xl": "max-w-2xl",
"3xl": "max-w-3xl",
"4xl": "max-w-4xl",
"5xl": "max-w-5xl",
"6xl": "max-w-6xl",
"7xl": "max-w-7xl",
};
return sizes[props.size || "md"];

View File

@@ -9,25 +9,33 @@
<h3 class="mb-4 text-base sm:text-lg font-medium text-gray-800">
{{ title }}
</h3>
<p v-if="content" class="mb-4 text-sm text-gray-500">{{ content }}</p>
<div class="flex justify-center items-center space-x-3 sm:space-x-4">
<button type="button" @click="onSubmit"
class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center min-w-[80px]">
<Button variant="danger" @click="onSubmit">
</button>
<button type="button" @click="closeModal"
class="py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 min-w-[80px]"></button>
</Button>
<Button variant="secondary" @click="closeModal">
</Button>
</div>
</div>
</BaseDialog>
</template>
<script setup lang="ts">
import { Button } from "@/components/ui";
import BaseDialog from "./BaseDialog.vue";
const { title, id, closeModal, onSubmit } = defineProps<{
const props = defineProps<{
/** 对话框标题 */
title: string;
/** 对话框内容 */
content?: string;
/** 对话框ID */
id: string;
/** 关闭对话框的回调函数 */
closeModal: () => void;
/** 确认操作的回调函数 */
onSubmit: () => Promise<void>;
}>();
</script>

View File

@@ -0,0 +1,23 @@
import BaseDialog from "./BaseDialog.vue";
import ConfirmationDialog from "./ConfirmationDialog.vue";
import DepartmentFormDialog from "./DepartmentFormDialog.vue";
import LibraryFormDialog from "./LibraryFormDialog.vue";
import LlmFormDialog from "./LlmFormDialog.vue";
import PermissionFormDialog from "./PermissionFormDialog.vue";
import PositionFormDialog from "./PositionFormDialog.vue";
import RoleFormDialog from "./RoleFormDialog.vue";
import SchedulerFormDialog from "./SchedulerFormDialog.vue";
import UserFormDialog from "./UserFormDialog.vue";
export {
BaseDialog,
ConfirmationDialog,
DepartmentFormDialog,
LibraryFormDialog,
LlmFormDialog,
PermissionFormDialog,
PositionFormDialog,
RoleFormDialog,
SchedulerFormDialog,
UserFormDialog,
};

View File

@@ -33,15 +33,15 @@
</select>
</div>
</template>
<button type="submit"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-2 min-w-[70px] flex items-center justify-center"
@click.prevent="handleSearch">
<svg class="w-4 h-4 mr-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
</svg>
<Button variant="primary" size="sm" @click.prevent="handleSearch">
<template #icon>
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" />
</svg>
</template>
搜索
</button>
</Button>
</form>
<!-- 额外操作按钮插槽 -->
@@ -53,6 +53,7 @@
<script setup lang="ts">
import { onMounted, reactive, watch } from "vue";
import { Button } from "@/components/ui";
export interface FilterOption {
value: string | number | boolean;

View File

@@ -0,0 +1,15 @@
import MobileCardList from "./MobileCardList.vue";
import MobileCardListWithCheckbox from "./MobileCardListWithCheckbox.vue";
import TableButton from "./TableButton.vue";
import TableFilterForm from "./TableFilterForm.vue";
import TableFormLayout from "./TableFormLayout.vue";
import TablePagination from "./TablePagination.vue";
export {
MobileCardList,
MobileCardListWithCheckbox,
TableButton,
TableFilterForm,
TableFormLayout,
TablePagination,
};

View File

@@ -1,8 +1,7 @@
<template>
<div class="rounded-full border border-gray-200 flex items-center justify-center overflow-hidden flex-shrink-0"
:class="sizeClass">
<img v-if="processedSrc" :src="processedSrc" class="w-full h-full object-cover" :alt="alt">
<div v-else class="w-full h-full bg-gray-100"></div>
<img :src="processedSrc" class="w-full h-full object-cover" :alt="alt">
</div>
</template>
@@ -10,34 +9,29 @@
import { getUserAvatarUrl } from "@/utils/avatarUtil";
import { computed } from "vue";
const {
src = "",
alt = "用户头像",
size = "md",
} = defineProps<{
const props = defineProps<{
/** 头像图片源 */
src?: string;
/** 头像替代文本 */
alt?: string;
size?: "sm" | "md" | "lg";
/** 头像尺寸 */
size?: "xs" | "sm" | "md" | "lg" | "xl";
}>();
/** 尺寸样式映射 */
const sizeClass = computed(() => {
switch (size) {
case "sm":
return "w-8 h-8";
case "lg":
return "w-12 h-12";
default:
return "w-10 h-10";
}
const sizes = {
xs: "w-6 h-6",
sm: "w-8 h-8",
md: "w-10 h-10",
lg: "w-12 h-12",
xl: "w-16 h-16"
};
return sizes[props.size || "md"];
});
/** 处理后的图片源 */
const processedSrc = computed(() => {
if (!src) {
return "";
}
if (src === "/trump.jpg") {
return src;
}
return getUserAvatarUrl(src);
return getUserAvatarUrl(props.src);
});
</script>

View File

@@ -0,0 +1,6 @@
import { useErrorHandling } from "./useErrorHandling";
import { usePagination } from "./usePagination";
import { useSorting } from "./useSorting";
import { useStyleSystem } from "./useStyleSystem";
export { useErrorHandling, usePagination, useSorting, useStyleSystem };

View File

@@ -1,5 +1,16 @@
export const getUserAvatarUrl = (avatar?: string): string | undefined => {
if (avatar?.startsWith("/")) {
/**
* 获取用户头像URL
* @param avatar 头像路径
* @returns 完整的头像URL或默认头像
*/
export const getUserAvatarUrl = (avatar?: string): string => {
if (!avatar) {
return "/trump.jpg"; // 默认头像
}
if (avatar.startsWith("/")) {
return `${import.meta.env.VITE_STATIC_URL}${avatar}`;
}
return avatar; // 如果已经是完整URL则直接返回
};

View File

@@ -0,0 +1,4 @@
import { getUserAvatarUrl } from "./avatarUtil";
import { dayjs, formatDate, formatDateString } from "./dateUtil";
export { getUserAvatarUrl, dayjs, formatDate, formatDateString };