add ai delete

This commit is contained in:
Chuck1sn
2025-05-29 17:09:29 +08:00
parent 55884bb1a1
commit 10bee7c656
14 changed files with 355 additions and 17 deletions

View File

@@ -1102,6 +1102,52 @@
}
}
}
},
"/ai/action/user": {
"delete": {
"tags": [
"ai-controller"
],
"operationId": "deleteUser_1",
"parameters": [
{
"name": "username",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/ai/action/department": {
"delete": {
"tags": [
"ai-controller"
],
"operationId": "deleteDepartment_1",
"parameters": [
{
"name": "name",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"components": {

View File

@@ -532,6 +532,38 @@ export interface paths {
patch?: never;
trace?: never;
};
"/ai/action/user": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
delete: operations["deleteUser_1"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/ai/action/department": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
delete: operations["deleteDepartment_1"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
@@ -1693,4 +1725,44 @@ export interface operations {
};
};
};
deleteUser_1: {
parameters: {
query: {
username: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
deleteDepartment_1: {
parameters: {
query: {
name: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
}

View File

@@ -0,0 +1,51 @@
<template>
<form class="max-w-xs mb-4">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only ">Search</label>
<div class="relative">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 " 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>
</div>
<input type="search" id="default-search" v-model="bindInput"
:class="['block w-full ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500', size === 'sm' ? 'p-2.5' : size === 'md' ? 'p-3' : 'p-4']"
:placeholder="placeholder" required />
<button type="submit"
:class="['text-white absolute end-2.5 font-medium rounded-lg text-sm', size === 'sm' ? 'text-xs px-1.5 py-1.5 bottom-2' : size === 'md' ? 'text-sm px-4 py-2 bottom-2.5' : 'text-base px-4 py-2', bgColor] "
@click.prevent="handleSubmitClick(bindInput)">{{ content }}</button>
</div>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { z } from "zod";
const {
placeholder,
content,
handleSubmit,
size = "md",
bgColor = "bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 ",
} = defineProps<{
placeholder?: string;
content: string;
handleSubmit: (input: string) => void;
size: "sm" | "md" | "lg";
bgColor: string;
}>();
const bindInput = ref<string>();
const handleSubmitClick = (input?: string) => {
const userSchema = z
.string({
message: "输入的内容不能为空",
})
.nonempty();
const result = userSchema.parse(input);
handleSubmit(result);
};
</script>

View File

@@ -43,7 +43,7 @@ defineProps<{
title: string;
id: string;
closeModal: () => void;
onSubmit: (event: Event) => Promise<void>;
onSubmit: () => Promise<void>;
}>();
onMounted(() => {

View File

@@ -0,0 +1,28 @@
import client from "../../api/client";
export const useAiAction = () => {
const deleteUserByUsername = async (username: string) => {
await client.DELETE("/ai/action/user", {
params: {
query: {
username,
},
},
});
};
const deleteDepartmentByName = async (name: string) => {
await client.DELETE("/ai/action/department", {
params: {
query: {
name,
},
},
});
};
return {
deleteUserByUsername,
deleteDepartmentByName,
};
};

View File

@@ -17,12 +17,21 @@
<div class="markdown-content markdown-body text-base font-normal py-2.5 text-gray-900 "
v-html="renderMarkdown(chatElement.content)">
</div>
<button v-if="chatElement.type === 'action' && chatElement.command" type="button"
@click="commandActionMap[chatElement.command!]"
<button
v-if="chatElement.type === 'action' && (chatElement.command === 'CREATE_USER' || chatElement.command === 'CREATE_DEPARTMENT')"
type="button" @click="commandActionMap[chatElement.command!]"
class="px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
{{
commandContentMap[chatElement.command!]
}}</button>
<InputButton
bgColor="bg-red-700 hover:bg-red-800 focus:ring-red-300 text-white focus:ring-4 focus:outline-none"
size="sm" :content="commandContentMap[chatElement.command!]" :handleSubmit="handleDeleteUserClick"
v-if="chatElement.command === 'DELETE_USER'" />
<InputButton
bgColor="bg-red-700 hover:bg-red-800 focus:ring-red-300 text-white focus:ring-4 focus:outline-none"
size="sm" :content="commandContentMap[chatElement.command!]" :handleSubmit="handleDeleteDepartmentClick"
v-if="chatElement.command === 'DELETE_DEPARTMENT'" />
</div>
</div>
</li>
@@ -54,6 +63,24 @@
帮我创建用户?
</span>
</button>
<button @click.prevent="() => handleSendClick('删除用户', true)" class=" inline-flex items-center justify-center p-0.5
mb-2 me-2 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-purple-500
to-pink-500 group-hover:from-purple-500 group-hover:to-pink-500 hover:text-white dark:text-white focus:ring-4
focus:outline-none focus:ring-purple-200 cursor-pointer dark:focus:ring-purple-800">
<span
class="px-3 py-2 text-xs transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-md group-hover:bg-transparent group-hover:dark:bg-transparent">
删除用户
</span>
</button>
<button @click.prevent="() => handleSendClick('删除部门', true)" class=" inline-flex items-center justify-center p-0.5
mb-2 me-2 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-purple-500
to-pink-500 group-hover:from-purple-500 group-hover:to-pink-500 hover:text-white dark:text-white focus:ring-4
focus:outline-none focus:ring-purple-200 cursor-pointer dark:focus:ring-purple-800">
<span
class="px-3 py-2 text-xs transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-md group-hover:bg-transparent group-hover:dark:bg-transparent">
删除部门
</span>
</button>
<button @click.prevent="() => handleSendClick('请帮我创建部门', true)" class="inline-flex items-center justify-center p-0.5 mb-2 me-2 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-purple-500 to-pink-500 group-hover:from-purple-500 group-hover:to-pink-500 hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-purple-200
cursor-pointer dark:focus:ring-purple-800">
<span
@@ -99,11 +126,19 @@
userUpsertModal!.hide();
}">
</UserUpsertModal>
<UserDeleteModal :id="'user-delete-modal'" :closeModal="() => {
currentDeleteUsername = undefined
userDeleteModal!.hide();
}" :onSubmit="handleDeleteUserSubmit" title="确定删除该用户吗" content="删除用户"></UserDeleteModal>
<DepartmentUpsertModal :id="'department-upsert-modal'" :onSubmit="handleUpsertDepartmentSubmit" :closeModal="() => {
availableDepartments = undefined
departmentUpsertModal!.hide();
}" :availableDepartments="availableDepartments">
</DepartmentUpsertModal>
<DepartmentDeleteModal :id="'department-delete-modal'" :closeModal="() => {
currentDeleteDepartmentName = undefined
departmentDeleteModal!.hide();
}" :onSubmit="handleDeleteDepartmentSubmit" title="确定删除该部门吗" content="删除部门"></DepartmentDeleteModal>
</template>
<script setup lang="ts">
@@ -124,6 +159,10 @@ import type { UserUpsertSubmitModel } from "../types/user";
import { useDepartmentQuery } from "@/composables/department/useDepartmentQuery";
import { useDepartmentUpsert } from "@/composables/department/useDepartmentUpsert";
import type { DepartmentUpsertModel } from "@/types/department";
import UserDeleteModal from "@/components/PopupModal.vue";
import { useAiAction } from "@/composables/ai/useAiAction";
import DepartmentDeleteModal from "@/components/PopupModal.vue";
import InputButton from "@/components/InputButton.vue";
const { messages, chat, isLoading, cancel, actionChat } = useAiChat();
const { user } = useUserStore();
@@ -135,6 +174,11 @@ const alertStore = useAlertStore();
const isCommandMode = ref(false);
const userUpsert = useUserUpsert();
const departmentUpsert = useDepartmentUpsert();
const userDeleteModal = ref<ModalInterface>();
const { deleteUserByUsername, deleteDepartmentByName } = useAiAction();
const departmentDeleteModal = ref<ModalInterface>();
const currentDeleteUsername = ref<string>();
const currentDeleteDepartmentName = ref<string>();
const { availableDepartments, fetchAvailableDepartments } =
useDepartmentQuery();
@@ -147,11 +191,19 @@ const commandActionMap: Record<string, () => void> = {
fetchAvailableDepartments();
departmentUpsertModal.value?.show();
},
DELETE_USER: () => {
userDeleteModal.value?.show();
},
DELETE_DEPARTMENT: () => {
departmentDeleteModal.value?.show();
},
};
const commandContentMap: Record<string, string> = {
CREATE_USER: "创建用户",
CREATE_DEPARTMENT: "创建部门",
DELETE_USER: "删除用户",
DELETE_DEPARTMENT: "删除部门",
};
const toggleMode = () => {
@@ -184,6 +236,20 @@ const renderMarkdown = (content: string | undefined) => {
// console.log('处理后HTML:', renderMarkdown(newVal[newVal.length - 1]));
// }, { deep: true });
const handleDeleteUserClick = (input: string) => {
currentDeleteUsername.value = input;
nextTick(() => {
userDeleteModal.value?.show();
});
};
const handleDeleteDepartmentClick = (input: string) => {
currentDeleteDepartmentName.value = input;
nextTick(() => {
departmentDeleteModal.value?.show();
});
};
const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => {
await userUpsert.upsertUser(data);
userUpsertModal.value?.hide();
@@ -204,6 +270,24 @@ const handleUpsertDepartmentSubmit = async (
});
};
const handleDeleteUserSubmit = async () => {
await deleteUserByUsername(currentDeleteUsername.value!);
userDeleteModal.value?.hide();
alertStore.showAlert({
content: "操作成功",
level: "success",
});
};
const handleDeleteDepartmentSubmit = async () => {
await deleteDepartmentByName(currentDeleteDepartmentName.value!);
departmentDeleteModal.value?.hide();
alertStore.showAlert({
content: "操作成功",
level: "success",
});
};
watch(
messages,
async () => {
@@ -266,6 +350,24 @@ onMounted(async () => {
id: "user-upsert-modal",
},
);
const $userDeleteModalElement: HTMLElement | null =
document.querySelector("#user-delete-modal");
userDeleteModal.value = new Modal(
$userDeleteModalElement,
{},
{
id: "user-delete-modal",
},
);
const $departmentDeleteModalElement: HTMLElement | null =
document.querySelector("#department-delete-modal");
departmentDeleteModal.value = new Modal(
$departmentDeleteModalElement,
{},
{
id: "department-delete-modal",
},
);
const $departmentUpsertModalElement: HTMLElement | null =
document.querySelector("#department-upsert-modal");
departmentUpsertModal.value = new Modal(

View File

@@ -114,8 +114,8 @@
<span>编辑</span>
</button>
<button class="flex items-center justify-center whitespace-nowrap gap-x-1
bg-red-700 hover:bg-red-800 focus:outline-none focus:ring-red-300
text-white focus:ring-4 focus:outline-nonefont-medium rounded-lg text-sm px-4 py-2.5"
bg-red-700 hover:bg-red-800 focus:ring-red-300
text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-4 py-2.5"
@click="handleDeleteUserClick(user)" type="button">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"