This commit is contained in:
13121765685 2025-12-19 17:27:23 +08:00
parent bdb9fa3a39
commit 0fd7cf4c72
28 changed files with 2498 additions and 666 deletions

View File

@ -0,0 +1,91 @@
# AdminUserList.vue 页面重新设计方案
## 1. 设计目标
- 整合所有提供的API方法实现完整的管理员用户管理功能
- 提供良好的用户体验,包括表单验证、加载状态和操作反馈
- 支持批量操作和分页功能
- 保持代码结构清晰,易于维护
- **确保所有文本支持中英文切换**使用现有的i18n机制
## 2. 页面结构设计
### 2.1 基础布局
- 保留原有的标题、卡片、搜索栏、表格和分页组件
- 添加批量操作工具栏
- 新增三个对话框:
- 用户创建/编辑对话框
- 密码重置对话框
- 操作确认对话框(用于删除和启用/禁用操作)
### 2.2 表格列设计
- 保留原有的用户名、邮箱、状态等列
- 新增角色列,显示用户关联的角色
- 新增全选复选框,支持批量操作
- 调整操作列,添加更多功能按钮
## 3. 功能实现
### 3.1 数据管理
- 使用`ref`和`reactive`管理页面状态
- 实现分页数据加载,使用`getAdminUsersList`方法
- 添加搜索和筛选功能
- 实现数据刷新机制
### 3.2 用户管理功能
- **创建用户**:点击"添加用户"按钮,打开创建对话框,调用`createAdminUser`方法
- **编辑用户**:点击"编辑"按钮,打开编辑对话框,调用`getAdminUserDetail`和`updateAdminUser`方法
- **删除用户**:支持单条删除和批量删除,调用`deleteAdminUsers`方法
- **启用/禁用用户**:点击状态切换按钮,调用`enableDisableUser`方法
- **重置密码**:点击"重置密码"按钮,打开密码重置对话框,调用`resetUserPassword`方法
### 3.3 表单验证
- 为用户创建/编辑表单添加验证规则
- 为密码重置表单添加验证规则
- 提供清晰的错误提示
### 3.4 交互体验
- 添加加载状态指示器
- 提供操作成功/失败的消息提示
- 实现平滑的对话框过渡效果
- 支持键盘快捷键(可选)
### 3.5 国际化支持
- 所有文本内容使用`{{ t('key') }}`语法,确保支持中英文切换
- 表单验证消息也需要国际化
- 动态生成的文本(如角色名称)也需要考虑国际化
## 4. API集成
- 导入`AdminRoleManagement`类
- 实例化API服务对象
- 实现所有API方法的调用和错误处理
- 添加请求取消机制(可选)
## 5. 代码结构
- 保持`<template>`、`<script setup>``<style scoped>`
- 使用组合式API编写逻辑
- 提取重复逻辑为可复用的函数
- 添加适当的注释
## 6. 实现步骤
1. 更新页面模板,添加对话框和批量操作工具栏
2. 实现数据加载和分页逻辑
3. 实现用户创建功能
4. 实现用户编辑功能
5. 实现用户删除功能
6. 实现用户启用/禁用功能
7. 实现密码重置功能
8. 添加表单验证和错误处理
9. 确保所有文本支持中英文切换
10. 优化UI/UX
11. 测试所有功能
## 7. 技术要点
- 使用Vue 3组合式API
- 使用Element Plus组件库
- 实现响应式设计
- 遵循代码规范
- 确保性能优化
- **正确使用i18n机制支持中英文切换**
这个方案将实现一个功能完整、用户体验良好、支持中英文切换的管理员用户管理页面整合了所有提供的API方法。

View File

