project-process.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Project管理系统</title>
  7. <!-- 引入Tailwind CSS -->
  8. <script src="https://cdn.tailwindcss.com"></script>
  9. <!-- 引入Font Awesome -->
  10. <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
  11. <!-- 配置Tailwind自定义颜色 -->
  12. <script>
  13. tailwind.config = {
  14. theme: {
  15. extend: {
  16. colors: {
  17. primary: '#3B82F6',
  18. secondary: '#10B981',
  19. danger: '#EF4444',
  20. neutral: '#64748B',
  21. },
  22. fontFamily: {
  23. sans: ['Inter', 'system-ui', 'sans-serif'],
  24. },
  25. }
  26. }
  27. }
  28. </script>
  29. <!-- 自定义工具类 -->
  30. <style type="text/tailwindcss">
  31. @layer utilities {
  32. .content-auto {
  33. content-visibility: auto;
  34. }
  35. .card-shadow {
  36. box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  37. }
  38. .transition-custom {
  39. transition: all 0.3s ease;
  40. }
  41. }
  42. </style>
  43. </head>
  44. <body class="bg-gray-50 min-h-screen">
  45. <div class="container mx-auto px-4 py-8 max-w-6xl">
  46. <header class="mb-10 text-center">
  47. <h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-gray-800 mb-2">Project管理系统</h1>
  48. <p class="text-gray-600">通过网络请求实现Project对象的增删查改操作</p>
  49. </header>
  50. <!-- 主要内容区 -->
  51. <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
  52. <!-- 左侧:添加/编辑项目表单 -->
  53. <div class="lg:col-span-1">
  54. <div class="bg-white rounded-xl p-6 card-shadow">
  55. <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
  56. <i class="fa fa-plus-circle text-primary mr-2"></i>
  57. <span id="formTitle">添加新项目</span>
  58. </h2>
  59. <form id="projectForm" class="space-y-4">
  60. <input type="hidden" id="projectId">
  61. <div>
  62. <label for="title" class="block text-sm font-medium text-gray-700 mb-1">项目标题</label>
  63. <input type="text" id="title"
  64. class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-custom"
  65. placeholder="请输入项目标题" required>
  66. </div>
  67. <div>
  68. <label for="duration" class="block text-sm font-medium text-gray-700 mb-1">项目时长(天)</label>
  69. <input type="number" id="duration" min="1"
  70. class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-custom"
  71. placeholder="请输入项目时长" required>
  72. </div>
  73. <div class="flex gap-3 pt-2">
  74. <button type="submit" id="submitBtn"
  75. class="flex-1 bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-lg transition-custom flex items-center justify-center">
  76. <i class="fa fa-save mr-2"></i> 保存项目
  77. </button>
  78. <button type="button" id="cancelBtn"
  79. class="hidden bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg transition-custom">
  80. 取消
  81. </button>
  82. </div>
  83. </form>
  84. </div>
  85. <!-- 查询区域 -->
  86. <div class="bg-white rounded-xl p-6 card-shadow mt-6">
  87. <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
  88. <i class="fa fa-search text-primary mr-2"></i>
  89. 查询项目
  90. </h2>
  91. <div class="space-y-4">
  92. <button onclick="queryAllProjects()"
  93. class="w-full bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-2 px-4 rounded-lg transition-custom flex items-center justify-center">
  94. <i class="fa fa-list mr-2"></i> 查询所有项目
  95. </button>
  96. <div>
  97. <label for="searchTitle" class="block text-sm font-medium text-gray-700 mb-1">按标题查询</label>
  98. <div class="flex">
  99. <input type="text" id="searchTitle"
  100. class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-custom"
  101. placeholder="请输入项目标题">
  102. <button onclick="queryProjectByTitle()"
  103. class="bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-r-lg transition-custom">
  104. <i class="fa fa-search"></i>
  105. </button>
  106. </div>
  107. </div>
  108. <div>
  109. <label for="searchRegex" class="block text-sm font-medium text-gray-700 mb-1">按标题前缀查询</label>
  110. <div class="flex">
  111. <input type="text" id="searchRegex"
  112. class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-custom"
  113. placeholder="请输入标题前缀">
  114. <button onclick="queryProjectByRegex()"
  115. class="bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-r-lg transition-custom">
  116. <i class="fa fa-search"></i>
  117. </button>
  118. </div>
  119. </div>
  120. </div>
  121. </div>
  122. </div>
  123. <!-- 右侧:项目列表 -->
  124. <div class="lg:col-span-2">
  125. <div class="bg-white rounded-xl p-6 card-shadow h-full">
  126. <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
  127. <i class="fa fa-table text-primary mr-2"></i>
  128. 项目列表
  129. </h2>
  130. <div id="loading" class="hidden text-center py-10">
  131. <i class="fa fa-spinner fa-spin text-2xl text-primary"></i>
  132. <p class="mt-2 text-gray-600">加载中...</p>
  133. </div>
  134. <div id="emptyState" class="text-center py-16 hidden">
  135. <i class="fa fa-folder-open-o text-4xl text-gray-300 mb-4"></i>
  136. <p class="text-gray-500">暂无项目数据,请添加项目或查询数据</p>
  137. </div>
  138. <div id="errorState" class="text-center py-16 hidden">
  139. <i class="fa fa-exclamation-triangle text-4xl text-yellow-500 mb-4"></i>
  140. <p class="text-gray-500" id="errorMessage">加载数据时发生错误</p>
  141. </div>
  142. <div id="projectList" class="overflow-x-auto">
  143. <table class="min-w-full divide-y divide-gray-200">
  144. <thead>
  145. <tr>
  146. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
  147. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">标题</th>
  148. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">时长(天)</th>
  149. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
  150. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
  151. </tr>
  152. </thead>
  153. <tbody id="projectTableBody" class="divide-y divide-gray-200">
  154. <!-- 项目数据将通过JavaScript动态填充 -->
  155. </tbody>
  156. </table>
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. </div>
  162. <!-- 通知提示组件 -->
  163. <div id="toast" class="fixed bottom-5 right-5 px-6 py-3 rounded-lg shadow-lg transform translate-y-20 opacity-0 transition-all duration-300 flex items-center">
  164. <i id="toastIcon" class="mr-2"></i>
  165. <span id="toastMessage"></span>
  166. </div>
  167. <script>
  168. // API基础URL
  169. const API_BASE_URL = "http://dev.fmode.cn:1337/parse/classes/Project";
  170. // 请求头配置
  171. const headers = {
  172. "accept": "*/*",
  173. "content-type": "text/plain;charset=UTF-8",
  174. "x-parse-application-id": "dev"
  175. };
  176. // DOM元素
  177. const projectForm = document.getElementById('projectForm');
  178. const projectIdInput = document.getElementById('projectId');
  179. const titleInput = document.getElementById('title');
  180. const durationInput = document.getElementById('duration');
  181. const formTitle = document.getElementById('formTitle');
  182. const submitBtn = document.getElementById('submitBtn');
  183. const cancelBtn = document.getElementById('cancelBtn');
  184. const projectTableBody = document.getElementById('projectTableBody');
  185. const loadingIndicator = document.getElementById('loading');
  186. const emptyState = document.getElementById('emptyState');
  187. const errorState = document.getElementById('errorState');
  188. const errorMessage = document.getElementById('errorMessage');
  189. const toast = document.getElementById('toast');
  190. const toastIcon = document.getElementById('toastIcon');
  191. const toastMessage = document.getElementById('toastMessage');
  192. // 初始化页面
  193. document.addEventListener('DOMContentLoaded', () => {
  194. // 加载所有项目
  195. queryAllProjects();
  196. // 表单提交事件
  197. projectForm.addEventListener('submit', handleFormSubmit);
  198. // 取消按钮事件
  199. cancelBtn.addEventListener('click', resetForm);
  200. });
  201. // 显示加载状态
  202. function showLoading() {
  203. loadingIndicator.classList.remove('hidden');
  204. emptyState.classList.add('hidden');
  205. errorState.classList.add('hidden');
  206. projectTableBody.innerHTML = '';
  207. }
  208. // 显示空状态
  209. function showEmptyState() {
  210. loadingIndicator.classList.add('hidden');
  211. emptyState.classList.remove('hidden');
  212. errorState.classList.add('hidden');
  213. }
  214. // 显示错误状态
  215. function showError(message) {
  216. loadingIndicator.classList.add('hidden');
  217. emptyState.classList.add('hidden');
  218. errorState.classList.remove('hidden');
  219. errorMessage.textContent = message;
  220. }
  221. // 显示通知提示
  222. function showToast(message, isSuccess = true) {
  223. toastMessage.textContent = message;
  224. toastIcon.className = isSuccess ? 'fa fa-check-circle text-green-500 mr-2' : 'fa fa-exclamation-circle text-red-500 mr-2';
  225. toast.className = `fixed bottom-5 right-5 px-6 py-3 rounded-lg shadow-lg transform translate-y-0 opacity-100 transition-all duration-300 flex items-center ${isSuccess ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'}`;
  226. // 3秒后隐藏
  227. setTimeout(() => {
  228. toast.className = 'fixed bottom-5 right-5 px-6 py-3 rounded-lg shadow-lg transform translate-y-20 opacity-0 transition-all duration-300 flex items-center';
  229. }, 3000);
  230. }
  231. // 重置表单
  232. function resetForm() {
  233. projectForm.reset();
  234. projectIdInput.value = '';
  235. formTitle.textContent = '添加新项目';
  236. submitBtn.innerHTML = '<i class="fa fa-save mr-2"></i> 保存项目';
  237. cancelBtn.classList.add('hidden');
  238. }
  239. // 处理表单提交
  240. async function handleFormSubmit(e) {
  241. e.preventDefault();
  242. const projectId = projectIdInput.value;
  243. const projectData = {
  244. title: titleInput.value.trim(),
  245. duration: parseInt(durationInput.value)
  246. };
  247. try {
  248. if (projectId) {
  249. // 更新项目
  250. await updateProject(projectId, projectData);
  251. showToast('项目更新成功');
  252. } else {
  253. // 创建新项目
  254. await createProject(projectData);
  255. showToast('项目创建成功');
  256. }
  257. // 重置表单
  258. resetForm();
  259. // 重新加载项目列表
  260. queryAllProjects();
  261. } catch (error) {
  262. showToast(`操作失败: ${error.message}`, false);
  263. console.error('操作失败:', error);
  264. }
  265. }
  266. // 创建项目
  267. async function createProject(projectData) {
  268. const response = await fetch(API_BASE_URL, {
  269. method: "POST",
  270. headers: headers,
  271. body: JSON.stringify(projectData),
  272. mode: "cors",
  273. credentials: "omit"
  274. });
  275. if (!response.ok) {
  276. throw new Error(`HTTP错误,状态码: ${response.status}`);
  277. }
  278. return await response.json();
  279. }
  280. // 查询所有项目
  281. async function queryAllProjects() {
  282. showLoading();
  283. try {
  284. const response = await fetch(API_BASE_URL, {
  285. method: "GET",
  286. headers: headers,
  287. mode: "cors",
  288. credentials: "omit"
  289. });
  290. if (!response.ok) {
  291. throw new Error(`HTTP错误,状态码: ${response.status}`);
  292. }
  293. const data = await response.json();
  294. renderProjectList(data.results);
  295. } catch (error) {
  296. showError(`查询失败: ${error.message}`);
  297. console.error('查询失败:', error);
  298. }
  299. }
  300. // 按标题查询项目
  301. async function queryProjectByTitle() {
  302. const title = document.getElementById('searchTitle').value.trim();
  303. if (!title) {
  304. showToast('请输入项目标题', false);
  305. return;
  306. }
  307. showLoading();
  308. try {
  309. // 构建查询参数,需要URL编码
  310. const encodedTitle = encodeURIComponent(title);
  311. const response = await fetch(`${API_BASE_URL}?where={"title":"${encodedTitle}"}`, {
  312. method: "GET",
  313. headers: headers,
  314. mode: "cors",
  315. credentials: "omit"
  316. });
  317. if (!response.ok) {
  318. throw new Error(`HTTP错误,状态码: ${response.status}`);
  319. }
  320. const data = await response.json();
  321. renderProjectList(data.results);
  322. } catch (error) {
  323. showError(`查询失败: ${error.message}`);
  324. console.error('查询失败:', error);
  325. }
  326. }
  327. // 按标题前缀查询项目
  328. async function queryProjectByRegex() {
  329. const prefix = document.getElementById('searchRegex').value.trim();
  330. if (!prefix) {
  331. showToast('请输入标题前缀', false);
  332. return;
  333. }
  334. showLoading();
  335. try {
  336. // 构建正则表达式查询参数
  337. const encodedRegex = encodeURIComponent(`^${prefix}`);
  338. const response = await fetch(`${API_BASE_URL}?where={"title":{"$regex":"${encodedRegex}"}}`, {
  339. method: "GET",
  340. headers: headers,
  341. mode: "cors",
  342. credentials: "omit"
  343. });
  344. if (!response.ok) {
  345. throw new Error(`HTTP错误,状态码: ${response.status}`);
  346. }
  347. const data = await response.json();
  348. renderProjectList(data.results);
  349. } catch (error) {
  350. showError(`查询失败: ${error.message}`);
  351. console.error('查询失败:', error);
  352. }
  353. }
  354. // 更新项目
  355. async function updateProject(projectId, projectData) {
  356. const response = await fetch(`${API_BASE_URL}/${projectId}`, {
  357. method: "PUT",
  358. headers: headers,
  359. body: JSON.stringify(projectData),
  360. mode: "cors",
  361. credentials: "omit"
  362. });
  363. if (!response.ok) {
  364. throw new Error(`HTTP错误,状态码: ${response.status}`);
  365. }
  366. return await response.json();
  367. }
  368. // 删除项目
  369. async function deleteProject(projectId) {
  370. if (!confirm('确定要删除这个项目吗?')) {
  371. return;
  372. }
  373. try {
  374. const response = await fetch(`${API_BASE_URL}/${projectId}`, {
  375. method: "DELETE",
  376. headers: headers,
  377. mode: "cors",
  378. credentials: "omit"
  379. });
  380. if (!response.ok) {
  381. throw new Error(`HTTP错误,状态码: ${response.status}`);
  382. }
  383. showToast('项目删除成功');
  384. queryAllProjects();
  385. } catch (error) {
  386. showToast(`删除失败: ${error.message}`, false);
  387. console.error('删除失败:', error);
  388. }
  389. }
  390. // 编辑项目
  391. function editProject(project) {
  392. projectIdInput.value = project.objectId;
  393. titleInput.value = project.title;
  394. durationInput.value = project.duration;
  395. formTitle.textContent = '编辑项目';
  396. submitBtn.innerHTML = '<i class="fa fa-pencil mr-2"></i> 更新项目';
  397. cancelBtn.classList.remove('hidden');
  398. // 滚动到表单
  399. projectForm.scrollIntoView({ behavior: 'smooth' });
  400. }
  401. // 渲染项目列表
  402. function renderProjectList(projects) {
  403. loadingIndicator.classList.add('hidden');
  404. if (!projects || projects.length === 0) {
  405. showEmptyState();
  406. return;
  407. }
  408. projectTableBody.innerHTML = '';
  409. // 按创建时间排序,最新的在前
  410. projects.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
  411. projects.forEach(project => {
  412. const row = document.createElement('tr');
  413. row.className = 'hover:bg-gray-50 transition-custom';
  414. // 格式化日期
  415. const formattedDate = new Date(project.createdAt).toLocaleString();
  416. row.innerHTML = `
  417. <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 max-w-xs truncate">${project.objectId}</td>
  418. <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${project.title}</td>
  419. <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${project.duration}</td>
  420. <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formattedDate}</td>
  421. <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
  422. <button onclick="editProject(${JSON.stringify(project)})"
  423. class="text-primary hover:text-primary/80 mr-4 transition-custom">
  424. <i class="fa fa-pencil"></i> 编辑
  425. </button>
  426. <button onclick="deleteProject('${project.objectId}')"
  427. class="text-danger hover:text-danger/80 transition-custom">
  428. <i class="fa fa-trash"></i> 删除
  429. </button>
  430. </td>
  431. `;
  432. projectTableBody.appendChild(row);
  433. });
  434. }
  435. // 暴露函数到全局,以便在HTML中调用
  436. window.createProject = createProject;
  437. window.queryAllProjects = queryAllProjects;
  438. window.queryProjectByTitle = queryProjectByTitle;
  439. window.queryProjectByRegex = queryProjectByRegex;
  440. window.editProject = editProject;
  441. window.deleteProject = deleteProject;
  442. </script>
  443. </body>
  444. </html>