|
@@ -0,0 +1,1110 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="zh-CN">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
+ <title>项目管理系统</title>
|
|
|
+ <script src="https://cdn.tailwindcss.com"></script>
|
|
|
+ <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|
|
+ <script>
|
|
|
+ tailwind.config = {
|
|
|
+ theme: {
|
|
|
+ extend: {
|
|
|
+ colors: {
|
|
|
+ primary: '#165DFF',
|
|
|
+ secondary: '#36CFC9',
|
|
|
+ success: '#52C41A',
|
|
|
+ warning: '#FAAD14',
|
|
|
+ danger: '#FF4D4F',
|
|
|
+ neutral: '#8C8C8C',
|
|
|
+ 'neutral-light': '#F5F5F5',
|
|
|
+ 'neutral-dark': '#434343',
|
|
|
+ },
|
|
|
+ fontFamily: {
|
|
|
+ sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ </script>
|
|
|
+ <style type="text/tailwindcss">
|
|
|
+ @layer utilities {
|
|
|
+ .content-auto {
|
|
|
+ content-visibility: auto;
|
|
|
+ }
|
|
|
+ .table-shadow {
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
+ }
|
|
|
+ .transition-bg {
|
|
|
+ transition: background-color 0.2s ease;
|
|
|
+ }
|
|
|
+ .transition-transform {
|
|
|
+ transition: transform 0.2s ease;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body class="bg-gray-50 font-sans text-neutral-dark">
|
|
|
+ <!-- 顶部导航栏 -->
|
|
|
+ <header class="bg-white shadow-sm fixed top-0 left-0 right-0 z-10">
|
|
|
+ <div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
|
|
+ <div class="flex items-center space-x-2">
|
|
|
+ <i class="fa fa-tasks text-primary text-2xl"></i>
|
|
|
+ <h1 class="text-xl font-bold text-primary">项目管理系统</h1>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
+ <button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-100 transition-bg">
|
|
|
+ <i class="fa fa-moon-o text-neutral"></i>
|
|
|
+ </button>
|
|
|
+ <div class="flex items-center space-x-2">
|
|
|
+ <img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-8 h-8 rounded-full object-cover border border-gray-200">
|
|
|
+ <span class="hidden md:inline">管理员</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <!-- 主内容区 -->
|
|
|
+ <div class="container mx-auto pt-16 px-4 pb-20 flex flex-col md:flex-row gap-6">
|
|
|
+ <!-- 侧边栏 -->
|
|
|
+ <aside class="w-full md:w-64 bg-white rounded-lg shadow-sm p-4 mt-6 md:mt-0">
|
|
|
+ <nav class="space-y-1">
|
|
|
+ <p class="text-xs font-semibold text-neutral uppercase tracking-wider mb-2 px-3">主菜单</p>
|
|
|
+ <a href="#projects" class="nav-link block px-3 py-2 rounded-md text-primary bg-primary/10 font-medium">
|
|
|
+ <i class="fa fa-briefcase w-5 inline-block"></i>
|
|
|
+ <span class="ml-2">项目管理</span>
|
|
|
+ </a>
|
|
|
+ <a href="#profiles" class="nav-link block px-3 py-2 rounded-md text-neutral-dark hover:bg-gray-100 transition-bg">
|
|
|
+ <i class="fa fa-users w-5 inline-block"></i>
|
|
|
+ <span class="ml-2">成员档案</span>
|
|
|
+ </a>
|
|
|
+ <a href="#project-teams" class="nav-link block px-3 py-2 rounded-md text-neutral-dark hover:bg-gray-100 transition-bg">
|
|
|
+ <i class="fa fa-sitemap w-5 inline-block"></i>
|
|
|
+ <span class="ml-2">项目成员</span>
|
|
|
+ </a>
|
|
|
+ </nav>
|
|
|
+ </aside>
|
|
|
+
|
|
|
+ <!-- 主要内容 -->
|
|
|
+ <main class="flex-1 mt-6">
|
|
|
+ <!-- 项目管理部分 -->
|
|
|
+ <section id="projects" class="content-section">
|
|
|
+ <div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
|
|
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between mb-6 gap-4">
|
|
|
+ <h2 class="text-xl font-bold">项目管理</h2>
|
|
|
+ <button id="add-project-btn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-md flex items-center transition-bg">
|
|
|
+ <i class="fa fa-plus mr-2"></i>
|
|
|
+ <span>添加项目</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="overflow-x-auto">
|
|
|
+ <table class="min-w-full table-shadow rounded-lg overflow-hidden">
|
|
|
+ <thead class="bg-neutral-light">
|
|
|
+ <tr>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">ID</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">项目标题</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">描述</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">项目天数</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">操作</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="projects-table-body">
|
|
|
+ <!-- 项目数据将通过JavaScript动态加载 -->
|
|
|
+ <tr>
|
|
|
+ <td colspan="5" class="py-10 text-center text-neutral">
|
|
|
+ <div class="flex flex-col items-center">
|
|
|
+ <i class="fa fa-spinner fa-spin text-primary text-2xl mb-2"></i>
|
|
|
+ <span>加载项目数据中...</span>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 成员档案部分 -->
|
|
|
+ <section id="profiles" class="content-section hidden">
|
|
|
+ <div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
|
|
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between mb-6 gap-4">
|
|
|
+ <h2 class="text-xl font-bold">成员档案</h2>
|
|
|
+ <button id="add-profile-btn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-md flex items-center transition-bg">
|
|
|
+ <i class="fa fa-plus mr-2"></i>
|
|
|
+ <span>添加成员</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="overflow-x-auto">
|
|
|
+ <table class="min-w-full table-shadow rounded-lg overflow-hidden">
|
|
|
+ <thead class="bg-neutral-light">
|
|
|
+ <tr>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">ID</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">姓名</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">性别</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">擅长技能</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">操作</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="profiles-table-body">
|
|
|
+ <!-- 成员数据将通过JavaScript动态加载 -->
|
|
|
+ <tr>
|
|
|
+ <td colspan="5" class="py-10 text-center text-neutral">
|
|
|
+ <div class="flex flex-col items-center">
|
|
|
+ <i class="fa fa-spinner fa-spin text-primary text-2xl mb-2"></i>
|
|
|
+ <span>加载成员数据中...</span>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 项目成员部分 -->
|
|
|
+ <section id="project-teams" class="content-section hidden">
|
|
|
+ <div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
|
|
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between mb-6 gap-4">
|
|
|
+ <h2 class="text-xl font-bold">项目成员管理</h2>
|
|
|
+ <div class="flex flex-col sm:flex-row gap-3">
|
|
|
+ <select id="project-select" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
|
|
|
+ <option value="">选择项目</option>
|
|
|
+ <!-- 项目选项将通过JavaScript动态加载 -->
|
|
|
+ </select>
|
|
|
+ <button id="add-team-member-btn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-md flex items-center transition-bg" disabled>
|
|
|
+ <i class="fa fa-plus mr-2"></i>
|
|
|
+ <span>添加成员</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="project-team-details" class="hidden">
|
|
|
+ <h3 class="text-lg font-semibold mb-4" id="current-project-name"></h3>
|
|
|
+ <div class="overflow-x-auto">
|
|
|
+ <table class="min-w-full table-shadow rounded-lg overflow-hidden">
|
|
|
+ <thead class="bg-neutral-light">
|
|
|
+ <tr>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">成员ID</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">姓名</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">性别</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">擅长技能</th>
|
|
|
+ <th class="py-3 px-4 text-left text-sm font-semibold text-neutral-dark">操作</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="team-members-table-body">
|
|
|
+ <!-- 项目成员数据将通过JavaScript动态加载 -->
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="no-project-selected" class="py-10 text-center text-neutral">
|
|
|
+ <p>请先选择一个项目查看和管理成员</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </main>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 添加/编辑项目模态框 -->
|
|
|
+ <div id="project-modal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center p-4">
|
|
|
+ <div class="bg-white rounded-lg shadow-lg w-full max-w-md">
|
|
|
+ <div class="p-5 border-b">
|
|
|
+ <h3 id="project-modal-title" class="text-lg font-bold">添加项目</h3>
|
|
|
+ </div>
|
|
|
+ <div class="p-5">
|
|
|
+ <form id="project-form">
|
|
|
+ <input type="hidden" id="project-id">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="project-title" class="block text-sm font-medium text-neutral-dark mb-1">项目标题</label>
|
|
|
+ <input type="text" id="project-title" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary" required>
|
|
|
+ </div>
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="project-desc" class="block text-sm font-medium text-neutral-dark mb-1">项目描述</label>
|
|
|
+ <textarea id="project-desc" rows="3" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"></textarea>
|
|
|
+ </div>
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="project-avatar" class="block text-sm font-medium text-neutral-dark mb-1">项目图片地址</label>
|
|
|
+ <input type="url" id="project-avatar" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
|
|
|
+ </div>
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="project-duration" class="block text-sm font-medium text-neutral-dark mb-1">项目天数</label>
|
|
|
+ <input type="number" id="project-duration" min="1" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary" required>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ <div class="p-5 border-t flex justify-end gap-3">
|
|
|
+ <button id="cancel-project-btn" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-bg">取消</button>
|
|
|
+ <button id="save-project-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-bg">保存</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 添加/编辑成员模态框 -->
|
|
|
+ <div id="profile-modal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center p-4">
|
|
|
+ <div class="bg-white rounded-lg shadow-lg w-full max-w-md">
|
|
|
+ <div class="p-5 border-b">
|
|
|
+ <h3 id="profile-modal-title" class="text-lg font-bold">添加成员</h3>
|
|
|
+ </div>
|
|
|
+ <div class="p-5">
|
|
|
+ <form id="profile-form">
|
|
|
+ <input type="hidden" id="profile-id">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="profile-name" class="block text-sm font-medium text-neutral-dark mb-1">姓名</label>
|
|
|
+ <input type="text" id="profile-name" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary" required>
|
|
|
+ </div>
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="profile-gender" class="block text-sm font-medium text-neutral-dark mb-1">性别</label>
|
|
|
+ <select id="profile-gender" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary" required>
|
|
|
+ <option value="">请选择</option>
|
|
|
+ <option value="男">男</option>
|
|
|
+ <option value="女">女</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="profile-desc" class="block text-sm font-medium text-neutral-dark mb-1">擅长技能</label>
|
|
|
+ <textarea id="profile-desc" rows="3" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"></textarea>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ <div class="p-5 border-t flex justify-end gap-3">
|
|
|
+ <button id="cancel-profile-btn" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-bg">取消</button>
|
|
|
+ <button id="save-profile-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-bg">保存</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 添加项目成员模态框 -->
|
|
|
+ <div id="add-team-member-modal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center p-4">
|
|
|
+ <div class="bg-white rounded-lg shadow-lg w-full max-w-md">
|
|
|
+ <div class="p-5 border-b">
|
|
|
+ <h3 class="text-lg font-bold">添加项目成员</h3>
|
|
|
+ </div>
|
|
|
+ <div class="p-5">
|
|
|
+ <form id="team-member-form">
|
|
|
+ <input type="hidden" id="team-project-id">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label for="team-profile-id" class="block text-sm font-medium text-neutral-dark mb-1">选择成员</label>
|
|
|
+ <select id="team-profile-id" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary" required>
|
|
|
+ <option value="">请选择成员</option>
|
|
|
+ <!-- 成员选项将通过JavaScript动态加载 -->
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ <div class="p-5 border-t flex justify-end gap-3">
|
|
|
+ <button id="cancel-team-member-btn" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-bg">取消</button>
|
|
|
+ <button id="save-team-member-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-bg">保存</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 确认删除模态框 -->
|
|
|
+ <div id="confirm-delete-modal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center p-4">
|
|
|
+ <div class="bg-white rounded-lg shadow-lg w-full max-w-md">
|
|
|
+ <div class="p-5 border-b">
|
|
|
+ <h3 class="text-lg font-bold text-danger">确认删除</h3>
|
|
|
+ </div>
|
|
|
+ <div class="p-5">
|
|
|
+ <p id="delete-confirmation-message">您确定要删除这项记录吗?此操作不可撤销。</p>
|
|
|
+ </div>
|
|
|
+ <div class="p-5 border-t flex justify-end gap-3">
|
|
|
+ <button id="cancel-delete-btn" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-bg">取消</button>
|
|
|
+ <button id="confirm-delete-btn" class="px-4 py-2 bg-danger text-white rounded-md hover:bg-danger/90 transition-bg">确认删除</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 通知提示 -->
|
|
|
+ <div id="notification" class="fixed bottom-4 right-4 px-6 py-3 rounded-md shadow-lg transform translate-y-20 opacity-0 transition-all duration-300 z-50 flex items-center">
|
|
|
+ <i id="notification-icon" class="mr-2 text-xl"></i>
|
|
|
+ <span id="notification-message"></span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ // 面向对象的API交互类
|
|
|
+ class FmodeQuery {
|
|
|
+ className;
|
|
|
+ where;
|
|
|
+ include;
|
|
|
+
|
|
|
+ constructor(className) {
|
|
|
+ this.className = className;
|
|
|
+ this.where = {};
|
|
|
+ }
|
|
|
+
|
|
|
+ equalTo(key, value) {
|
|
|
+ this.where[key] = value;
|
|
|
+ }
|
|
|
+
|
|
|
+ lessThanAndEqualTo(key, value) {
|
|
|
+ this.where[key] = { $lte: value };
|
|
|
+ }
|
|
|
+
|
|
|
+ greaterThan(key, value) {
|
|
|
+ this.where[key] = { $gt: value };
|
|
|
+ }
|
|
|
+
|
|
|
+ async get() {
|
|
|
+ try {
|
|
|
+ let whereStr = '';
|
|
|
+ if (Object.keys(this.where).length > 0) {
|
|
|
+ whereStr = 'where='+encodeURIComponent(JSON.stringify(this.where));
|
|
|
+ }
|
|
|
+
|
|
|
+ const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}?${whereStr}`, {
|
|
|
+ "headers": {
|
|
|
+ "accept": "*/*",
|
|
|
+ "x-parse-application-id": "dev"
|
|
|
+ },
|
|
|
+ "method": "GET",
|
|
|
+ "mode": "cors",
|
|
|
+ "credentials": "omit"
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+ return data.results.map(item => {
|
|
|
+ const fmodeObject = new FmodeObject(this.className);
|
|
|
+ fmodeObject.set(item);
|
|
|
+ return fmodeObject;
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error in get:', error);
|
|
|
+ showNotification('获取数据失败', 'error');
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async find() {
|
|
|
+ return this.get();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ class FmodeObject {
|
|
|
+ className;
|
|
|
+ id;
|
|
|
+ data;
|
|
|
+
|
|
|
+ constructor(className) {
|
|
|
+ this.className = className;
|
|
|
+ this.data = {};
|
|
|
+ this.id = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ set(data) {
|
|
|
+ this.data = {
|
|
|
+ ...this.data,
|
|
|
+ ...data
|
|
|
+ };
|
|
|
+ if (data?.objectId) {
|
|
|
+ this.id = data.objectId;
|
|
|
+ this.data.objectId = data.objectId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async save() {
|
|
|
+ try {
|
|
|
+ const data = { ...this.data };
|
|
|
+ delete data.objectId;
|
|
|
+ delete data.updatedAt;
|
|
|
+ delete data.createdAt;
|
|
|
+ delete data.ACL;
|
|
|
+
|
|
|
+ const updateId = this.id ? `/${this.id}` : '';
|
|
|
+ const method = this.id ? "PUT" : "POST";
|
|
|
+
|
|
|
+ const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}${updateId}`, {
|
|
|
+ "headers": {
|
|
|
+ "accept": "*/*",
|
|
|
+ "content-type": "application/json",
|
|
|
+ "x-parse-application-id": "dev"
|
|
|
+ },
|
|
|
+ "body": JSON.stringify(data),
|
|
|
+ "method": method,
|
|
|
+ "mode": "cors",
|
|
|
+ "credentials": "omit"
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+ if (result.objectId) {
|
|
|
+ this.id = result.objectId;
|
|
|
+ this.data.objectId = result.objectId;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error in save:', error);
|
|
|
+ showNotification('保存失败', 'error');
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async destroy() {
|
|
|
+ try {
|
|
|
+ if (!this.id) {
|
|
|
+ throw new Error('No object ID to delete');
|
|
|
+ }
|
|
|
+
|
|
|
+ const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
|
|
|
+ "headers": {
|
|
|
+ "accept": "*/*",
|
|
|
+ "x-parse-application-id": "dev"
|
|
|
+ },
|
|
|
+ "method": "DELETE",
|
|
|
+ "mode": "cors",
|
|
|
+ "credentials": "omit"
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error in destroy:', error);
|
|
|
+ showNotification('删除失败', 'error');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 全局变量
|
|
|
+ let currentDeleteItem = null;
|
|
|
+ let currentDeleteType = null;
|
|
|
+
|
|
|
+ // DOM 元素加载完成后执行
|
|
|
+ document.addEventListener('DOMContentLoaded', () => {
|
|
|
+ // 加载所有数据
|
|
|
+ loadAllData();
|
|
|
+
|
|
|
+ // 导航链接切换
|
|
|
+ document.querySelectorAll('.nav-link').forEach(link => {
|
|
|
+ link.addEventListener('click', (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ // 更新导航样式
|
|
|
+ document.querySelectorAll('.nav-link').forEach(item => {
|
|
|
+ item.classList.remove('text-primary', 'bg-primary/10');
|
|
|
+ item.classList.add('text-neutral-dark', 'hover:bg-gray-100');
|
|
|
+ });
|
|
|
+ link.classList.remove('text-neutral-dark', 'hover:bg-gray-100');
|
|
|
+ link.classList.add('text-primary', 'bg-primary/10');
|
|
|
+
|
|
|
+ // 显示对应的内容区域
|
|
|
+ const targetId = link.getAttribute('href').substring(1);
|
|
|
+ document.querySelectorAll('.content-section').forEach(section => {
|
|
|
+ section.classList.add('hidden');
|
|
|
+ });
|
|
|
+ document.getElementById(targetId).classList.remove('hidden');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 项目相关事件
|
|
|
+ document.getElementById('add-project-btn').addEventListener('click', () => {
|
|
|
+ openProjectModal();
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('cancel-project-btn').addEventListener('click', () => {
|
|
|
+ closeProjectModal();
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('save-project-btn').addEventListener('click', saveProject);
|
|
|
+
|
|
|
+ // 成员相关事件
|
|
|
+ document.getElementById('add-profile-btn').addEventListener('click', () => {
|
|
|
+ openProfileModal();
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('cancel-profile-btn').addEventListener('click', () => {
|
|
|
+ closeProfileModal();
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('save-profile-btn').addEventListener('click', saveProfile);
|
|
|
+
|
|
|
+ // 项目成员相关事件
|
|
|
+ document.getElementById('project-select').addEventListener('change', (e) => {
|
|
|
+ const projectId = e.target.value;
|
|
|
+ if (projectId) {
|
|
|
+ document.getElementById('project-team-details').classList.remove('hidden');
|
|
|
+ document.getElementById('no-project-selected').classList.add('hidden');
|
|
|
+ document.getElementById('add-team-member-btn').removeAttribute('disabled');
|
|
|
+
|
|
|
+ // 加载项目成员
|
|
|
+ loadProjectMembers(projectId);
|
|
|
+
|
|
|
+ // 更新当前项目名称
|
|
|
+ const option = e.target.options[e.target.selectedIndex];
|
|
|
+ document.getElementById('current-project-name').textContent = `项目: ${option.textContent}`;
|
|
|
+ } else {
|
|
|
+ document.getElementById('project-team-details').classList.add('hidden');
|
|
|
+ document.getElementById('no-project-selected').classList.remove('hidden');
|
|
|
+ document.getElementById('add-team-member-btn').setAttribute('disabled', 'true');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('add-team-member-btn').addEventListener('click', () => {
|
|
|
+ const projectId = document.getElementById('project-select').value;
|
|
|
+ if (projectId) {
|
|
|
+ openAddTeamMemberModal(projectId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('cancel-team-member-btn').addEventListener('click', () => {
|
|
|
+ closeAddTeamMemberModal();
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('save-team-member-btn').addEventListener('click', saveTeamMember);
|
|
|
+
|
|
|
+ // 确认删除相关事件
|
|
|
+ document.getElementById('cancel-delete-btn').addEventListener('click', closeDeleteModal);
|
|
|
+ document.getElementById('confirm-delete-btn').addEventListener('click', confirmDelete);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 加载所有数据
|
|
|
+ async function loadAllData() {
|
|
|
+ await Promise.all([
|
|
|
+ loadProjects(),
|
|
|
+ loadProfiles()
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载项目列表
|
|
|
+ async function loadProjects() {
|
|
|
+ try {
|
|
|
+ const query = new FmodeQuery("Project");
|
|
|
+ const projects = await query.find();
|
|
|
+
|
|
|
+ const tableBody = document.getElementById('projects-table-body');
|
|
|
+ tableBody.innerHTML = '';
|
|
|
+
|
|
|
+ if (projects.length === 0) {
|
|
|
+ tableBody.innerHTML = `
|
|
|
+ <tr>
|
|
|
+ <td colspan="5" class="py-10 text-center text-neutral">
|
|
|
+ <p>暂无项目数据</p>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ projects.forEach(project => {
|
|
|
+ const row = document.createElement('tr');
|
|
|
+ row.className = 'border-b hover:bg-gray-50 transition-bg';
|
|
|
+ row.innerHTML = `
|
|
|
+ <td class="py-3 px-4">${project.id}</td>
|
|
|
+ <td class="py-3 px-4">${project.data.title || '-'}</td>
|
|
|
+ <td class="py-3 px-4 max-w-xs truncate">${project.data.desc || '-'}</td>
|
|
|
+ <td class="py-3 px-4">${project.data.duration || '-'}</td>
|
|
|
+ <td class="py-3 px-4">
|
|
|
+ <div class="flex space-x-2">
|
|
|
+ <button class="text-primary hover:text-primary/80 p-1" onclick="editProject('${project.id}')">
|
|
|
+ <i class="fa fa-edit"></i>
|
|
|
+ </button>
|
|
|
+ <button class="text-danger hover:text-danger/80 p-1" onclick="confirmDeleteProject('${project.id}')">
|
|
|
+ <i class="fa fa-trash"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ `;
|
|
|
+ tableBody.appendChild(row);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新项目选择下拉框
|
|
|
+ const projectSelect = document.getElementById('project-select');
|
|
|
+ // 保存当前选中值
|
|
|
+ const currentValue = projectSelect.value;
|
|
|
+ // 清除现有选项(保留第一个)
|
|
|
+ while (projectSelect.options.length > 1) {
|
|
|
+ projectSelect.remove(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ projects.forEach(project => {
|
|
|
+ const option = document.createElement('option');
|
|
|
+ option.value = project.id;
|
|
|
+ option.textContent = project.data.title || `项目 ${project.id}`;
|
|
|
+ projectSelect.appendChild(option);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 恢复选中值
|
|
|
+ if (currentValue) {
|
|
|
+ projectSelect.value = currentValue;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error loading projects:', error);
|
|
|
+ showNotification('加载项目失败', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载成员列表
|
|
|
+ async function loadProfiles() {
|
|
|
+ try {
|
|
|
+ const query = new FmodeQuery("Profile");
|
|
|
+ const profiles = await query.find();
|
|
|
+
|
|
|
+ const tableBody = document.getElementById('profiles-table-body');
|
|
|
+ tableBody.innerHTML = '';
|
|
|
+
|
|
|
+ if (profiles.length === 0) {
|
|
|
+ tableBody.innerHTML = `
|
|
|
+ <tr>
|
|
|
+ <td colspan="5" class="py-10 text-center text-neutral">
|
|
|
+ <p>暂无成员数据</p>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ profiles.forEach(profile => {
|
|
|
+ const row = document.createElement('tr');
|
|
|
+ row.className = 'border-b hover:bg-gray-50 transition-bg';
|
|
|
+ row.innerHTML = `
|
|
|
+ <td class="py-3 px-4">${profile.id}</td>
|
|
|
+ <td class="py-3 px-4">${profile.data.name || '-'}</td>
|
|
|
+ <td class="py-3 px-4">${profile.data.gender || '-'}</td>
|
|
|
+ <td class="py-3 px-4">${profile.data.desc || '-'}</td>
|
|
|
+ <td class="py-3 px-4">
|
|
|
+ <div class="flex space-x-2">
|
|
|
+ <button class="text-primary hover:text-primary/80 p-1" onclick="editProfile('${profile.id}')">
|
|
|
+ <i class="fa fa-edit"></i>
|
|
|
+ </button>
|
|
|
+ <button class="text-danger hover:text-danger/80 p-1" onclick="confirmDeleteProfile('${profile.id}')">
|
|
|
+ <i class="fa fa-trash"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ `;
|
|
|
+ tableBody.appendChild(row);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新成员选择下拉框(用于添加项目成员)
|
|
|
+ const profileSelect = document.getElementById('team-profile-id');
|
|
|
+ // 清除现有选项(保留第一个)
|
|
|
+ while (profileSelect.options.length > 1) {
|
|
|
+ profileSelect.remove(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ profiles.forEach(profile => {
|
|
|
+ const option = document.createElement('option');
|
|
|
+ option.value = profile.id;
|
|
|
+ option.textContent = profile.data.name || `成员 ${profile.id}`;
|
|
|
+ profileSelect.appendChild(option);
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error loading profiles:', error);
|
|
|
+ showNotification('加载成员失败', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载项目成员
|
|
|
+ async function loadProjectMembers(projectId) {
|
|
|
+ try {
|
|
|
+ const query = new FmodeQuery("ProjectTeam");
|
|
|
+ query.equalTo("project", { __type: "Pointer", className: "Project", objectId: projectId });
|
|
|
+ const teamMembers = await query.find();
|
|
|
+
|
|
|
+ // 获取所有成员ID
|
|
|
+ const profileIds = teamMembers.map(member => member.data.profile?.objectId).filter(Boolean);
|
|
|
+
|
|
|
+ // 获取成员详细信息
|
|
|
+ let profiles = [];
|
|
|
+ if (profileIds.length > 0) {
|
|
|
+ const profileQuery = new FmodeQuery("Profile");
|
|
|
+ profileQuery.where = { objectId: { $in: profileIds } };
|
|
|
+ profiles = await profileQuery.find();
|
|
|
+ }
|
|
|
+
|
|
|
+ const tableBody = document.getElementById('team-members-table-body');
|
|
|
+ tableBody.innerHTML = '';
|
|
|
+
|
|
|
+ if (profiles.length === 0) {
|
|
|
+ tableBody.innerHTML = `
|
|
|
+ <tr>
|
|
|
+ <td colspan="5" class="py-10 text-center text-neutral">
|
|
|
+ <p>该项目暂无成员,请添加成员</p>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 为每个成员找到对应的team记录ID,以便删除
|
|
|
+ profiles.forEach(profile => {
|
|
|
+ const teamMember = teamMembers.find(m => m.data.profile?.objectId === profile.id);
|
|
|
+ const row = document.createElement('tr');
|
|
|
+ row.className = 'border-b hover:bg-gray-50 transition-bg';
|
|
|
+ row.innerHTML = `
|
|
|
+ <td class="py-3 px-4">${profile.id}</td>
|
|
|
+ <td class="py-3 px-4">${profile.data.name || '-'}</td>
|
|
|
+ <td class="py-3 px-4">${profile.data.gender || '-'}</td>
|
|
|
+ <td class="py-3 px-4">${profile.data.desc || '-'}</td>
|
|
|
+ <td class="py-3 px-4">
|
|
|
+ <button class="text-danger hover:text-danger/80 p-1" onclick="confirmDeleteTeamMember('${teamMember.id}', '${projectId}')">
|
|
|
+ <i class="fa fa-trash"></i>
|
|
|
+ </button>
|
|
|
+ </td>
|
|
|
+ `;
|
|
|
+ tableBody.appendChild(row);
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error loading project members:', error);
|
|
|
+ showNotification('加载项目成员失败', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开项目模态框
|
|
|
+ function openProjectModal(projectId = null) {
|
|
|
+ document.getElementById('project-modal-title').textContent = projectId ? '编辑项目' : '添加项目';
|
|
|
+ document.getElementById('project-form').reset();
|
|
|
+ document.getElementById('project-id').value = '';
|
|
|
+
|
|
|
+ if (projectId) {
|
|
|
+ // 加载项目数据
|
|
|
+ const query = new FmodeQuery("Project");
|
|
|
+ query.equalTo("objectId", projectId);
|
|
|
+ query.find().then(projects => {
|
|
|
+ if (projects.length > 0) {
|
|
|
+ const project = projects[0];
|
|
|
+ document.getElementById('project-id').value = project.id;
|
|
|
+ document.getElementById('project-title').value = project.data.title || '';
|
|
|
+ document.getElementById('project-desc').value = project.data.desc || '';
|
|
|
+ document.getElementById('project-avatar').value = project.data.avatar || '';
|
|
|
+ document.getElementById('project-duration').value = project.data.duration || '';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ document.getElementById('project-modal').classList.remove('hidden');
|
|
|
+ document.getElementById('project-modal').classList.add('flex');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭项目模态框
|
|
|
+ function closeProjectModal() {
|
|
|
+ document.getElementById('project-modal').classList.remove('flex');
|
|
|
+ document.getElementById('project-modal').classList.add('hidden');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存项目
|
|
|
+ async function saveProject() {
|
|
|
+ const projectId = document.getElementById('project-id').value;
|
|
|
+ const title = document.getElementById('project-title').value;
|
|
|
+ const desc = document.getElementById('project-desc').value;
|
|
|
+ const avatar = document.getElementById('project-avatar').value;
|
|
|
+ const duration = document.getElementById('project-duration').value;
|
|
|
+
|
|
|
+ if (!title || !duration) {
|
|
|
+ showNotification('请填写必填字段', 'warning');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const project = new FmodeObject("Project");
|
|
|
+ if (projectId) {
|
|
|
+ project.id = projectId;
|
|
|
+ }
|
|
|
+
|
|
|
+ project.set({
|
|
|
+ title,
|
|
|
+ desc,
|
|
|
+ avatar,
|
|
|
+ duration: parseInt(duration)
|
|
|
+ });
|
|
|
+
|
|
|
+ await project.save();
|
|
|
+ closeProjectModal();
|
|
|
+ await loadProjects();
|
|
|
+ showNotification(projectId ? '项目更新成功' : '项目创建成功', 'success');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error saving project:', error);
|
|
|
+ showNotification('保存项目失败', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开成员模态框
|
|
|
+ function openProfileModal(profileId = null) {
|
|
|
+ document.getElementById('profile-modal-title').textContent = profileId ? '编辑成员' : '添加成员';
|
|
|
+ document.getElementById('profile-form').reset();
|
|
|
+ document.getElementById('profile-id').value = '';
|
|
|
+
|
|
|
+ if (profileId) {
|
|
|
+ // 加载成员数据
|
|
|
+ const query = new FmodeQuery("Profile");
|
|
|
+ query.equalTo("objectId", profileId);
|
|
|
+ query.find().then(profiles => {
|
|
|
+ if (profiles.length > 0) {
|
|
|
+ const profile = profiles[0];
|
|
|
+ document.getElementById('profile-id').value = profile.id;
|
|
|
+ document.getElementById('profile-name').value = profile.data.name || '';
|
|
|
+ document.getElementById('profile-gender').value = profile.data.gender || '';
|
|
|
+ document.getElementById('profile-desc').value = profile.data.desc || '';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ document.getElementById('profile-modal').classList.remove('hidden');
|
|
|
+ document.getElementById('profile-modal').classList.add('flex');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭成员模态框
|
|
|
+ function closeProfileModal() {
|
|
|
+ document.getElementById('profile-modal').classList.remove('flex');
|
|
|
+ document.getElementById('profile-modal').classList.add('hidden');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存成员
|
|
|
+ async function saveProfile() {
|
|
|
+ const profileId = document.getElementById('profile-id').value;
|
|
|
+ const name = document.getElementById('profile-name').value;
|
|
|
+ const gender = document.getElementById('profile-gender').value;
|
|
|
+ const desc = document.getElementById('profile-desc').value;
|
|
|
+
|
|
|
+ if (!name || !gender) {
|
|
|
+ showNotification('请填写必填字段', 'warning');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const profile = new FmodeObject("Profile");
|
|
|
+ if (profileId) {
|
|
|
+ profile.id = profileId;
|
|
|
+ }
|
|
|
+
|
|
|
+ profile.set({
|
|
|
+ name,
|
|
|
+ gender,
|
|
|
+ desc
|
|
|
+ });
|
|
|
+
|
|
|
+ await profile.save();
|
|
|
+ closeProfileModal();
|
|
|
+ await loadProfiles();
|
|
|
+ showNotification(profileId ? '成员更新成功' : '成员创建成功', 'success');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error saving profile:', error);
|
|
|
+ showNotification('保存成员失败', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开添加项目成员模态框
|
|
|
+ function openAddTeamMemberModal(projectId) {
|
|
|
+ document.getElementById('team-project-id').value = projectId;
|
|
|
+ document.getElementById('team-profile-id').value = '';
|
|
|
+ document.getElementById('add-team-member-modal').classList.remove('hidden');
|
|
|
+ document.getElementById('add-team-member-modal').classList.add('flex');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭添加项目成员模态框
|
|
|
+ function closeAddTeamMemberModal() {
|
|
|
+ document.getElementById('add-team-member-modal').classList.remove('flex');
|
|
|
+ document.getElementById('add-team-member-modal').classList.add('hidden');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存项目成员
|
|
|
+ async function saveTeamMember() {
|
|
|
+ const projectId = document.getElementById('team-project-id').value;
|
|
|
+ const profileId = document.getElementById('team-profile-id').value;
|
|
|
+
|
|
|
+ if (!projectId || !profileId) {
|
|
|
+ showNotification('请选择项目和成员', 'warning');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 检查是否已存在该关联
|
|
|
+ const query = new FmodeQuery("ProjectTeam");
|
|
|
+ query.equalTo("project", { __type: "Pointer", className: "Project", objectId: projectId });
|
|
|
+ query.equalTo("profile", { __type: "Pointer", className: "Profile", objectId: profileId });
|
|
|
+ const existing = await query.find();
|
|
|
+
|
|
|
+ if (existing.length > 0) {
|
|
|
+ showNotification('该成员已在项目中', 'warning');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const teamMember = new FmodeObject("ProjectTeam");
|
|
|
+ teamMember.set({
|
|
|
+ project: { __type: "Pointer", className: "Project", objectId: projectId },
|
|
|
+ profile: { __type: "Pointer", className: "Profile", objectId: profileId }
|
|
|
+ });
|
|
|
+
|
|
|
+ await teamMember.save();
|
|
|
+ closeAddTeamMemberModal();
|
|
|
+ await loadProjectMembers(projectId);
|
|
|
+ showNotification('成员已添加到项目', 'success');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error saving team member:', error);
|
|
|
+ showNotification('添加项目成员失败', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开删除确认模态框
|
|
|
+ function openDeleteModal(itemId, type, extraData = null) {
|
|
|
+ currentDeleteItem = { id: itemId, extraData };
|
|
|
+ currentDeleteType = type;
|
|
|
+
|
|
|
+ let message = '';
|
|
|
+ switch (type) {
|
|
|
+ case 'project':
|
|
|
+ message = '您确定要删除这个项目吗?相关的项目成员关联也将被删除。';
|
|
|
+ break;
|
|
|
+ case 'profile':
|
|
|
+ message = '您确定要删除这个成员吗?该成员在所有项目中的关联也将被删除。';
|
|
|
+ break;
|
|
|
+ case 'teamMember':
|
|
|
+ message = '您确定要将这个成员从项目中移除吗?';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ message = '您确定要删除这项记录吗?此操作不可撤销。';
|
|
|
+ }
|
|
|
+
|
|
|
+ document.getElementById('delete-confirmation-message').textContent = message;
|
|
|
+ document.getElementById('confirm-delete-modal').classList.remove('hidden');
|
|
|
+ document.getElementById('confirm-delete-modal').classList.add('flex');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭删除确认模态框
|
|
|
+ function closeDeleteModal() {
|
|
|
+ currentDeleteItem = null;
|
|
|
+ currentDeleteType = null;
|
|
|
+ document.getElementById('confirm-delete-modal').classList.remove('flex');
|
|
|
+ document.getElementById('confirm-delete-modal').classList.add('hidden');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认删除
|
|
|
+ async function confirmDelete() {
|
|
|
+ if (!currentDeleteItem || !currentDeleteType) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ switch (currentDeleteType) {
|
|
|
+ case 'project':
|
|
|
+ // 先删除相关的项目成员关联
|
|
|
+ const teamQuery = new FmodeQuery("ProjectTeam");
|
|
|
+ teamQuery.equalTo("project", { __type: "Pointer", className: "Project", objectId: currentDeleteItem.id });
|
|
|
+ const teamMembers = await teamQuery.find();
|
|
|
+ for (const member of teamMembers) {
|
|
|
+ await member.destroy();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 再删除项目
|
|
|
+ const project = new FmodeObject("Project");
|
|
|
+ project.id = currentDeleteItem.id;
|
|
|
+ await project.destroy();
|
|
|
+
|
|
|
+ await loadProjects();
|
|
|
+ showNotification('项目已删除', 'success');
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'profile':
|
|
|
+ // 先删除相关的项目成员关联
|
|
|
+ const profileTeamQuery = new FmodeQuery("ProjectTeam");
|
|
|
+ profileTeamQuery.equalTo("profile", { __type: "Pointer", className: "Profile", objectId: currentDeleteItem.id });
|
|
|
+ const profileTeamMembers = await profileTeamQuery.find();
|
|
|
+ for (const member of profileTeamMembers) {
|
|
|
+ await member.destroy();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 再删除成员
|
|
|
+ const profile = new FmodeObject("Profile");
|
|
|
+ profile.id = currentDeleteItem.id;
|
|
|
+ await profile.destroy();
|
|
|
+
|
|
|
+ await loadProfiles();
|
|
|
+ // 如果有项目被选中,刷新项目成员列表
|
|
|
+ const selectedProject = document.getElementById('project-select').value;
|
|
|
+ if (selectedProject) {
|
|
|
+ await loadProjectMembers(selectedProject);
|
|
|
+ }
|
|
|
+ showNotification('成员已删除', 'success');
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'teamMember':
|
|
|
+ const teamMember = new FmodeObject("ProjectTeam");
|
|
|
+ teamMember.id = currentDeleteItem.id;
|
|
|
+ await teamMember.destroy();
|
|
|
+
|
|
|
+ await loadProjectMembers(currentDeleteItem.extraData);
|
|
|
+ showNotification('成员已从项目中移除', 'success');
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ closeDeleteModal();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error deleting item:', error);
|
|
|
+ showNotification('删除失败', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示通知
|
|
|
+ function showNotification(message, type = 'info') {
|
|
|
+ const notification = document.getElementById('notification');
|
|
|
+ const icon = document.getElementById('notification-icon');
|
|
|
+ const messageEl = document.getElementById('notification-message');
|
|
|
+
|
|
|
+ // 设置通知类型样式
|
|
|
+ notification.className = 'fixed bottom-4 right-4 px-6 py-3 rounded-md shadow-lg transform translate-y-20 opacity-0 transition-all duration-300 z-50 flex items-center';
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case 'success':
|
|
|
+ notification.classList.add('bg-success', 'text-white');
|
|
|
+ icon.className = 'fa fa-check-circle mr-2 text-xl';
|
|
|
+ break;
|
|
|
+ case 'error':
|
|
|
+ notification.classList.add('bg-danger', 'text-white');
|
|
|
+ icon.className = 'fa fa-times-circle mr-2 text-xl';
|
|
|
+ break;
|
|
|
+ case 'warning':
|
|
|
+ notification.classList.add('bg-warning', 'text-white');
|
|
|
+ icon.className = 'fa fa-exclamation-triangle mr-2 text-xl';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ notification.classList.add('bg-primary', 'text-white');
|
|
|
+ icon.className = 'fa fa-info-circle mr-2 text-xl';
|
|
|
+ }
|
|
|
+
|
|
|
+ messageEl.textContent = message;
|
|
|
+
|
|
|
+ // 显示通知
|
|
|
+ setTimeout(() => {
|
|
|
+ notification.classList.remove('translate-y-20', 'opacity-0');
|
|
|
+ }, 10);
|
|
|
+
|
|
|
+ // 3秒后隐藏通知
|
|
|
+ setTimeout(() => {
|
|
|
+ notification.classList.add('translate-y-20', 'opacity-0');
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 全局函数,用于HTML中的事件处理
|
|
|
+ window.editProject = function(projectId) {
|
|
|
+ openProjectModal(projectId);
|
|
|
+ };
|
|
|
+
|
|
|
+ window.confirmDeleteProject = function(projectId) {
|
|
|
+ openDeleteModal(projectId, 'project');
|
|
|
+ };
|
|
|
+
|
|
|
+ window.editProfile = function(profileId) {
|
|
|
+ openProfileModal(profileId);
|
|
|
+ };
|
|
|
+
|
|
|
+ window.confirmDeleteProfile = function(profileId) {
|
|
|
+ openDeleteModal(profileId, 'profile');
|
|
|
+ };
|
|
|
+
|
|
|
+ window.confirmDeleteTeamMember = function(teamMemberId, projectId) {
|
|
|
+ openDeleteModal(teamMemberId, 'teamMember', projectId);
|
|
|
+ };
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+</html>
|