@ -0,0 +1,59 @@
# 替换提示词管理页面的模拟数据为API调用
## 目标
`AdminPromptManagement.vue` 组件中的模拟数据替换为真实的 API 调用,使用 `AdminPromptManagement` 类中定义的方法。
## 实现步骤
### 1. 导入 API 类并创建实例
- 在 `AdminPromptManagement.vue` 中导入 `AdminPromptManagement`
- 创建 API 实例用于调用方法
### 2. 替换模拟数据结构
- 移除 `prompts` 模拟数据数组
- 添加响应式数据:`prompts`(用于存储所有提示词)、`loading`(加载状态)
- 调整 `formData` 结构以匹配 API 要求(将 `referenceImage` 改为 `imageUrls`
### 3. 实现数据获取逻辑
- 创建 `fetchPrompts` 方法,调用 `getPromptList` 获取所有提示词
- 调用时设置 `pageSize: 99` 以获取所有数据(不分页)
- 无需按 `isActive` 区分调用,获取所有数据后在前端过滤
- 在组件挂载时调用 `fetchPrompts` 初始化数据
### 4. 更新计算属性
- `inactivePrompts`:过滤 `prompts``isActive === 0` 的数据
- `activePrompts`:过滤 `prompts``isActive === 1` 的数据
- `sortedActivePrompts`:排序逻辑保持不变,但使用 `sortOrder` 字段替代 `sortIndex`
### 5. 更新组件方法
- **savePrompt**:根据 `isEditing` 状态调用 `updatePrompt``createPrompt`
- 将 `formData.imageUrls` 转换为数组格式
- **deletePrompt**:调用 `deletePrompt` API 方法,成功后重新获取数据
- **removeFromActive**:调用 `deactivatePrompt` API 方法,成功后重新获取数据
- **handleDrop**:调用 `activatePrompt` API 方法,成功后重新获取数据
- **handleSortUpdate**:调用 `batchUpdateSort` API 方法,传递排序后的提示词列表
### 6. 调整拖拽功能
- 确保拖拽排序后正确调用 `batchUpdateSort` 更新后端数据
- 转换数据格式以匹配 API 要求(`sortOrder` 字段)
### 7. 处理 API 响应
- 确保 API 返回的数据格式与组件期望的格式匹配
- 处理 API 调用的错误情况
- 添加适当的加载状态提示
## 注意事项
- API 方法返回的是 Promise需要使用 `async/await` 处理
- API 数据结构与模拟数据略有不同,需要调整:
- `isActive` 是数字类型0/1而非布尔值
- `imageUrls` 是数组,而非单个字符串
- 排序字段是 `sortOrder`,而非 `sortIndex`
- 需要添加错误处理和加载状态管理
- 确保所有数据更新操作后重新获取最新数据
## 预期效果
- 页面初始加载时从 API 获取所有提示词不分页pageSize=99
- 左侧显示 `isActive=0` 的提示词,右侧显示 `isActive=1` 的提示词
- 所有操作(添加、编辑、删除、激活、取消激活、排序)都会调用相应的 API 方法
- 页面数据与后端保持同步
- 提供良好的加载状态和错误提示

View File

@ -0,0 +1,51 @@
# 重新设计AdminUserList.vue页面
## 1. 页面结构优化
- 保留现有页面布局,但增强功能
- 添加用户创建/编辑对话框
- 添加用户状态切换、密码重置等操作按钮
- 完善搜索功能
## 2. 数据绑定与API集成
- 引入AdminRoleManagement类
- 实现分页查询用户列表(getAdminUsersList)
- 实现用户搜索功能
- 实现用户创建(createAdminUser)
- 实现用户编辑(updateAdminUser)
- 实现用户启用/禁用(enableDisableUser)
- 实现用户密码重置(resetUserPassword)
- 实现用户删除(deleteAdminUsers)
## 3. 组件功能实现
- **用户列表**使用el-table展示用户数据包含用户名、邮箱、姓名、状态、创建时间等字段
- **搜索功能**:实现根据用户名和邮箱搜索
- **分页功能**与API集成实现分页加载
- **用户创建/编辑对话框**:包含表单验证
- **用户操作**
- 编辑用户
- 启用/禁用用户
- 重置密码
- 删除用户
## 4. 交互优化
- 添加加载状态
- 添加操作确认提示
- 添加成功/失败消息反馈
- 优化表单验证
## 5. 代码实现步骤
1. 导入AdminRoleManagement类
2. 替换硬编码数据为API调用
3. 实现分页查询
4. 添加用户创建/编辑对话框
5. 实现各项操作功能
6. 优化交互体验
7. 测试所有功能
## 6. 预期效果
- 页面能够正常加载用户列表
- 搜索功能正常工作
- 分页功能正常工作
- 用户创建/编辑/删除功能正常
- 用户状态切换和密码重置功能正常
- 交互流畅,反馈及时

View File

@ -101,12 +101,12 @@
<template #title>{{ t('admin.layout.users') }}</template> <template #title>{{ t('admin.layout.users') }}</template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/admin/points-management"> <el-menu-item index="/admin/points-management" v-if="false">
<el-icon><Coin /></el-icon> <el-icon><Coin /></el-icon>
<template #title>{{ t('admin.layout.pointsManagement') }}</template> <template #title>{{ t('admin.layout.pointsManagement') }}</template>
</el-menu-item> </el-menu-item>
<el-menu-item index="/admin/commission-management"> <el-menu-item index="/admin/commission-management" v-if="false">
<el-icon><Coin /></el-icon> <el-icon><Coin /></el-icon>
<template #title>{{ t('admin.layout.commissionManagement') }}</template> <template #title>{{ t('admin.layout.commissionManagement') }}</template>
</el-menu-item> </el-menu-item>
@ -115,7 +115,7 @@
<template #title>{{ t('admin.layout.promptManagement') }}</template> <template #title>{{ t('admin.layout.promptManagement') }}</template>
</el-menu-item> </el-menu-item>
<el-sub-menu index="/admin/permission"> <el-sub-menu index="/admin/permission" v-if="false">
<template #title> <template #title>
<el-icon><Lock /></el-icon> <el-icon><Lock /></el-icon>
<span>{{ t('admin.layout.permission') }}</span> <span>{{ t('admin.layout.permission') }}</span>

View File

@ -6,10 +6,10 @@
@click="handleClick" @click="handleClick"
> >
<!-- 参考图片 --> <!-- 参考图片 -->
<div v-if="prompt.referenceImage" class="card-image"> <div v-if="prompt.imageUrls.length > 0" class="card-image">
<img :src="prompt.referenceImage" :alt="prompt.title" /> <img :src="prompt.imageUrls[0]" />
</div> </div>
<div v-else class="card-image"></div>
<!-- 卡片内容 --> <!-- 卡片内容 -->
<div class="card-content"> <div class="card-content">
<h4 class="card-title">{{ prompt.title }}</h4> <h4 class="card-title">{{ prompt.title }}</h4>
@ -33,6 +33,11 @@
</div> </div>
</div> </div>
<!-- 卡片类型标签 -->
<div class="type-badge">
{{ getTypeLabel(prompt.type) }}
</div>
<!-- 生效标识 --> <!-- 生效标识 -->
<div v-if="isActive" class="active-badge"> <div v-if="isActive" class="active-badge">
<el-icon><Check /></el-icon> <el-icon><Check /></el-icon>
@ -57,7 +62,7 @@ const props = defineProps({
title: '', title: '',
content: '', content: '',
type: '', type: '',
referenceImage: '', imageUrls: [],
isActive: false isActive: false
}) })
}, },
@ -70,6 +75,16 @@ const props = defineProps({
// //
const emit = defineEmits(['edit', 'delete', 'drag-start', 'click']) const emit = defineEmits(['edit', 'delete', 'drag-start', 'click'])
//
const getTypeLabel = (type) => {
const typeMap = {
animal: t('admin.promptManagement.animal'),
person: t('admin.promptManagement.person'),
general: t('admin.promptManagement.general')
}
return typeMap[type] || type
}
// //
const onDragStart = (event) => { const onDragStart = (event) => {
event.dataTransfer.effectAllowed = 'move' event.dataTransfer.effectAllowed = 'move'
@ -178,11 +193,25 @@ const handleDelete = () => {
color: #ef4444; color: #ef4444;
} }
/* 卡片类型标签 */
.type-badge {
position: absolute;
top: 8px;
right: 8px;
background: #6b7280;
color: white;
font-size: 12px;
padding: 4px 8px;
border-radius: 12px;
font-weight: 500;
z-index: 1;
}
/* 生效标识 */ /* 生效标识 */
.active-badge { .active-badge {
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 8px; left: 8px;
background: #10b981; background: #10b981;
color: white; color: white;
font-size: 12px; font-size: 12px;

View File

@ -6,8 +6,8 @@
@click="handleClick" @click="handleClick"
> >
<!-- 左侧图片 --> <!-- 左侧图片 -->
<div v-if="prompt.referenceImage" class="card-image"> <div v-if="prompt.imageUrls.length > 0" class="card-image">
<img :src="prompt.referenceImage" :alt="prompt.title" /> <img :src="prompt.imageUrls[0]" />
</div> </div>
<!-- 右侧内容 --> <!-- 右侧内容 -->
@ -40,12 +40,6 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<!-- 生效标识 -->
<div class="active-badge">
<el-icon><Check /></el-icon>
{{ t('admin.promptManagement.active') }}
</div>
</div> </div>
</template> </template>

View File

@ -217,6 +217,9 @@ export default {
captchaLength: 'Verification code should be 4-6 characters' captchaLength: 'Verification code should be 4-6 characters'
}, },
common: { common: {
detail: 'Detail',
active: 'Active',
inactive: 'Inactive',
refresh: 'Refresh', refresh: 'Refresh',
search: 'Search', search: 'Search',
reset: 'Reset', reset: 'Reset',

View File

@ -15,7 +15,14 @@ export default {
error: '出错了', error: '出错了',
success: '操作成功', success: '操作成功',
warning: '警告', warning: '警告',
info: '提示' info: '提示',
active: '活跃',
inactive: '非活跃',
detail: '详情',
saveSuccess: '保存成功',
saveFailed: '保存失败',
deleteSuccess: '删除成功',
deleteFailed: '删除失败'
}, },
orderManagement: { orderManagement: {
title: '订单', title: '订单',
@ -228,6 +235,9 @@ orderManagement: {
} }
}, },
common: { common: {
detail: '详情',
active: '活跃',
inactive: '非活跃',
refresh: '刷新', refresh: '刷新',
search: '搜索', search: '搜索',
reset: '重置', reset: '重置',
@ -331,18 +341,58 @@ orderManagement: {
title: '用户列表管理', title: '用户列表管理',
userList: '用户列表', userList: '用户列表',
addUser: '添加用户', addUser: '添加用户',
editUser: '编辑用户',
searchPlaceholder: '搜索用户名、昵称或邮箱', searchPlaceholder: '搜索用户名、昵称或邮箱',
username: '用户名', username: '用户名',
fullName: '姓名',
nickname: '昵称', nickname: '昵称',
email: '邮箱', email: '邮箱',
phone: '电话', phone: '电话',
role: '角色', role: '角色',
roles: '角色',
status: '状态', status: '状态',
createTime: '创建时间', createTime: '创建时间',
lastLogin: '最后登录',
admin: '管理员', admin: '管理员',
user: '普通用户', user: '普通用户',
active: '活跃', active: '活跃',
inactive: '非活跃' inactive: '非活跃',
enable: '启用',
disable: '禁用',
resetPassword: '重置密码',
usernamePlaceholder: '请输入用户名',
fullNamePlaceholder: '请输入姓名',
emailPlaceholder: '请输入邮箱',
passwordPlaceholder: '请输入密码',
newPassword: '新密码',
newPasswordPlaceholder: '请输入新密码',
confirmPassword: '确认密码',
confirmPasswordPlaceholder: '请再次输入新密码',
selectRoles: '请选择角色',
usernameRequired: '请输入用户名',
usernameLength: '用户名长度应在3-20个字符之间',
fullNameRequired: '请输入姓名',
emailRequired: '请输入邮箱',
emailFormat: '请输入有效的邮箱地址',
passwordRequired: '请输入密码',
passwordLength: '密码长度不能少于6个字符',
rolesRequired: '请选择至少一个角色',
confirmPasswordRequired: '请输入确认密码',
passwordsNotMatch: '两次输入的密码不一致',
editSuccess: '编辑用户成功',
addSuccess: '添加用户成功',
editFailed: '编辑用户失败',
addFailed: '添加用户失败',
resetPasswordSuccess: '重置密码成功',
resetPasswordFailed: '重置密码失败',
deleteSuccess: '删除用户成功',
enableSuccess: '启用用户成功',
disableSuccess: '禁用用户成功',
statusChangeSuccess: '状态变更成功',
deleteConfirmContent: '确定要删除选中的用户吗?',
enableConfirmContent: '确定要启用选中的用户吗?',
disableConfirmContent: '确定要禁用选中的用户吗?',
isSuperuser: '超级管理员'
}, },
content: { content: {
title: '内容管理', title: '内容管理',

View File

@ -17,10 +17,10 @@ const AdminRoleManagement = () => import('@/views/admin/AdminRoleManagement/Admi
const AdminRoleDetail = () => import('@/views/admin/AdminRoleManagement/AdminRoleDetail.vue') const AdminRoleDetail = () => import('@/views/admin/AdminRoleManagement/AdminRoleDetail.vue')
const AdminPermissionManagement = () => import('@/views/admin/AdminPermissionManagement/AdminPermissionManagement.vue') const AdminPermissionManagement = () => import('@/views/admin/AdminPermissionManagement/AdminPermissionManagement.vue')
const AdminPermissionDetail = () => import('@/views/admin/AdminPermissionManagement/AdminPermissionDetail.vue') const AdminPermissionDetail = () => import('@/views/admin/AdminPermissionManagement/AdminPermissionDetail.vue')
const AdminUserList = () => import('@/views/admin/AdminUserList.vue') const AdminUserList = () => import('@/views/admin/AdminRoleManagement/AdminUserList.vue')
const AdminPointsManagement = () => import('@/views/admin/AdminPointsManagement.vue') const AdminPointsManagement = () => import('@/views/admin/AdminPointsManagement.vue')
const AdminCommissionManagement = () => import('@/views/admin/AdminCommissionManagement.vue') const AdminCommissionManagement = () => import('@/views/admin/AdminCommissionManagement.vue')
const AdminPromptManagement = () => import('@/views/admin/AdminPromptManagement.vue') const AdminPromptManagement = () => import('@/views/admin/AdminPromptManagement/AdminPromptManagement.vue')
const routes = [ const routes = [
{ {

View File

@ -53,25 +53,25 @@
center center
> >
<el-form :model="permissionForm" label-width="120px" :rules="permissionRules" ref="permissionFormRef"> <el-form :model="permissionForm" label-width="120px" :rules="permissionRules" ref="permissionFormRef">
<el-form-item label="权限名称" prop="permName"> <el-form-item :label="t('admin.permissionManagement.permissionName')" prop="permName">
<el-input v-model="permissionForm.permName" placeholder="请输入权限名称" /> <el-input v-model="permissionForm.permName" :placeholder="t('admin.permissionManagement.permissionName')" />
</el-form-item> </el-form-item>
<el-form-item label="权限代码" prop="permCode"> <el-form-item :label="t('admin.permissionManagement.permissionCode')" prop="permCode">
<el-input v-model="permissionForm.permCode" placeholder="请输入权限代码" /> <el-input v-model="permissionForm.permCode" :placeholder="t('admin.permissionManagement.permissionCode')" />
</el-form-item> </el-form-item>
<el-form-item label="所属模块" prop="module"> <el-form-item :label="t('admin.permissionManagement.module')" prop="module">
<el-input v-model="permissionForm.module" placeholder="请输入所属模块" /> <el-input v-model="permissionForm.module" :placeholder="t('admin.permissionManagement.module')" />
</el-form-item> </el-form-item>
<el-form-item label="操作类型" prop="action"> <el-form-item :label="t('admin.permissionManagement.action')" prop="action">
<el-input v-model="permissionForm.action" placeholder="请输入操作类型view, add, edit, delete" /> <el-input v-model="permissionForm.action" :placeholder="t('admin.permissionManagement.action')" />
</el-form-item> </el-form-item>
<el-form-item label="资源名称" prop="resource"> <el-form-item :label="t('admin.permissionManagement.resource')" prop="resource">
<el-input v-model="permissionForm.resource" placeholder="请输入资源名称" /> <el-input v-model="permissionForm.resource" :placeholder="t('admin.permissionManagement.resource')" />
</el-form-item> </el-form-item>
<el-form-item label="权限描述" prop="description"> <el-form-item :label="t('admin.permissionManagement.description')" prop="description">
<el-input type="textarea" v-model="permissionForm.description" placeholder="请输入权限描述" rows="3" /> <el-input type="textarea" v-model="permissionForm.description" :placeholder="t('admin.permissionManagement.description')" rows="3" />
</el-form-item> </el-form-item>
<el-form-item label="状态"> <el-form-item :label="t('admin.permissionManagement.isActive')">
<el-switch v-model="permissionForm.isActive" /> <el-switch v-model="permissionForm.isActive" />
</el-form-item> </el-form-item>
</el-form> </el-form>

View File

@ -18,7 +18,9 @@
<span class="count">{{ inactivePrompts.length }}</span> <span class="count">{{ inactivePrompts.length }}</span>
</div> </div>
<div class="prompt-list"> <div class="prompt-list">
<el-skeleton v-if="loading" :rows="6" animated />
<PromptCard <PromptCard
v-else
v-for="prompt in inactivePrompts" v-for="prompt in inactivePrompts"
:key="prompt.id" :key="prompt.id"
:prompt="prompt" :prompt="prompt"
@ -41,7 +43,9 @@
@drop="handleDrop" @drop="handleDrop"
@dragover="handleDragOver" @dragover="handleDragOver"
> >
<el-skeleton v-if="loading" :rows="6" animated />
<draggable <draggable
v-else
v-model="sortedActivePrompts" v-model="sortedActivePrompts"
item-key="id" item-key="id"
@start="handleDragStart" @start="handleDragStart"
@ -102,6 +106,7 @@
:auto-upload="false" :auto-upload="false"
:limit="1" :limit="1"
:on-change="handleImageChange" :on-change="handleImageChange"
:on-remove="handleImageRemove"
> >
<el-icon><Plus /></el-icon> <el-icon><Plus /></el-icon>
<template #tip> <template #tip>
@ -140,11 +145,12 @@
<label>{{ t('admin.promptManagement.content') }}:</label> <label>{{ t('admin.promptManagement.content') }}:</label>
<p>{{ selectedPrompt.content }}</p> <p>{{ selectedPrompt.content }}</p>
</div> </div>
<div class="detail-item" v-if="selectedPrompt.referenceImage"> <div class="detail-item" v-if="selectedPrompt.imageUrls && selectedPrompt.imageUrls.length > 0">
<label>{{ t('admin.promptManagement.referenceImage') }}:</label> <label>{{ t('admin.promptManagement.referenceImage') }}:</label>
<img :src="selectedPrompt.referenceImage" alt="参考图" class="reference-image" /> <img :src="selectedPrompt.imageUrls[0]" alt="参考图" class="reference-image" />
</div> </div>
</div> </div>
<el-skeleton v-else :rows="4" animated />
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@ -156,15 +162,19 @@
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import PromptCard from '@/components/admin/PromptCard.vue' import PromptCard from '@/components/admin/PromptCard.vue'
import PromptCardHorizontal from '@/components/admin/PromptCardHorizontal.vue' import PromptCardHorizontal from '@/components/admin/PromptCardHorizontal.vue'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { AdminPromptManagement } from './index.js'
const { t } = useI18n() const { t } = useI18n()
// API
const promptApi = new AdminPromptManagement()
// //
const dialogVisible = ref(false) const dialogVisible = ref(false)
const detailVisible = ref(false) const detailVisible = ref(false)
@ -173,197 +183,68 @@ const selectedPromptId = ref(null)
const selectedPrompt = ref(null) const selectedPrompt = ref(null)
const draggedPrompt = ref(null) const draggedPrompt = ref(null)
const fileList = ref([]) const fileList = ref([])
const prompts = ref([])
const loading = ref(false)
// //
const formData = ref({ const formData = ref({
title: '', title: '',
content: '', content: '',
type: '', type: '',
referenceImage: '' imageUrls: []
}) })
//
const prompts = ref([
{
id: 1,
title: '可爱的小狗',
content: '一只可爱的白色小狗,毛茸茸的,大眼睛,活泼好动',
type: 'animal',
referenceImage: 'https://picsum.photos/id/237/300/200',
isActive: false,
sortIndex: 0,
createdAt: new Date()
},
{
id: 2,
title: '商务人士',
content: '一位穿着西装的商务人士,自信的表情,背景是办公室',
type: 'person',
referenceImage: '',
isActive: false,
sortIndex: 1,
createdAt: new Date()
},
{
id: 3,
title: '美丽的风景',
content: '一片美丽的自然风光,蓝天白云,绿水青山',
type: 'general',
referenceImage: 'https://picsum.photos/id/1015/300/200',
isActive: true,
sortIndex: 0,
createdAt: new Date()
},
{
id: 4,
title: '优雅的猫咪',
content: '一只优雅的黑色猫咪,黄色的眼睛,柔软的毛发',
type: 'animal',
referenceImage: 'https://picsum.photos/id/452/300/200',
isActive: false,
sortIndex: 2,
createdAt: new Date()
},
{
id: 5,
title: '医生',
content: '一位穿着白大褂的医生,戴着口罩,手持听诊器',
type: 'person',
referenceImage: 'https://picsum.photos/id/225/300/200',
isActive: false,
sortIndex: 3,
createdAt: new Date()
},
{
id: 6,
title: '现代办公室',
content: '一个现代化的办公室,整洁的桌面,舒适的座椅',
type: 'general',
referenceImage: 'https://picsum.photos/id/1059/300/200',
isActive: true,
sortIndex: 1,
createdAt: new Date()
},
{
id: 7,
title: '强壮的狮子',
content: '一只强壮的狮子,金色的鬃毛,威严的表情',
type: 'animal',
referenceImage: 'https://picsum.photos/id/287/300/200',
isActive: false,
sortIndex: 4,
createdAt: new Date()
},
{
id: 8,
title: '教师',
content: '一位和蔼可亲的教师,手持书本,站在讲台前',
type: 'person',
referenceImage: '',
isActive: false,
sortIndex: 5,
createdAt: new Date()
},
{
id: 9,
title: '科技感会议室',
content: '一个充满科技感的会议室,大屏幕,智能设备',
type: 'general',
referenceImage: 'https://picsum.photos/id/1069/300/200',
isActive: true,
sortIndex: 2,
createdAt: new Date()
},
{
id: 10,
title: '活泼的兔子',
content: '一只活泼的灰色兔子,长长的耳朵,蹦蹦跳跳',
type: 'animal',
referenceImage: 'https://picsum.photos/id/297/300/200',
isActive: false,
sortIndex: 6,
createdAt: new Date()
},
{
id: 11,
title: '艺术家',
content: '一位专注的艺术家,正在画板前创作',
type: 'person',
referenceImage: 'https://picsum.photos/id/447/300/200',
isActive: false,
sortIndex: 7,
createdAt: new Date()
},
{
id: 12,
title: '温馨的咖啡馆',
content: '一个温馨的咖啡馆,柔和的灯光,舒适的氛围',
type: 'general',
referenceImage: 'https://picsum.photos/id/438/300/200',
isActive: true,
sortIndex: 3,
createdAt: new Date()
},
{
id: 13,
title: '聪明的鹦鹉',
content: '一只色彩鲜艳的鹦鹉,站在树枝上',
type: 'animal',
referenceImage: '',
isActive: false,
sortIndex: 8,
createdAt: new Date()
},
{
id: 14,
title: '运动员',
content: '一位正在跑步的运动员,充满活力',
type: 'person',
referenceImage: 'https://picsum.photos/id/340/300/200',
isActive: false,
sortIndex: 9,
createdAt: new Date()
},
{
id: 15,
title: '科幻城市',
content: '一个未来的科幻城市,高楼大厦,飞行器',
type: 'general',
referenceImage: 'https://picsum.photos/id/1058/300/200',
isActive: true,
sortIndex: 4,
createdAt: new Date()
}
])
// //
const inactivePrompts = computed(() => { const inactivePrompts = computed(() => {
return prompts.value.filter(prompt => !prompt.isActive) return prompts.value.filter(prompt => prompt.isActive === 0)
}) })
// //
const activePrompts = computed(() => { const activePrompts = computed(() => {
return prompts.value.filter(prompt => prompt.isActive) return prompts.value.filter(prompt => prompt.isActive === 1)
}) })
// //
const sortedActivePrompts = computed({ const sortedActivePrompts = computed({
get: () => { get: () => {
return prompts.value return prompts.value
.filter(prompt => prompt.isActive) .filter(prompt => prompt.isActive === 1)
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0)) .sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
}, },
set: (newValue) => { set: (newValue) => {
// sortIndex // sortOrder
newValue.forEach((prompt, index) => { newValue.forEach((prompt, index) => {
const originalPrompt = prompts.value.find(p => p.id === prompt.id) const originalPrompt = prompts.value.find(p => p.id === prompt.id)
if (originalPrompt) { if (originalPrompt) {
originalPrompt.sortIndex = index originalPrompt.sortOrder = index
} }
}) })
} }
}) })
//
const fetchPrompts = async () => {
try {
loading.value = true
const response = await promptApi.getPromptList({
pageSize: 99, //
pageNum: 1
})
if (response.success && response.data) {
prompts.value = response.data.rows || []
}
} catch (error) {
console.error('获取提示词列表失败:', error)
} finally {
loading.value = false
}
}
//
onMounted(() => {
fetchPrompts()
})
// //
const showAddDialog = () => { const showAddDialog = () => {
isEditing.value = false isEditing.value = false
@ -372,7 +253,7 @@ const showAddDialog = () => {
title: '', title: '',
content: '', content: '',
type: '', type: '',
referenceImage: '' imageUrls: []
} }
fileList.value = [] fileList.value = []
dialogVisible.value = true dialogVisible.value = true
@ -383,70 +264,88 @@ const showEditDialog = (prompt) => {
isEditing.value = true isEditing.value = true
selectedPromptId.value = prompt.id selectedPromptId.value = prompt.id
formData.value = { formData.value = {
id: prompt.id,
title: prompt.title, title: prompt.title,
content: prompt.content, content: prompt.content,
type: prompt.type, type: prompt.type,
referenceImage: prompt.referenceImage imageUrls: prompt.imageUrls || []
} }
fileList.value = prompt.referenceImage ? [{ url: prompt.referenceImage }] : [] fileList.value = (prompt.imageUrls && prompt.imageUrls.length > 0) ? [{ url: prompt.imageUrls[0] }] : []
dialogVisible.value = true dialogVisible.value = true
} }
// //
const savePrompt = () => { const savePrompt = async () => {
try {
loading.value = true
if (isEditing.value) { if (isEditing.value) {
// //
const index = prompts.value.findIndex(p => p.id === selectedPromptId.value) await promptApi.updatePrompt(formData.value)
if (index !== -1) {
prompts.value[index] = {
...prompts.value[index],
title: formData.value.title,
content: formData.value.content,
type: formData.value.type,
referenceImage: formData.value.referenceImage
}
}
} else { } else {
// //
const newPrompt = { await promptApi.createPrompt(formData.value)
id: Date.now(),
title: formData.value.title,
content: formData.value.content,
type: formData.value.type,
referenceImage: formData.value.referenceImage,
isActive: false,
sortIndex: prompts.value.filter(p => !p.isActive).length,
createdAt: new Date()
}
prompts.value.push(newPrompt)
} }
dialogVisible.value = false dialogVisible.value = false
//
await fetchPrompts()
} catch (error) {
console.error('保存提示词失败:', error)
} finally {
loading.value = false
}
} }
// //
const deletePrompt = (promptId) => { const deletePrompt = async (promptId) => {
prompts.value = prompts.value.filter(p => p.id !== promptId) try {
loading.value = true
await promptApi.deletePrompt(promptId)
//
await fetchPrompts()
} catch (error) {
console.error('删除提示词失败:', error)
} finally {
loading.value = false
}
} }
// //
const removeFromActive = (promptId) => { const removeFromActive = async (promptId) => {
const prompt = prompts.value.find(p => p.id === promptId) try {
if (prompt) { loading.value = true
prompt.isActive = false await promptApi.deactivatePrompt(promptId)
//
await fetchPrompts()
} catch (error) {
console.error('取消激活提示词失败:', error)
} finally {
loading.value = false
} }
} }
// //
const handleImageChange = (file) => { const handleImageChange = async (file) => {
if (file.raw) { if (file.raw) {
const reader = new FileReader() try {
reader.onload = (e) => { loading.value = true
formData.value.referenceImage = e.target.result // API
const response = await promptApi.uploadFileCom(file.raw)
// URL
formData.value.imageUrls = [response]
} catch (error) {
console.error('上传图片失败:', error)
} finally {
loading.value = false
} }
reader.readAsDataURL(file.raw)
} }
} }
//
const handleImageRemove = () => {
// formDataimageUrls
formData.value.imageUrls = []
}
// //
const handleLeftDragStart = (prompt) => { const handleLeftDragStart = (prompt) => {
draggedPrompt.value = prompt draggedPrompt.value = prompt
@ -458,20 +357,43 @@ const handleDragStart = (evt) => {
} }
// //
const handleSortUpdate = (evt) => { const handleSortUpdate = async (evt) => {
// sortedActivePromptssetter //
const sortData = {
items: sortedActivePrompts.value.map((prompt, index) => ({
id: prompt.id,
sortOrder: index
}))
}
try {
loading.value = true
await promptApi.batchUpdateSort(sortData)
//
await fetchPrompts()
} catch (error) {
console.error('更新排序失败:', error)
} finally {
loading.value = false
}
} }
// //
const handleDrop = (event) => { const handleDrop = async (event) => {
event.preventDefault() event.preventDefault()
if (draggedPrompt.value && !draggedPrompt.value.isActive) { if (draggedPrompt.value && draggedPrompt.value.isActive === 0) {
// try {
draggedPrompt.value.isActive = true loading.value = true
// sortIndex // API
draggedPrompt.value.sortIndex = sortedActivePrompts.value.length await promptApi.activatePrompt(draggedPrompt.value.id)
//
await fetchPrompts()
} catch (error) {
console.error('激活提示词失败:', error)
} finally {
loading.value = false
draggedPrompt.value = null draggedPrompt.value = null
} }
}
} }
// //

View File

@ -0,0 +1,163 @@
import { adminApi,requestUtils,FileServer} from '@deotaland/utils';
export class AdminPromptManagement extends FileServer {
constructor() {
super();
}
//查询提示词详情
async getPromptDetail(id) {
let parmas = {
id:id,
}
return await requestUtils.common(adminApi.default.getPromptDetail,parmas);
/**
返回示例
{
"code": 0,
"success": true,
"data": {
"id": 9007199254740991,
"type": "string",
"title": "string",
"content": "string",
"imageUrls": "string",
"isActive": 1073741824,
"sortOrder": 1073741824,
"adminId": 9007199254740991,
"remark": "string",
"isDelete": 1073741824,
"createdAt": "2025-12-19T06:38:48.802Z",
"updatedAt": "2025-12-19T06:38:48.802Z"
},
"message": "操作成功"
}
*/
}
//分页查询提示词列表
async getPromptList(data) {
let params = {
type:data.type,
title:data.title,
isActive:data.isActive,
pageSize:data.pageSize,
pageNum:data.pageNum,
orderByColumn:data.orderByColumn,
isAsc:data.isAsc
}
return await requestUtils.common(adminApi.default.getPromptList,params);
/**
返回示例
{
"code": 0,
"success": true,
"data": {
"total": 9007199254740991,
"rows": [
{
"id": 9007199254740991,
"type": "string",
"typeName": "string",
"title": "string",
"content": "string",
"imageUrls": [
"string"
],
"isActive": 1073741824,
"sortOrder": 1073741824,
"adminId": 9007199254740991,
"adminName": "string",
"remark": "string",
"createdAt": "2025-12-19T06:54:33.803Z",
"updatedAt": "2025-12-19T06:54:33.803Z"
}
],
"code": 1073741824,
"msg": "string"
},
"message": "操作成功"
}
*
*/
}
//更新提示词
async updatePrompt(data) {
let params = {
type: data.type,
title: data.title,
content: data.content,
imageUrls: data.imageUrls,
remark: data.remark
}
let requestUrl = {
url:adminApi.default.updatePrompt.url.replace('{id}',data.id),
method:adminApi.default.updatePrompt.method,
isLoading:adminApi.default.updatePrompt.isLoading,
}
return await requestUtils.common(requestUrl,params);
}
//批量更新排序
async batchUpdateSort(data) {
// data传递参数示例{//sortOrder为排序字段值为整数id为提示词id
// "items": [
// {
// "id": 9007199254740991,
// "sortOrder": 1073741824
// }
// ]
// }
let params = {
items: data.items
}
return await requestUtils.common(adminApi.default.batchUpdateSort,params);
}
//删除提示词
async deletePrompt(id) {
let params = {
id:id,
}
let requestUrl = {
url:adminApi.default.deletePrompt.url.replace('{id}',id),
method:adminApi.default.deletePrompt.method,
isLoading:adminApi.default.deletePrompt.isLoading,
}
return await requestUtils.common(requestUrl,params);
}
//取消激活提示词
async deactivatePrompt(id) {
let params = {
id:id,
}
let requestUrl = {
url:adminApi.default.deactivatePrompt.url.replace('{id}',id),
method:adminApi.default.deactivatePrompt.method,
isLoading:adminApi.default.deactivatePrompt.isLoading,
}
return await requestUtils.common(requestUrl,params);
}
//激活提示词
async activatePrompt(id) {
let params = {
id:id,
}
let requestUrl = {
url:adminApi.default.activatePrompt.url.replace('{id}',id),
method:adminApi.default.activatePrompt.method,
isLoading:adminApi.default.activatePrompt.isLoading,
}
return await requestUtils.common(requestUrl,params);
}
//创建提示词
async createPrompt(data) {
let params = {
type: data.type,
title: data.title,
content: data.content,
imageUrls: data.imageUrls,
remark: data.remark
}
return await requestUtils.common(adminApi.default.createPrompt,params);
}
//上传文件返回图片url
async uploadFileCom(file) {
return this.uploadFile(file);//会返回图片url
}
}

View File

@ -59,19 +59,19 @@
center center
> >
<el-form :model="roleForm" label-width="120px" :rules="roleRules" ref="roleFormRef"> <el-form :model="roleForm" label-width="120px" :rules="roleRules" ref="roleFormRef">
<el-form-item label="角色名称" prop="roleName"> <el-form-item :label="t('admin.roleManagement.roleName')" prop="roleName">
<el-input v-model="roleForm.roleName" placeholder="请输入角色名称" /> <el-input v-model="roleForm.roleName" :placeholder="t('admin.roleManagement.roleName')" />
</el-form-item> </el-form-item>
<el-form-item label="角色代码" prop="roleCode"> <el-form-item :label="t('admin.roleManagement.roleCode')" prop="roleCode">
<el-input v-model="roleForm.roleCode" placeholder="请输入角色代码" /> <el-input v-model="roleForm.roleCode" :placeholder="t('admin.roleManagement.roleCode')" />
</el-form-item> </el-form-item>
<el-form-item label="角色描述" prop="description"> <el-form-item :label="t('admin.roleManagement.description')" prop="description">
<el-input type="textarea" v-model="roleForm.description" placeholder="请输入角色描述" rows="3" /> <el-input type="textarea" v-model="roleForm.description" :placeholder="t('admin.roleManagement.description')" rows="3" />
</el-form-item> </el-form-item>
<el-form-item label="是否系统角色"> <el-form-item :label="t('admin.roleManagement.isSystem')">
<el-switch v-model="roleForm.isSystem" /> <el-switch v-model="roleForm.isSystem" />
</el-form-item> </el-form-item>
<el-form-item label="状态"> <el-form-item :label="t('admin.roleManagement.isActive')">
<el-switch v-model="roleForm.isActive" /> <el-switch v-model="roleForm.isActive" />
</el-form-item> </el-form-item>
</el-form> </el-form>

View File

@ -0,0 +1,593 @@
<template>
<div class="admin-user-list">
<h2 class="page-title">{{ t('admin.userList.title') }}</h2>
<div class="user-list-content">
<el-card shadow="hover" class="user-card">
<template #header>
<div class="card-header">
<span>{{ t('admin.userList.userList') }}</span>
<el-button type="primary" size="small" @click="showUserDialog">
{{ t('admin.userList.addUser') }}
</el-button>
</div>
</template>
<div class="card-body">
<div class="search-bar">
<el-input :placeholder="t('admin.userList.searchPlaceholder')" v-model="searchQuery" clearable style="width: 300px;">
<template #append>
<el-button type="primary" @click="searchUsers">
{{ t('admin.common.search') }}
</el-button>
</template>
</el-input>
</div>
<!-- 批量操作工具栏 -->
<div class="batch-operations" v-if="selectedUserIds.length > 0">
<el-button type="danger" size="small" @click="showConfirmDialog('delete')">
{{ t('admin.common.delete') }}
</el-button>
<el-button type="warning" size="small" @click="showConfirmDialog('enable')">
{{ t('admin.userList.enable') }}
</el-button>
<el-button type="warning" size="small" @click="showConfirmDialog('disable')">
{{ t('admin.userList.disable') }}
</el-button>
</div>
<!-- 用户列表表格 -->
<el-table
:data="userList"
style="width: 100%"
v-loading="loading"
@selection-change="handleSelectionChange"
stripe
>
<el-table-column type="selection" width="55" />
<el-table-column prop="username" :label="t('admin.userList.username')" width="180" />
<el-table-column prop="fullName" :label="t('admin.userList.fullName')" width="180" />
<el-table-column prop="email" :label="t('admin.userList.email')" />
<el-table-column prop="roles" :label="t('admin.userList.roles')" width="200">
<template #default="scope">
<el-tag
v-for="role in (scope.row.roles || [])"
:key="role.id"
size="small"
type="info"
style="margin-right: 5px; margin-bottom: 5px;"
>
{{ role.roleName }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="isActive" :label="t('admin.userList.status')" width="160">
<template #default="scope">
<el-switch
v-model="scope.row.isActive"
@change="handleStatusChange(scope.row)"
:active-text="t('admin.userList.active')"
:inactive-text="t('admin.userList.inactive')"
/>
</template>
</el-table-column>
<el-table-column prop="createdAt" :label="t('admin.userList.createTime')" width="180" />
<el-table-column prop="lastLogin" :label="t('admin.userList.lastLogin')" width="180" />
<el-table-column :label="t('admin.common.action')" width="250" fixed="right">
<template #default="scope">
<el-button size="small" type="primary" @click="showUserDialog(scope.row)">
{{ t('admin.common.edit') }}
</el-button>
<el-button size="small" type="warning" @click="showResetPasswordDialog(scope.row)">
{{ t('admin.userList.resetPassword') }}
</el-button>
<el-button size="small" type="danger" @click="showConfirmDialog('delete', [scope.row.id])">
{{ t('admin.common.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页控件 -->
<div class="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</el-card>
</div>
<!-- 用户创建/编辑对话框 -->
<el-dialog
v-model="userDialogVisible"
:title="isEditMode ? t('admin.userList.editUser') : t('admin.userList.addUser')"
width="600px"
>
<el-form :model="userForm" :rules="userFormRules" ref="userFormRef" label-width="100px">
<el-form-item :label="t('admin.userList.username')" prop="username">
<el-input v-model="userForm.username" :placeholder="t('admin.userList.usernamePlaceholder')" />
</el-form-item>
<el-form-item :label="t('admin.userList.fullName')" prop="fullName">
<el-input v-model="userForm.fullName" :placeholder="t('admin.userList.fullNamePlaceholder')" />
</el-form-item>
<el-form-item :label="t('admin.userList.email')" prop="email">
<el-input v-model="userForm.email" :placeholder="t('admin.userList.emailPlaceholder')" type="email" />
</el-form-item>
<el-form-item v-if="!isEditMode" :label="t('admin.userList.password')" prop="password">
<el-input v-model="userForm.password" :placeholder="t('admin.userList.passwordPlaceholder')" type="password" show-password />
</el-form-item>
<el-form-item :label="t('admin.userList.status')" prop="isActive">
<el-switch v-model="userForm.isActive" />
</el-form-item>
<el-form-item :label="t('admin.userList.isSuperuser')" prop="isSuperuser">
<el-switch v-model="userForm.isSuperuser" />
</el-form-item>
<el-form-item :label="t('admin.userList.roles')" prop="roleIds">
<el-select v-model="userForm.roleIds" multiple :placeholder="t('admin.userList.selectRoles')" style="width: 100%">
<el-option
v-for="role in roleList"
:key="role.id"
:label="role.roleName"
:value="role.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="userDialogVisible = false">{{ t('admin.common.cancel') }}</el-button>
<el-button type="primary" @click="submitUserForm">{{ t('admin.common.save') }}</el-button>
</span>
</template>
</el-dialog>
<!-- 密码重置对话框 -->
<el-dialog
v-model="resetPasswordDialogVisible"
:title="t('admin.userList.resetPassword')"
width="400px"
>
<el-form :model="resetPasswordForm" :rules="resetPasswordFormRules" ref="resetPasswordFormRef" label-width="100px">
<el-form-item :label="t('admin.userList.newPassword')" prop="newPassword">
<el-input v-model="resetPasswordForm.newPassword" :placeholder="t('admin.userList.newPasswordPlaceholder')" type="password" show-password />
</el-form-item>
<el-form-item :label="t('admin.userList.confirmPassword')" prop="confirmPassword">
<el-input v-model="resetPasswordForm.confirmPassword" :placeholder="t('admin.userList.confirmPasswordPlaceholder')" type="password" show-password />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="resetPasswordDialogVisible = false">{{ t('admin.common.cancel') }}</el-button>
<el-button type="primary" @click="submitResetPassword">{{ t('admin.common.confirm') }}</el-button>
</span>
</template>
</el-dialog>
<!-- 操作确认对话框 -->
<el-dialog
v-model="confirmDialogVisible"
:title="confirmDialogTitle"
width="400px"
>
<div>{{ confirmDialogContent }}</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="confirmDialogVisible = false">{{ t('admin.common.cancel') }}</el-button>
<el-button type="primary" @click="confirmAction">{{ t('admin.common.confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
import { AdminRoleManagement } from './index.js'
const { t } = useI18n()
// API
const adminRoleManagement = new AdminRoleManagement()
//
const searchQuery = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const loading = ref(false)
//
const userList = ref([])
const roleList = ref([])
// ID
const selectedUserIds = ref([])
//
const userDialogVisible = ref(false)
const resetPasswordDialogVisible = ref(false)
const confirmDialogVisible = ref(false)
//
const isEditMode = ref(false)
// ID
const currentUserId = ref(null)
//
const confirmDialogTitle = ref('')
const confirmDialogContent = ref('')
const confirmActionType = ref('')
const confirmActionIds = ref([])
//
const userForm = reactive({
id: '',
username: '',
fullName: '',
email: '',
password: '',
isActive: true,
isSuperuser: false,
roleIds: []
})
//
const resetPasswordForm = reactive({
newPassword: '',
confirmPassword: ''
})
//
const userFormRef = ref(null)
const resetPasswordFormRef = ref(null)
//
const userFormRules = reactive({
username: [
{ required: true, message: t('admin.userList.usernameRequired'), trigger: 'blur' },
{ min: 3, max: 20, message: t('admin.userList.usernameLength'), trigger: 'blur' }
],
fullName: [
{ required: true, message: t('admin.userList.fullNameRequired'), trigger: 'blur' }
],
email: [
{ required: true, message: t('admin.userList.emailRequired'), trigger: 'blur' },
{ type: 'email', message: t('admin.userList.emailFormat'), trigger: 'blur' }
],
password: [
{ required: true, message: t('admin.userList.passwordRequired'), trigger: 'blur' },
{ min: 6, message: t('admin.userList.passwordLength'), trigger: 'blur' }
],
roleIds: [
{ type: 'array', required: true, message: t('admin.userList.rolesRequired'), trigger: 'change' }
]
})
//
const resetPasswordFormRules = reactive({
newPassword: [
{ required: true, message: t('admin.userList.passwordRequired'), trigger: 'blur' },
{ min: 6, message: t('admin.userList.passwordLength'), trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: t('admin.userList.confirmPasswordRequired'), trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== resetPasswordForm.newPassword) {
callback(new Error(t('admin.userList.passwordsNotMatch')))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
//
const getRoleList = async () => {
try {
const response = await adminRoleManagement.getRoleList()
if (response.success) {
roleList.value = response.data
}
} catch (error) {
ElMessage.error(t('admin.common.requestFailed'))
}
}
//
const getAdminUsers = async () => {
loading.value = true
try {
const params = {
username: searchQuery.value,
email: '',
isActive: undefined,
pageSize: pageSize.value,
pageNum: currentPage.value,
orderByColumn: '',
isAsc: 'asc'
}
const response = await adminRoleManagement.getAdminUsersList(params)
if (response.success) {
userList.value = response.data.rows || []
total.value = response.data.total || 0
}
} catch (error) {
ElMessage.error(t('admin.common.requestFailed'))
} finally {
loading.value = false
}
}
//
const searchUsers = () => {
currentPage.value = 1
getAdminUsers()
}
//
const handleSizeChange = (size) => {
pageSize.value = size
getAdminUsers()
}
const handleCurrentChange = (page) => {
currentPage.value = page
getAdminUsers()
}
//
const handleSelectionChange = (selection) => {
selectedUserIds.value = selection.map(user => user.id)
}
//
const showUserDialog = (user = null) => {
if (user) {
//
isEditMode.value = true
userForm.id = user.id
userForm.username = user.username
userForm.fullName = user.fullName
userForm.email = user.email
userForm.isActive = user.isActive
userForm.isSuperuser = user.isSuperuser
userForm.roleIds = Array.isArray(user.roles) ? user.roles.map(role => role.id) : []
} else {
//
isEditMode.value = false
Object.assign(userForm, {
id: '',
username: '',
fullName: '',
email: '',
password: '',
isActive: true,
isSuperuser: false,
roleIds: []
})
}
userDialogVisible.value = true
}
//
const showResetPasswordDialog = (user) => {
currentUserId.value = user.id
resetPasswordForm.newPassword = ''
resetPasswordForm.confirmPassword = ''
resetPasswordDialogVisible.value = true
}
//
const showConfirmDialog = (actionType, ids = []) => {
confirmActionType.value = actionType
confirmActionIds.value = ids.length > 0 ? ids : selectedUserIds.value
switch (actionType) {
case 'delete':
confirmDialogTitle.value = t('admin.common.deleteConfirm')
confirmDialogContent.value = t('admin.userList.deleteConfirmContent')
break
case 'enable':
confirmDialogTitle.value = t('admin.common.enableConfirm')
confirmDialogContent.value = t('admin.userList.enableConfirmContent')
break
case 'disable':
confirmDialogTitle.value = t('admin.common.disableConfirm')
confirmDialogContent.value = t('admin.userList.disableConfirmContent')
break
}
confirmDialogVisible.value = true
}
//
const submitUserForm = async () => {
if (!userFormRef.value) return
await userFormRef.value.validate(async (valid) => {
if (valid) {
try {
let response
if (isEditMode.value) {
//
response = await adminRoleManagement.updateAdminUser(userForm)
} else {
//
response = await adminRoleManagement.createAdminUser(userForm)
}
if (response.success) {
ElMessage.success(isEditMode.value ? t('admin.userList.editSuccess') : t('admin.userList.addSuccess'))
userDialogVisible.value = false
getAdminUsers()
}
} catch (error) {
ElMessage.error(isEditMode.value ? t('admin.userList.editFailed') : t('admin.userList.addFailed'))
}
}
})
}
//
const submitResetPassword = async () => {
if (!resetPasswordFormRef.value) return
await resetPasswordFormRef.value.validate(async (valid) => {
if (valid) {
try {
const response = await adminRoleManagement.resetUserPassword({
id: currentUserId.value,
newPassword: resetPasswordForm.newPassword
})
if (response.success) {
ElMessage.success(t('admin.userList.resetPasswordSuccess'))
resetPasswordDialogVisible.value = false
}
} catch (error) {
ElMessage.error(t('admin.userList.resetPasswordFailed'))
}
}
})
}
//
const confirmAction = async () => {
try {
let response
switch (confirmActionType.value) {
case 'delete':
response = await adminRoleManagement.deleteAdminUsers({ ids: confirmActionIds.value })
if (response.success) {
ElMessage.success(t('admin.userList.deleteSuccess'))
getAdminUsers()
}
break
case 'enable':
for (const id of confirmActionIds.value) {
await adminRoleManagement.enableDisableUser({ id, isActive: true })
}
ElMessage.success(t('admin.userList.enableSuccess'))
getAdminUsers()
break
case 'disable':
for (const id of confirmActionIds.value) {
await adminRoleManagement.enableDisableUser({ id, isActive: false })
}
ElMessage.success(t('admin.userList.disableSuccess'))
getAdminUsers()
break
}
confirmDialogVisible.value = false
} catch (error) {
ElMessage.error(t('admin.common.requestFailed'))
}
}
//
const handleStatusChange = async (user) => {
try {
const response = await adminRoleManagement.enableDisableUser({
id: user.id,
isActive: user.isActive
})
if (!response.success) {
//
user.isActive = !user.isActive
ElMessage.error(t('admin.common.requestFailed'))
} else {
ElMessage.success(t('admin.userList.statusChangeSuccess'))
}
} catch (error) {
//
user.isActive = !user.isActive
ElMessage.error(t('admin.common.requestFailed'))
}
}
//
onMounted(async () => {
await getRoleList()
await getAdminUsers()
})
</script>
<style scoped>
.admin-user-list {
width: 100%;
height: 100%;
}
.page-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.user-list-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.user-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
padding: 20px 0;
}
.search-bar {
margin-bottom: 20px;
display: flex;
gap: 10px;
align-items: center;
}
.batch-operations {
margin-bottom: 15px;
display: flex;
gap: 10px;
align-items: center;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 深色主题适配 */
[data-theme="dark"] .page-title {
color: #fff;
}
[data-theme="dark"] .batch-operations {
background-color: #1f2937;
}
</style>

View File

@ -228,4 +228,136 @@ export class AdminRoleManagement {
} }
*/ */
} }
//创建管理员用户
async createAdminUser(data) {
let params = {
username: data.username || '',
email: data.email || '',
password: data.password || '',
fullName: data.fullName || '',
isActive: data.isActive !== undefined ? data.isActive : true,
isSuperuser: data.isSuperuser !== undefined ? data.isSuperuser : false,
roleIds: Array.isArray(data.roleIds) ? data.roleIds : []
}
return requestUtils.common(adminApi.default.createAdminUser, params);
}
//分页查询管理员用户列表
async getAdminUsersList(data) {
let params = {
username: data.username || '',
email: data.email || '',
isActive: data.isActive !== undefined ? data.isActive : true,
pageSize: data.pageSize || 10,
pageNum: data.pageNum || 1,
orderByColumn: data.orderByColumn || '',
isAsc: data.isAsc || 'asc'
}
return requestUtils.common(adminApi.default.getAdminUserList, params);
/**
返回示例
{
"code": 0,
"success": true,
"data": {
"total": 9007199254740991,
"rows": [
{
"id": 9007199254740991,
"username": "string",
"email": "string",
"fullName": "string",
"isActive": true,
"isSuperuser": true,
"lastLogin": "2025-12-19T05:33:51.763Z",
"createdAt": "2025-12-19T05:33:51.763Z",
"updatedAt": "2025-12-19T05:33:51.763Z",
"roles": [
{
"id": 9007199254740991,
"roleCode": "string",
"roleName": "string",
"description": "string"
}
]
}
],
"code": 1073741824,
"msg": "string"
},
"message": "操作成功"
}
*/
}
//更新管理员用户
async updateAdminUser(data) {
let params = {
id: data.id,
username: data.username,
email: data.email,
fullName: data.fullName,
isActive: data.isActive,
isSuperuser: data.isSuperuser,
roleIds: data.roleIds
}
return requestUtils.common(adminApi.default.updateAdminUser, params);
}
//启用/禁用用户
async enableDisableUser(data) {
let params = {
userId: data.id,
isActive : data.isActive
}
return requestUtils.common(adminApi.default.toggleAdminUserStatus, params);
}
//重置用户密码
async resetUserPassword(data) {
let params = {
userId: data.id,
newPassword: data.newPassword
}
return requestUtils.common(adminApi.default.resetAdminUserPassword, params);
}
//根据用户ID查询管理员用户详情
async getAdminUserDetail(data) {
let params = {
userId : data.id || ''
}
const requestUrl = {
method: adminApi.default.getAdminUserDetail.method,
url: adminApi.default.getAdminUserDetail.url.replace('USERID', data.id),
isLoading: adminApi.default.getAdminUserDetail.isLoading
}
return requestUtils.common(requestUrl, params);
/**
{
"code": 0,
"success": true,
"data": {
"id": 9007199254740991,
"username": "string",
"email": "string",
"fullName": "string",
"isActive": true,
"isSuperuser": true,
"lastLogin": "2025-12-19T05:36:04.270Z",
"createdAt": "2025-12-19T05:36:04.270Z",
"updatedAt": "2025-12-19T05:36:04.270Z",
"roles": [
{
"id": 9007199254740991,
"roleCode": "string",
"roleName": "string",
"description": "string"
}
]
},
"message": "操作成功"
}
*/
}
//批量删除管理员用户
async deleteAdminUsers(data) {
let arr = data.ids || []
return requestUtils.common(adminApi.default.batchDeleteAdminUser, arr);
}
} }

View File

@ -1,204 +0,0 @@
<template>
<div class="admin-user-list">
<h2 class="page-title">{{ t('admin.userList.title') }}</h2>
<div class="user-list-content">
<el-card shadow="hover" class="user-card">
<template #header>
<div class="card-header">
<span>{{ t('admin.userList.userList') }}</span>
<el-button type="primary" size="small">
{{ t('admin.userList.addUser') }}
</el-button>
</div>
</template>
<div class="card-body">
<div class="search-bar">
<el-input :placeholder="t('admin.userList.searchPlaceholder')" v-model="searchQuery" clearable style="width: 300px;">
<template #append>
<el-button type="primary" @click="searchUsers">
{{ t('admin.common.search') }}
</el-button>
</template>
</el-input>
</div>
<!-- stripe -->
<el-table :data="userList" style="width: 100%" v-loading="loading">
<el-table-column prop="username" :label="t('admin.userList.username')" width="180" />
<el-table-column prop="nickname" :label="t('admin.userList.nickname')" width="180" />
<el-table-column prop="email" :label="t('admin.userList.email')" />
<el-table-column prop="phone" :label="t('admin.userList.phone')" width="180" />
<el-table-column prop="role" :label="t('admin.userList.role')" width="120">
<template #default="scope">
<el-tag :type="scope.row.role === 'admin' ? 'danger' : 'success'">
{{ scope.row.role === 'admin' ? t('admin.userList.admin') : t('admin.userList.user') }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" :label="t('admin.userList.status')" width="120">
<template #default="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'warning'">
{{ scope.row.status === 'active' ? t('admin.userList.active') : t('admin.userList.inactive') }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('admin.userList.createTime')" width="180" />
<el-table-column :label="t('admin.common.action')" width="200" fixed="right">
<template #default="scope">
<el-button size="small" type="primary" @click="editUser(scope.row)">
{{ t('admin.common.edit') }}
</el-button>
<el-button size="small" type="danger" @click="deleteUser(scope.row)">
{{ t('admin.common.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
const { t } = useI18n()
//
const searchQuery = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(100)
const loading = ref(false)
//
const userList = ref([
{
id: 1,
username: 'admin',
nickname: '管理员',
email: 'admin@example.com',
phone: '13800138000',
role: 'admin',
status: 'active',
createTime: '2023-01-01 10:00:00'
},
{
id: 2,
username: 'user1',
nickname: '普通用户',
email: 'user1@example.com',
phone: '13800138001',
role: 'user',
status: 'active',
createTime: '2023-01-02 10:00:00'
},
{
id: 3,
username: 'user2',
nickname: '测试用户',
email: 'user2@example.com',
phone: '13800138002',
role: 'user',
status: 'inactive',
createTime: '2023-01-03 10:00:00'
}
])
//
const searchUsers = () => {
ElMessage.info(t('admin.common.functionUnderDevelopment'))
}
//
const editUser = (row) => {
ElMessage.info(t('admin.common.functionUnderDevelopment'))
}
//
const deleteUser = (row) => {
ElMessage.info(t('admin.common.functionUnderDevelopment'))
}
//
const handleSizeChange = (size) => {
pageSize.value = size
// API
}
const handleCurrentChange = (page) => {
currentPage.value = page
// API
}
onMounted(() => {
// API
})
</script>
<style scoped>
.admin-user-list {
width: 100%;
height: 100%;
}
.page-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.user-list-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.user-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
padding: 20px 0;
}
.search-bar {
margin-bottom: 20px;
display: flex;
gap: 10px;
align-items: center;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 深色主题适配 */
[data-theme="dark"] .page-title {
color: #fff;
}
</style>

View File

@ -116,136 +116,5 @@ export class AdminOrders {
} }
return requestUtils.common(requestUrl, params); return requestUtils.common(requestUrl, params);
} }
//创建管理员用户
async createAdminUser(data) {
let params = {
username: data.username || '',
email: data.email || '',
password: data.password || '',
fullName: data.fullName || '',
isActive: data.isActive !== undefined ? data.isActive : true,
isSuperuser: data.isSuperuser !== undefined ? data.isSuperuser : false,
roleIds: Array.isArray(data.roleIds) ? data.roleIds : []
}
return requestUtils.common(adminApi.default.createAdminUser, params);
}
//分页查询管理员用户列表
async getAdminUsersList(data) {
let params = {
username: data.username || '',
email: data.email || '',
isActive: data.isActive !== undefined ? data.isActive : true,
pageSize: data.pageSize || 10,
pageNum: data.pageNum || 1,
orderByColumn: data.orderByColumn || '',
isAsc: data.isAsc || 'asc'
}
return requestUtils.common(adminApi.default.getAdminUserList, params);
/**
返回示例
{
"code": 0,
"success": true,
"data": {
"total": 9007199254740991,
"rows": [
{
"id": 9007199254740991,
"username": "string",
"email": "string",
"fullName": "string",
"isActive": true,
"isSuperuser": true,
"lastLogin": "2025-12-19T05:33:51.763Z",
"createdAt": "2025-12-19T05:33:51.763Z",
"updatedAt": "2025-12-19T05:33:51.763Z",
"roles": [
{
"id": 9007199254740991,
"roleCode": "string",
"roleName": "string",
"description": "string"
}
]
}
],
"code": 1073741824,
"msg": "string"
},
"message": "操作成功"
}
*/
}
//更新管理员用户
async updateAdminUser(data) {
let params = {
id: data.id,
username: data.username,
email: data.email,
fullName: data.fullName,
isActive: data.isActive,
isSuperuser: data.isSuperuser,
roleIds: data.roleIds
}
return requestUtils.common(adminApi.default.updateAdminUser, params);
}
//启用/禁用用户
async enableDisableUser(data) {
let params = {
userId: data.id,
isActive : data.isActive
}
return requestUtils.common(adminApi.default.toggleAdminUserStatus, params);
}
//重置用户密码
async resetUserPassword(data) {
let params = {
userId: data.id,
newPassword: data.newPassword
}
return requestUtils.common(adminApi.default.resetAdminUserPassword, params);
}
//根据用户ID查询管理员用户详情
async getAdminUserDetail(data) {
let params = {
userId : data.id || ''
}
const requestUrl = {
method: adminApi.default.getAdminUserDetail.method,
url: adminApi.default.getAdminUserDetail.url.replace('USERID', params.id),
isLoading: adminApi.default.getAdminUserDetail.isLoading
}
return requestUtils.common(requestUrl, params);
/**
{
"code": 0,
"success": true,
"data": {
"id": 9007199254740991,
"username": "string",
"email": "string",
"fullName": "string",
"isActive": true,
"isSuperuser": true,
"lastLogin": "2025-12-19T05:36:04.270Z",
"createdAt": "2025-12-19T05:36:04.270Z",
"updatedAt": "2025-12-19T05:36:04.270Z",
"roles": [
{
"id": 9007199254740991,
"roleCode": "string",
"roleName": "string",
"description": "string"
}
]
},
"message": "操作成功"
}
*/
}
//批量删除管理员用户
async deleteAdminUsers(data) {
let arr = data.ids || []
return requestUtils.common(adminApi.default.batchDeleteAdminUser, arr);
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -173,6 +173,10 @@ const handleTouchEnd = () => {
}; };
// //
const props = defineProps({ const props = defineProps({
combinedPromptJson:{//
type: Object,
default: () => ({})
},
// URL // URL
imageUrl: { imageUrl: {
type: String, type: String,
@ -218,69 +222,27 @@ const handleGenerateImage = async () => {
if (props?.cardData?.inspirationImage) { if (props?.cardData?.inspirationImage) {
referenceImages.push(props.cardData.inspirationImage); referenceImages.push(props.cardData.inspirationImage);
} }
if(props?.cardData?.ipType){
if(props?.cardData?.ipType==1){
humanTypeImg&&referenceImages.push(humanTypeImg);
}else{
anTypeImg&&referenceImages.push(anTypeImg);
}
}
if(iscjt){ if(iscjt){
props.cardData.imgyt&&referenceImages.push(props.cardData.imgyt); props.cardData.imgyt&&referenceImages.push(props.cardData.imgyt);
referenceImages.push(props.cardData.diyPromptImg); referenceImages.push(props.cardData.diyPromptImg);
referenceImages.push(cjimg); referenceImages.push(cjimg);
} }
// if(props.cardData.diyPromptText){ if(props.cardData.diyPromptText){
// console.log(props.cardData.diyPromptImg,'diyPromptImgdiyPromptImgdiyPromptImg'); referenceImages.push(props.cardData.diyPromptImg);
// referenceImages.push(props.cardData.diyPromptImg); }
// if(iscjt){ let dtprompt;
// props.cardData.imgyt&&referenceImages.push(props.cardData.imgyt); if(props?.cardData?.ipType==1){
// referenceImages.push(cjimg); dtprompt = props.combinedPromptJson.person.content;
// } referenceImages.push(...props.combinedPromptJson.person.imgs);
// }else{ }else if(props?.cardData?.ipType==2){
// referenceImages.push(humanTypeImg); dtprompt = props.combinedPromptJson.animal.content;
// referenceImages.push(anTypeImg); referenceImages.push(...props.combinedPromptJson.animal.imgs);
// } }
// referenceImages.push(cz2); if(props.cardData.prompt){
// referenceImages.push(humanTypeImg); dtprompt = `角色外观:${props.cardData.prompt}.${dtprompt}`
// if(props?.cardData?.selectedExpression){ }
// referenceImages.push(props.cardData.selectedExpression.imageUrl); let prompt = props.cardData.diyPromptText|| dtprompt
// }
//
let prompt = props.cardData.diyPromptText|| `
首先保证生成的角色符合以下要求
角色肤色和衣服材质都为纯色一种颜色如下
重点保证角色所有的服饰衣服都为木头材质颜色并且要带一些木头纹理颜色为#e2cfb3
一个通体由单一纯色木材雕刻而成的角色全身无布料无皮肤无金属表面光滑颜色均匀一致无纹理变化整体呈现木质雕塑或木偶风格极简设计.
A full-body character portrait
角色特征Q 版萌系造型头身比例夸张大头小身神态纯真服饰设计融合童话风与复古感(简化一下复杂衣服纹理,只保留特征).
Style:潮玩盲盒角色设计采用 3D 立体建模渲染呈现细腻的质感与精致的细节
${props?.cardData?.prompt? `Appearance: ${props?.cardData?.prompt}.`:``}
Note: The image should not have white borders.
去除原图中复杂的背景只保留人物角色的主体
适配3D打印请保持服装边缘装饰等细节略微加厚避免过细结构以提高打印稳定性手指头轮廓清晰重点保证角色全身包括衣服都为木头材质颜色并且要带一些木头纹理颜色为#e2cfb3
3D打印结构优化
模型用于3D打印必须保持结构厚实稳定无细小悬空部件或过薄结构
不生成透明或复杂内构
保持厚度和连贯性适合打印
材质处理
整体需光滑稳固边缘柔和防止打印时断裂
模型应呈现专业3D效果
${props.cardData?.ipType==1?`
调整角色的发型使其厚实蓬松且结构坚固轮廓清晰扎实适合3D打印
确保头发具备足够的厚度与结构完整性避免在打印过程中出现脆弱断裂同时保留原有的可爱美感
头发纹理细节需针对3D制造进行优化层次平滑且分明兼顾视觉吸引力与可打印性维持整体俏皮且高品质的盲盒角色风格
`:`采用疯狂动物城的设计风格`}
调整背景为极简风格换成中性纯白色,让图片中的人物呈现3D立体效果
保证生成的图片一定要有眼睛一定要有嘴巴,眼睛效果要可爱童真Q版大眼睛
角色肤色和衣服材质都为纯色一种颜色如下
保证角色全身都为木头材质颜色并且要带一些木头纹理颜色为#e2cfb3
衣服如果不适合做木制一定要简化衣服不能用复杂的衣服设计保留衣服特征即可衣服一定要纯色木质材质
保证角色所有的服饰衣服都为木头材质颜色并且要带一些木头纹理颜色为#e2cfb3
`
;
// 姿${props.cardData.ipType==1?``:``} // 姿${props.cardData.ipType==1?``:``}
if(props.cardData.prompt&&props.cardData.prompt.indexOf('nospec')!=-1){ if(props.cardData.prompt&&props.cardData.prompt.indexOf('nospec')!=-1){
prompt = '按原图生成' prompt = '按原图生成'
referenceImages = [props.cardData.inspirationImage]; referenceImages = [props.cardData.inspirationImage];

View File

@ -324,7 +324,7 @@ const formData = ref({
const textareaRef = ref(null); const textareaRef = ref(null);
const fileInput = ref(null); // const fileInput = ref(null); //
const generateCount = ref(4); // 1 const generateCount = ref(1); // 1
const isOptimizing = ref(false); // const isOptimizing = ref(false); //
const isDragOver = ref(false); // const isDragOver = ref(false); //
const isUploading = ref(false); // const isUploading = ref(false); //

View File

@ -64,6 +64,7 @@
<!-- 根据卡片类型显示不同组件 --> <!-- 根据卡片类型显示不同组件 -->
<IPCard <IPCard
:combinedPromptJson="combinedPromptJson"
@preview-image="handlePreviewImage" @preview-image="handlePreviewImage"
@generate-smooth-white-model="(imageUrl)=>handleGenerateSmoothWhiteModel(index,imageUrl)" @generate-smooth-white-model="(imageUrl)=>handleGenerateSmoothWhiteModel(index,imageUrl)"
@create-new-card="(data)=>handleCreateFourViewCard(index,data)" @create-new-card="(data)=>handleCreateFourViewCard(index,data)"
@ -73,7 +74,7 @@
@generate-model-requested="(data)=>handleGenerateModelRequested(index,data)" @generate-model-requested="(data)=>handleGenerateModelRequested(index,data)"
@save-project="(item)=>{handleSaveProject(index,item,'image')}" @save-project="(item)=>{handleSaveProject(index,item,'image')}"
@create-remaining-image-data="handleCreateRemainingImageData" @create-remaining-image-data="handleCreateRemainingImageData"
v-if="card.type === 'image'" v-if="card.type === 'image'&&combinedPromptJson.animal"
:cardData="card" :cardData="card"
/> />
<ModelCard <ModelCard
@ -178,6 +179,16 @@ const getGenerateCount = async ()=>{
const cards = ref([ const cards = ref([
]); ]);
const combinedPromptJson = ref({});
//
const getCombinedPrompt = async (type=1)=>{
try {
const data = await PluginProject.getCombinedPrompt(type);
combinedPromptJson.value = data;
} catch (error) {
console.error(error);
}
}
watch(()=>[projectInfo.value,cards.value], () => { watch(()=>[projectInfo.value,cards.value], () => {
let newProjectInfo = {...projectInfo.value}; let newProjectInfo = {...projectInfo.value};
newProjectInfo.details.node_card = cards.value; newProjectInfo.details.node_card = cards.value;
@ -960,6 +971,7 @@ onMounted(() => {
// //
// showGuideModal.value = true; // showGuideModal.value = true;
init(); init();
getCombinedPrompt();
// 使 // 使
const removeWheelListener = addPassiveEventListener(document, 'wheel', preventZoom); const removeWheelListener = addPassiveEventListener(document, 'wheel', preventZoom);
const removeTouchStartListener = addPassiveEventListener(document, 'touchstart', preventPinchZoom); const removeTouchStartListener = addPassiveEventListener(document, 'touchstart', preventPinchZoom);

View File

@ -728,4 +728,69 @@ export class Project{
this.duration_seconds +=1; this.duration_seconds +=1;
}, 1000); }, 1000);
} }
//获取动态提示词
async getCombinedPrompt(){//type:1人物类型2动物类型
try {
return new Promise(async (resolve, reject) => {
const res = await requestUtils.common(clientApi.default.combined)
if(res.code === 0){
let data = res.data;
// 初始化返回数据结构
const result = {
person: {
content: '',
imgs: []
},
animal: {
content: '',
imgs: []
}
};
// 按sortOrder排序
data.sort((a, b) => a.sortOrder - b.sortOrder);
// 处理person和general类型的数据
const personAndGeneral = data.filter(item => item.type === 'person' || item.type === 'general');
personAndGeneral.forEach(item => {
// 拼接content
result.person.content += item.content;
// 处理图片
if (item.imageUrls) {
try {
const images = JSON.parse(item.imageUrls);
if (Array.isArray(images)) {
result.person.imgs.push(...images);
}
} catch (e) {
console.error('解析imageUrls失败:', e);
}
}
});
// 处理animal和general类型的数据
const animalAndGeneral = data.filter(item => item.type === 'animal' || item.type === 'general');
animalAndGeneral.forEach(item => {
// 拼接content
result.animal.content += item.content;
// 处理图片
if (item.imageUrls) {
try {
const images = JSON.parse(item.imageUrls);
if (Array.isArray(images)) {
result.animal.imgs.push(...images);
}
} catch (e) {
console.error('解析imageUrls失败:', e);
}
}
});
resolve(result);
}else{
reject(res.msg)
}
})
} catch (error) {
reject(error)
}
}
} }

View File

@ -5,6 +5,7 @@ import meshy from './meshy.js';
import logistics from './logistics.js'; import logistics from './logistics.js';
import user from './user.js'; import user from './user.js';
import permission from './permission.js'; import permission from './permission.js';
import promptManagement from './promptManagement.js';
export default { export default {
...login, ...login,
...order, ...order,
@ -13,4 +14,5 @@ export default {
...logistics, ...logistics,
...user, ...user,
...permission, ...permission,
...promptManagement,
}; };

View File

@ -2,5 +2,6 @@ const login = {
IMAGE_TO_3DADMIN:{url:'/api-core/admin/meshy/image-to-3d',method:'POST'},// 图片转3D模型任务提交 IMAGE_TO_3DADMIN:{url:'/api-core/admin/meshy/image-to-3d',method:'POST'},// 图片转3D模型任务提交
// FIND_TASK_IDADMIN:{url:'/api-core/front/mesh/image-to-3d/TASKID',method:'GET'},// 获取3D模型任务查询 // FIND_TASK_IDADMIN:{url:'/api-core/front/mesh/image-to-3d/TASKID',method:'GET'},// 获取3D模型任务查询
FIND_TASK_IDADMIN:{url:'/api-core/admin/meshy/ai-record/TASKID',method:'GET'},// 获取3D模型任务查询 FIND_TASK_IDADMIN:{url:'/api-core/admin/meshy/ai-record/TASKID',method:'GET'},// 获取3D模型任务查询
adminUPLOADS3:{url:'/api-core/admin/s3/get-presigned-post',method:'POST'},// 文件直传S3
} }
export default login; export default login;

View File

@ -0,0 +1,11 @@
const promptManagement = {
getPromptDetail:{url:'/api-base/admin/prompt/{id}',method:'GET',isLoading:true},//查询提示词详情
getPromptList:{url:'/api-base/admin/prompt/list',method:'GET',isLoading:true},//分页查询提示词列表
updatePrompt:{url:'/api-base/admin/prompt/update/{id}',method:'POST',isLoading:true},//更新提示词
batchUpdateSort:{url:'/api-base/admin/prompt/sort',method:'POST',isLoading:true},//批量更新排序
deletePrompt:{url:'/api-base/admin/prompt/delete/{id}',method:'POST',isLoading:true},//删除提示词
deactivatePrompt:{url:'/api-base/admin/prompt/deactivate/{id}',method:'POST',isLoading:true},//取消激活提示词
createPrompt:{url:'/api-base/admin/prompt/create',method:'POST',isLoading:true},//创建提示词
activatePrompt:{url:'/api-base/admin/prompt/activate/{id}',method:'POST',isLoading:true},//激活提示词
}
export default promptManagement;

View File

@ -3,5 +3,6 @@ const login = {
USER_STATISTICS:{url:'/api-core/front/user/statistics',method:'GET'},// 用户统计 USER_STATISTICS:{url:'/api-core/front/user/statistics',method:'GET'},// 用户统计
INVITE_CODES:{url:'/api-base/user/invite/codes',method:'GET'},// 返回当前用户的邀请码列表及使用状态 INVITE_CODES:{url:'/api-base/user/invite/codes',method:'GET'},// 返回当前用户的邀请码列表及使用状态
INVITE_RECORDS:{url:'/api-base/user/invite/records',method:'GET'},// 返回邀请的用户列表,包含奖励明细 INVITE_RECORDS:{url:'/api-base/user/invite/records',method:'GET'},// 返回邀请的用户列表,包含奖励明细
combined:{url:'/api-base/prompt/active',method:'GET'},// 返回动态提示词
} }
export default login; export default login;

View File

@ -1,7 +1,21 @@
import { request as requestUtils } from '../utils/request.js' import { request as requestUtils } from '../utils/request.js'
import * as clientApi from '../api/frontend/index.js' import * as clientApi from '../api/frontend/index.js'
import * as adminApi from '../api/FrontendDesigner/index.js'
let urlRule = 'https://api.deotaland.aiIMGURL' let urlRule = 'https://api.deotaland.aiIMGURL'
// 获取环境变量中的
const getPorjectType = () => {
// 浏览器环境
if (typeof window !== 'undefined') {
// Vite 环境变量
return import.meta.env.VITE_PROJECTTYPE;
}
// Node.js 环境
if (typeof process !== 'undefined') {
return process.env.VITE_PROJECTTYPE ;
}
};
export class FileServer { export class FileServer {
RULE = getPorjectType();
//文件上传缓存映射 - 静态属性,所有实例共享 //文件上传缓存映射 - 静态属性,所有实例共享
static fileCacheMap = new Map(); static fileCacheMap = new Map();
constructor() { constructor() {
@ -328,7 +342,9 @@ export class FileServer {
file_type:file.type.split('/')[0], file_type:file.type.split('/')[0],
prefix:'images' prefix:'images'
} }
const response = await requestUtils.common(clientApi.default.UPLOADS3, params); // const requestUrl = this.RULE=='admin'?adminApi.default.adminUPLOADS3:clientApi.default.UPLOADS3;
const requestUrl = clientApi.default.UPLOADS3;
const response = await requestUtils.common(requestUrl, params);
if(response.code==0){ if(response.code==0){
let data = response.data; let data = response.data;
let {url,fields,file_key,file_url } = data; let {url,fields,file_key,file_url } = data;

View File

@ -29,7 +29,11 @@ const service = axios.create({
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
// 从localStorage中获取token如果存在 // 从localStorage中获取token如果存在
const token = localStorage.getItem('token'); // console.log(config,'请求配置');
let token = localStorage.getItem('token');
if(config.url=='/api-core/front/s3/get-presigned-post'){
token = '123'
}
if (token) { if (token) {
// 将token添加到请求头 // 将token添加到请求头
config.headers['Authorization'] = `Bearer ${token}`; config.headers['Authorization'] = `Bearer ${token}`;