mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-13 21:27:19 +08:00
新增表单和对话框组件,优化头像处理逻辑,更新相关工具函数,提升用户界面和交互体验。
This commit is contained in:
7
frontend/src/components/form/index.ts
Normal file
7
frontend/src/components/form/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import FormInput from './FormInput.vue';
|
||||
import FormSelect from './FormSelect.vue';
|
||||
|
||||
export {
|
||||
FormInput,
|
||||
FormSelect
|
||||
};
|
||||
@@ -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"];
|
||||
|
||||
@@ -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>
|
||||
|
||||
23
frontend/src/components/modals/index.ts
Normal file
23
frontend/src/components/modals/index.ts
Normal 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,
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
15
frontend/src/components/tables/index.ts
Normal file
15
frontend/src/components/tables/index.ts
Normal 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,
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
6
frontend/src/composables/common/index.ts
Normal file
6
frontend/src/composables/common/index.ts
Normal 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 };
|
||||
@@ -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则直接返回
|
||||
};
|
||||
|
||||
4
frontend/src/utils/index.ts
Normal file
4
frontend/src/utils/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { getUserAvatarUrl } from "./avatarUtil";
|
||||
import { dayjs, formatDate, formatDateString } from "./dateUtil";
|
||||
|
||||
export { getUserAvatarUrl, dayjs, formatDate, formatDateString };
|
||||
Reference in New Issue
Block a user