create-bubbles 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/dist/index.js +15 -0
  2. package/package.json +1 -2
  3. package/template-nextjs-vinext-eslint/Dockerfile +4 -4
  4. package/template-nextjs-vinext-eslint/README.md +6 -2
  5. package/template-nextjs-vinext-eslint/docker-compose.yaml +4 -4
  6. package/template-nextjs-vinext-eslint/eslint.config.js +2 -0
  7. package/template-nextjs-vinext-eslint/package.json +28 -26
  8. package/template-nextjs-vinext-eslint/pnpm-workspace.yaml +13 -1
  9. package/template-nextjs-vinext-eslint/src/utils/request/core/index.ts +63 -45
  10. package/template-nextjs-vinext-eslint/src/utils/request/core/utils.ts +12 -6
  11. package/template-nextjs-vinext-eslint/vite.config.ts +1 -1
  12. package/template-react-rsbuild-biome/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
  13. package/template-react-rsbuild-biome/AGENTS.md +11 -0
  14. package/template-react-rsbuild-biome/index.html +2 -2
  15. package/template-react-rsbuild-biome/package.json +25 -24
  16. package/template-react-rsbuild-biome/pnpm-workspace.yaml +5 -0
  17. package/template-react-rsbuild-biome/rsbuild.config.ts +1 -1
  18. package/template-react-rsbuild-biome/src/assets/icon/logo.svg +1 -1
  19. package/template-react-rsbuild-biome/src/assets/svg/draft.svg +4 -0
  20. package/template-react-rsbuild-biome/src/components/Icon/svg-icon/README.md +37 -0
  21. package/template-react-rsbuild-biome/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
  22. package/template-react-rsbuild-biome/src/env.d.ts +7 -0
  23. package/template-react-rsbuild-biome/src/utils/request/index.ts +3 -4
  24. package/template-react-rsbuild-biome/tsconfig.json +1 -2
  25. package/template-taro-react-oxc/.oxlintrc.json +0 -1
  26. package/template-taro-react-oxc/package.json +53 -52
  27. package/template-taro-react-oxc/pnpm-workspace.yaml +46 -0
  28. package/template-taro-vue-eslint/package.json +56 -56
  29. package/template-vp-monorepo-react-hono/apps/web/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
  30. package/template-vp-monorepo-react-hono/apps/web/AGENTS.md +11 -0
  31. package/template-vp-monorepo-react-hono/apps/web/package.json +1 -0
  32. package/template-vp-monorepo-react-hono/apps/web/src/assets/svg/draft.svg +4 -0
  33. package/template-vp-monorepo-react-hono/apps/web/src/components/Icon/svg-icon/README.md +37 -0
  34. package/template-vp-monorepo-react-hono/apps/web/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
  35. package/template-vp-monorepo-react-hono/apps/web/tsconfig.json +1 -1
  36. package/template-vp-monorepo-react-hono/apps/web/vite.config.ts +2 -0
  37. package/template-vp-monorepo-react-hono/pnpm-workspace.yaml +1 -0
  38. package/template-vp-monorepo-react-nestjs/.agents/rules//344/273/243/347/240/201/350/256/276/350/256/241.md +4 -0
  39. package/template-vp-monorepo-react-nestjs/.agents/rules//345/205/261/344/272/253/344/273/243/347/240/201.md +4 -0
  40. package/template-vp-monorepo-react-nestjs/.spaces/01./344/274/201/344/270/232/347/272/247/351/241/271/347/233/256/347/272/247//351/234/200/346/261/202/346/226/207/346/241/243.md +177 -0
  41. package/template-vp-monorepo-react-nestjs/.spaces/server/04.upload.md +4504 -0
  42. package/template-vp-monorepo-react-nestjs/.spaces/server/05./344/270/212/344/274/240/346/224/271/351/200/240/346/226/271/346/241/210/README.md +858 -0
  43. package/template-vp-monorepo-react-nestjs/.spaces/server/06./345/205/254/345/205/261/351/230/237/345/210/227/346/216/245/345/205/245/346/226/271/346/241/210/README.md +1890 -0
  44. package/template-vp-monorepo-react-nestjs/.spaces/server/06./345/205/254/345/205/261/351/230/237/345/210/227/346/216/245/345/205/245/346/226/271/346/241/210//344/270/212/344/274/240/346/270/205/347/220/206.md +1465 -0
  45. package/template-vp-monorepo-react-nestjs/AGENTS.md +8 -12
  46. package/template-vp-monorepo-react-nestjs/apps/server/.agents/rules/error-handling.md +84 -0
  47. package/template-vp-monorepo-react-nestjs/apps/server/.env.development +60 -0
  48. package/template-vp-monorepo-react-nestjs/apps/server/.env.production +16 -0
  49. package/template-vp-monorepo-react-nestjs/apps/server/AGENTS.md +9 -0
  50. package/template-vp-monorepo-react-nestjs/apps/server/drizzle/0000_military_colonel_america.sql +11 -0
  51. package/template-vp-monorepo-react-nestjs/apps/server/drizzle/0001_loud_siren.sql +27 -0
  52. package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/0000_snapshot.json +97 -0
  53. package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/0001_snapshot.json +354 -0
  54. package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/_journal.json +20 -0
  55. package/template-vp-monorepo-react-nestjs/apps/server/package.json +4 -0
  56. package/template-vp-monorepo-react-nestjs/apps/server/src/app.module.ts +14 -2
  57. package/template-vp-monorepo-react-nestjs/apps/server/src/common/adapters/fastify.adapter.ts +6 -0
  58. package/template-vp-monorepo-react-nestjs/apps/server/src/config/index.ts +2 -0
  59. package/template-vp-monorepo-react-nestjs/apps/server/src/config/queue.config.ts +105 -0
  60. package/template-vp-monorepo-react-nestjs/apps/server/src/config/storage.config.ts +33 -0
  61. package/template-vp-monorepo-react-nestjs/apps/server/src/database/schema.ts +57 -1
  62. package/template-vp-monorepo-react-nestjs/apps/server/src/main.ts +1 -0
  63. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.service.ts +1 -3
  64. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/auth-repsponse.dto.ts +2 -2
  65. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/queue-worker.bootstrap.ts +24 -0
  66. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.constants.ts +24 -0
  67. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.contracts.ts +35 -0
  68. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.module.ts +56 -0
  69. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.processor.ts +62 -0
  70. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.service.ts +58 -0
  71. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/error-catelog.spec.ts +34 -0
  72. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/dto/initiate-mutipart-upload.dto.ts +26 -0
  73. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/dto/upload-params.dto.ts +15 -0
  74. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/exact-size.transform.ts +66 -0
  75. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/minio-storage.adapter.ts +239 -0
  76. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/storage.port.ts +61 -0
  77. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.constants.ts +23 -0
  78. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.controller.ts +109 -0
  79. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.errors.ts +80 -0
  80. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.module.ts +20 -0
  81. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.reponsitory.ts +166 -0
  82. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.service.ts +696 -0
  83. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/utils/object-key.ts +10 -0
  84. package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.integration.spec.ts +120 -0
  85. package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.processor.spec.ts +63 -0
  86. package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.service.spec.ts +106 -0
  87. package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
  88. package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//345/274/271/347/252/227/347/273/204/344/273/266.md +4 -0
  89. package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//346/226/207/344/273/266/346/213/206/345/210/206.md +4 -0
  90. package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//347/273/204/344/273/266/345/221/275/345/220/215.md +3 -0
  91. package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//350/241/250/346/240/274/351/241/265/351/235/242.md +11 -0
  92. package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//351/241/265/351/235/242/346/216/245/345/217/243.md +3 -0
  93. package/template-vp-monorepo-react-nestjs/apps/web/.env.dev +1 -1
  94. package/template-vp-monorepo-react-nestjs/apps/web/AGENTS.md +16 -0
  95. package/template-vp-monorepo-react-nestjs/apps/web/README.MD +153 -0
  96. package/template-vp-monorepo-react-nestjs/apps/web/index.html +3 -2
  97. package/template-vp-monorepo-react-nestjs/apps/web/package.json +6 -1
  98. package/template-vp-monorepo-react-nestjs/apps/web/public/favicon.svg +9 -1
  99. package/template-vp-monorepo-react-nestjs/apps/web/src/App.module.css +5 -0
  100. package/template-vp-monorepo-react-nestjs/apps/web/src/App.tsx +16 -9
  101. package/template-vp-monorepo-react-nestjs/apps/web/src/api/auth.ts +9 -0
  102. package/template-vp-monorepo-react-nestjs/apps/web/src/assets/svg/draft.svg +4 -0
  103. package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/DraftProTable.module.css +78 -0
  104. package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/DraftProTable.tsx +99 -0
  105. package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/README.md +34 -0
  106. package/template-vp-monorepo-react-nestjs/apps/web/src/components/FullHeightProTable/FullHeightProTable.module.css +54 -0
  107. package/template-vp-monorepo-react-nestjs/apps/web/src/components/FullHeightProTable/FullHeightProTable.tsx +41 -0
  108. package/template-vp-monorepo-react-nestjs/apps/web/src/components/Icon/svg-icon/README.md +37 -0
  109. package/template-vp-monorepo-react-nestjs/apps/web/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
  110. package/template-vp-monorepo-react-nestjs/apps/web/src/components/Loading/PageLoading.module.css +7 -0
  111. package/template-vp-monorepo-react-nestjs/apps/web/src/components/Loading/PageLoading.tsx +2 -1
  112. package/template-vp-monorepo-react-nestjs/apps/web/src/config/theme.ts +20 -0
  113. package/template-vp-monorepo-react-nestjs/apps/web/src/layouts/BasicLayout.module.css +17 -0
  114. package/template-vp-monorepo-react-nestjs/apps/web/src/layouts/BasicLayout.tsx +106 -0
  115. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/i18n/index.module.css +23 -0
  116. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/i18n/index.tsx +53 -0
  117. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/components/ProjectFormDialog.tsx +132 -0
  118. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/columns.tsx +121 -0
  119. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/index.ts +77 -0
  120. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/components/DraftProjectFormDialog.tsx +140 -0
  121. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/columns.tsx +127 -0
  122. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/index.ts +39 -0
  123. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/projects.ts +48 -0
  124. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/index.module.css +49 -0
  125. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/index.tsx +163 -0
  126. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/index.module.css +53 -0
  127. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/index.tsx +135 -0
  128. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/home/index.module.css +23 -0
  129. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/home/index.tsx +26 -43
  130. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/api.ts +11 -0
  131. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/index.tsx +183 -0
  132. package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/login.css +164 -0
  133. package/template-vp-monorepo-react-nestjs/apps/web/src/router/index.tsx +36 -12
  134. package/template-vp-monorepo-react-nestjs/apps/web/src/styles/index.css +11 -4
  135. package/template-vp-monorepo-react-nestjs/apps/web/src/utils/request/core/index.ts +55 -47
  136. package/template-vp-monorepo-react-nestjs/apps/web/src/utils/request/index.ts +10 -5
  137. package/template-vp-monorepo-react-nestjs/apps/web/src/utils/storage/cookie.ts +50 -0
  138. package/template-vp-monorepo-react-nestjs/apps/web/src/utils/storage/session.ts +33 -0
  139. package/template-vp-monorepo-react-nestjs/apps/web/test/draft-projects.spec.ts +89 -0
  140. package/template-vp-monorepo-react-nestjs/apps/web/test/request.spec.ts +145 -0
  141. package/template-vp-monorepo-react-nestjs/apps/web/test/storage.spec.ts +108 -0
  142. package/template-vp-monorepo-react-nestjs/apps/web/test/tsconfig.json +7 -0
  143. package/template-vp-monorepo-react-nestjs/apps/web/tsconfig.json +1 -1
  144. package/template-vp-monorepo-react-nestjs/apps/web/vite.config.ts +7 -0
  145. package/template-vp-monorepo-react-nestjs/apps/web-vue/tsconfig.tsbuildinfo +1 -0
  146. package/template-vp-monorepo-react-nestjs/docker-compose.yml +19 -0
  147. package/template-vp-monorepo-react-nestjs/mise.toml +1 -1
  148. package/template-vp-monorepo-react-nestjs/package.json +1 -6
  149. package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +9 -1
  150. package/template-vp-react/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
  151. package/template-vp-react/AGENTS.md +11 -0
  152. package/template-vp-react/commitlint.config.js +0 -0
  153. package/template-vp-react/package.json +21 -26
  154. package/template-vp-react/pnpm-workspace.yaml +6 -0
  155. package/template-vp-react/src/assets/svg/draft.svg +4 -0
  156. package/template-vp-react/src/components/Icon/svg-icon/README.md +37 -0
  157. package/template-vp-react/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
  158. package/template-vp-react/src/utils/request/index.ts +18 -5
  159. package/template-vp-react/tsconfig.json +2 -3
  160. package/template-vp-react/vite.config.ts +2 -0
  161. package/template-vp-react-shadcn/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
  162. package/template-vp-react-shadcn/.vscode/settings.json +2 -2
  163. package/template-vp-react-shadcn/{agents.md → AGENTS.md} +12 -2
  164. package/template-vp-react-shadcn/README.md +41 -0
  165. package/template-vp-react-shadcn/commitlint.config.js +1 -1
  166. package/template-vp-react-shadcn/components.json +1 -1
  167. package/template-vp-react-shadcn/package.json +25 -32
  168. package/template-vp-react-shadcn/pnpm-workspace.yaml +10 -0
  169. package/template-vp-react-shadcn/src/App.tsx +10 -10
  170. package/template-vp-react-shadcn/src/assets/svg/draft.svg +4 -0
  171. package/template-vp-react-shadcn/src/components/Icon/svg-icon/README.md +37 -0
  172. package/template-vp-react-shadcn/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
  173. package/template-vp-react-shadcn/src/components/Loading/PageLoading.tsx +4 -4
  174. package/template-vp-react-shadcn/src/components/Player/VideoJS/index.tsx +94 -78
  175. package/template-vp-react-shadcn/src/components/Player/index.ts +1 -1
  176. package/template-vp-react-shadcn/src/components/ui/button.tsx +25 -32
  177. package/template-vp-react-shadcn/src/components/ui/spinner.tsx +8 -7
  178. package/template-vp-react-shadcn/src/components/ui/toast.tsx +229 -0
  179. package/template-vp-react-shadcn/src/lib/utils.ts +3 -3
  180. package/template-vp-react-shadcn/src/main.tsx +5 -4
  181. package/template-vp-react-shadcn/src/pages/home/index.tsx +43 -35
  182. package/template-vp-react-shadcn/src/router/index.tsx +15 -15
  183. package/template-vp-react-shadcn/src/styles/index.css +4 -4
  184. package/template-vp-react-shadcn/src/styles/shadcn.css +2 -2
  185. package/template-vp-react-shadcn/src/utils/env/index.tsx +2 -2
  186. package/template-vp-react-shadcn/src/utils/request/alova-core/index.ts +102 -30
  187. package/template-vp-react-shadcn/src/utils/request/alova-core/utils.ts +5 -2
  188. package/template-vp-react-shadcn/src/utils/request/index.ts +32 -19
  189. package/template-vp-react-shadcn/src/utils/request/readme.md +41 -38
  190. package/template-vp-react-shadcn/tsconfig.json +2 -3
  191. package/template-vp-react-shadcn/vite.config.ts +29 -15
  192. package/template-vp-vue-eslint-vapor/package.json +26 -33
  193. package/template-vp-vue-eslint-vapor/pnpm-workspace.yaml +29 -1
  194. package/template-vue-vp-eslint/README.md +3 -10
  195. package/template-vue-vp-eslint/package.json +27 -34
  196. package/template-vue-vp-eslint/pnpm-workspace.yaml +27 -2
  197. package/template-vue-vp-eslint/src/layout/default/index.vue +4 -3
  198. package/dist/index.mjs +0 -422
  199. package/template-vp-monorepo-react-nestjs/.agents/auth.config.ts +0 -0
  200. package/template-vp-monorepo-react-nestjs/.spaces/server/todo.md +0 -991
  201. package/template-vp-monorepo-react-nestjs/apps/server/README.md +0 -3
  202. package/template-vp-react-shadcn/.oxfmtrc.jsonc +0 -8
  203. package/template-vp-react-shadcn/.oxlintrc.jsonc +0 -3
  204. package/template-vp-react-shadcn/src/components/ui/sonner.tsx +0 -43
@@ -0,0 +1,132 @@
1
+ import {
2
+ ModalForm,
3
+ ProFormDatePicker,
4
+ ProFormDigit,
5
+ ProFormGroup,
6
+ ProFormSelect,
7
+ ProFormText,
8
+ ProFormTextArea,
9
+ } from '@ant-design/pro-components'
10
+ import { useImperativeHandle, useState, type Ref } from 'react'
11
+ import {
12
+ owners,
13
+ priorityOptions,
14
+ statusOptions,
15
+ type ProjectFormValues,
16
+ type ProjectRecord,
17
+ } from '../config'
18
+
19
+ export interface ProjectFormDialogRef {
20
+ show: (project?: ProjectRecord) => void
21
+ hide: () => void
22
+ }
23
+
24
+ interface ProjectFormDialogProps {
25
+ ref: Ref<ProjectFormDialogRef>
26
+ onSave: (values: ProjectFormValues, project?: ProjectRecord) => void
27
+ }
28
+
29
+ export default function ProjectFormDialog({ ref, onSave }: ProjectFormDialogProps) {
30
+ const [open, setOpen] = useState(false)
31
+ const [project, setProject] = useState<ProjectRecord>()
32
+
33
+ function hide() {
34
+ setOpen(false)
35
+ }
36
+
37
+ useImperativeHandle(ref, () => ({
38
+ show(record) {
39
+ setProject(record)
40
+ setOpen(true)
41
+ },
42
+ hide,
43
+ }))
44
+
45
+ return (
46
+ <ModalForm<ProjectFormValues>
47
+ name="project-editor"
48
+ title={project ? '编辑项目' : '新增项目'}
49
+ open={open}
50
+ width={600}
51
+ grid
52
+ rowProps={{ gutter: 16 }}
53
+ initialValues={project ?? { status: 'planning', priority: 'medium', progress: 0 }}
54
+ modalProps={{ destroyOnHidden: true, onCancel: hide }}
55
+ submitter={{ searchConfig: { submitText: '保存项目', resetText: '取消' } }}
56
+ onOpenChange={(visible) => {
57
+ if (!visible) hide()
58
+ }}
59
+ onFinish={async (values) => {
60
+ onSave(
61
+ {
62
+ ...values,
63
+ name: values.name.trim(),
64
+ description: values.description?.trim() ?? '',
65
+ progress:
66
+ values.status === 'completed'
67
+ ? 100
68
+ : values.status === 'planning'
69
+ ? 0
70
+ : values.progress,
71
+ },
72
+ project,
73
+ )
74
+ hide()
75
+ return true
76
+ }}
77
+ >
78
+ <ProFormText
79
+ name="name"
80
+ label="项目名称"
81
+ placeholder="例如:客户服务工作台"
82
+ rules={[{ required: true, whitespace: true, message: '请输入项目名称' }]}
83
+ fieldProps={{ maxLength: 40, showCount: true }}
84
+ />
85
+ <ProFormGroup>
86
+ <ProFormSelect
87
+ name="owner"
88
+ label="负责人"
89
+ options={owners}
90
+ colProps={{ xs: 24, sm: 12 }}
91
+ rules={[{ required: true, message: '请选择负责人' }]}
92
+ />
93
+ <ProFormDatePicker
94
+ name="dueDate"
95
+ label="截止日期"
96
+ colProps={{ xs: 24, sm: 12 }}
97
+ rules={[{ required: true, message: '请选择截止日期' }]}
98
+ fieldProps={{ style: { width: '100%' } }}
99
+ />
100
+ <ProFormSelect
101
+ name="status"
102
+ label="项目状态"
103
+ valueEnum={statusOptions}
104
+ colProps={{ xs: 24, sm: 12 }}
105
+ rules={[{ required: true, message: '请选择项目状态' }]}
106
+ />
107
+ <ProFormSelect
108
+ name="priority"
109
+ label="优先级"
110
+ valueEnum={priorityOptions}
111
+ colProps={{ xs: 24, sm: 12 }}
112
+ rules={[{ required: true, message: '请选择优先级' }]}
113
+ />
114
+ </ProFormGroup>
115
+ <ProFormDigit
116
+ name="progress"
117
+ label="完成进度(%)"
118
+ min={0}
119
+ max={100}
120
+ fieldProps={{ precision: 0 }}
121
+ rules={[{ required: true, message: '请输入完成进度' }]}
122
+ extra="待启动项目保存为 0%,已完成项目保存为 100%。"
123
+ />
124
+ <ProFormTextArea
125
+ name="description"
126
+ label="项目说明"
127
+ placeholder="补充项目目标或备注(选填)"
128
+ fieldProps={{ maxLength: 200, showCount: true, rows: 3 }}
129
+ />
130
+ </ModalForm>
131
+ )
132
+ }
@@ -0,0 +1,121 @@
1
+ import { FolderOutlined } from '@ant-design/icons'
2
+ import type { ProColumns } from '@ant-design/pro-components'
3
+ import { Avatar, Button, Popconfirm, Progress, Space, Tag, Tooltip } from 'antd'
4
+ import { owners, priorityOptions, statusOptions, type ProjectRecord } from '.'
5
+ import styles from '../index.module.css'
6
+
7
+ interface ProjectColumnOptions {
8
+ onEdit: (project: ProjectRecord) => void
9
+ onDelete: (project: ProjectRecord) => void
10
+ }
11
+
12
+ export function createProjectColumns({
13
+ onEdit,
14
+ onDelete,
15
+ }: ProjectColumnOptions): ProColumns<ProjectRecord>[] {
16
+ return [
17
+ {
18
+ title: '项目名称',
19
+ dataIndex: 'name',
20
+ width: 260,
21
+ fieldProps: { placeholder: '搜索项目名称或编号', allowClear: true },
22
+ render: (_, project) => (
23
+ <div className={styles.project}>
24
+ <span className={styles.projectIcon}>
25
+ <FolderOutlined />
26
+ </span>
27
+ <div className={styles.projectInfo}>
28
+ <Tooltip title={project.description || project.name}>
29
+ <Button type="link" className={styles.projectName} onClick={() => onEdit(project)}>
30
+ {project.name}
31
+ </Button>
32
+ </Tooltip>
33
+ <span className={styles.projectId}>{project.id}</span>
34
+ </div>
35
+ </div>
36
+ ),
37
+ },
38
+ {
39
+ title: '项目状态',
40
+ dataIndex: 'status',
41
+ width: 115,
42
+ valueType: 'select',
43
+ valueEnum: statusOptions,
44
+ },
45
+ {
46
+ title: '负责人',
47
+ dataIndex: 'owner',
48
+ width: 125,
49
+ valueType: 'select',
50
+ fieldProps: { options: owners.map((owner) => ({ label: owner, value: owner })) },
51
+ render: (_, project) => (
52
+ <Space size={8}>
53
+ <Avatar size={26} className={styles.avatar}>
54
+ {project.owner.slice(0, 1)}
55
+ </Avatar>
56
+ {project.owner}
57
+ </Space>
58
+ ),
59
+ },
60
+ {
61
+ title: '优先级',
62
+ dataIndex: 'priority',
63
+ width: 90,
64
+ valueType: 'select',
65
+ valueEnum: priorityOptions,
66
+ render: (_, project) => (
67
+ <Tag color={priorityOptions[project.priority].color}>
68
+ {priorityOptions[project.priority].text}
69
+ </Tag>
70
+ ),
71
+ },
72
+ {
73
+ title: '完成进度',
74
+ dataIndex: 'progress',
75
+ width: 155,
76
+ search: false,
77
+ sorter: (first, second) => first.progress - second.progress,
78
+ render: (_, project) => (
79
+ <Progress
80
+ percent={project.progress}
81
+ size="small"
82
+ status={project.status === 'completed' ? 'success' : 'normal'}
83
+ strokeColor={project.status === 'paused' ? '#d6a348' : undefined}
84
+ className={styles.progress}
85
+ />
86
+ ),
87
+ },
88
+ {
89
+ title: '截止日期',
90
+ dataIndex: 'dueDate',
91
+ width: 130,
92
+ valueType: 'date',
93
+ search: false,
94
+ sorter: (first, second) => first.dueDate.localeCompare(second.dueDate),
95
+ },
96
+ { title: '截止日期', dataIndex: 'dueDate', valueType: 'dateRange', hideInTable: true },
97
+ {
98
+ title: '操作',
99
+ valueType: 'option',
100
+ width: 120,
101
+ fixed: 'right',
102
+ render: (_, project) => [
103
+ <Button key="edit" type="link" size="small" onClick={() => onEdit(project)}>
104
+ 编辑
105
+ </Button>,
106
+ <Popconfirm
107
+ key="delete"
108
+ title="删除项目"
109
+ description={`确定删除「${project.name}」吗?`}
110
+ onConfirm={() => onDelete(project)}
111
+ okText="删除"
112
+ okButtonProps={{ danger: true }}
113
+ >
114
+ <Button type="link" danger size="small">
115
+ 删除
116
+ </Button>
117
+ </Popconfirm>,
118
+ ],
119
+ },
120
+ ]
121
+ }
@@ -0,0 +1,77 @@
1
+ export type ProjectStatus = 'planning' | 'active' | 'paused' | 'completed'
2
+ export type ProjectPriority = 'high' | 'medium' | 'low'
3
+
4
+ // 仅用于页面演示;接入真实接口时再在 shared 中定义接口契约。
5
+ export interface ProjectRecord {
6
+ id: string
7
+ name: string
8
+ owner: string
9
+ status: ProjectStatus
10
+ priority: ProjectPriority
11
+ progress: number
12
+ dueDate: string
13
+ description: string
14
+ }
15
+
16
+ export type ProjectFormValues = Omit<ProjectRecord, 'id'>
17
+
18
+ export interface ProjectSearchValues {
19
+ name?: string
20
+ owner?: string
21
+ status?: ProjectStatus
22
+ priority?: ProjectPriority
23
+ dueDate?: [string, string]
24
+ }
25
+
26
+ export const statusOptions = {
27
+ planning: { text: '待启动', status: 'Default' },
28
+ active: { text: '进行中', status: 'Processing' },
29
+ paused: { text: '已暂停', status: 'Warning' },
30
+ completed: { text: '已完成', status: 'Success' },
31
+ }
32
+
33
+ export const priorityOptions = {
34
+ high: { text: '高', color: 'volcano' },
35
+ medium: { text: '中', color: 'gold' },
36
+ low: { text: '低', color: 'default' },
37
+ }
38
+
39
+ export const owners = ['林知夏', '陈一舟', '周予安', '许清和', '沈亦宁']
40
+
41
+ const projectNames = [
42
+ '客户服务工作台',
43
+ '官网体验升级',
44
+ '移动端设计系统',
45
+ '数据分析平台',
46
+ '团队知识库',
47
+ '会员积分中心',
48
+ '订单管理优化',
49
+ '消息通知服务',
50
+ '供应链协同平台',
51
+ '用户反馈中心',
52
+ '内容发布系统',
53
+ '企业权限管理',
54
+ '运营活动配置',
55
+ '数据报表导出',
56
+ '开放接口文档',
57
+ '支付流程优化',
58
+ '文件资源管理',
59
+ '服务监控看板',
60
+ ]
61
+
62
+ const statuses: ProjectStatus[] = ['active', 'active', 'planning', 'active', 'completed', 'paused']
63
+ const priorities: ProjectPriority[] = ['high', 'medium', 'low']
64
+
65
+ export const initialProjects: ProjectRecord[] = projectNames.map((name, index) => {
66
+ const status = statuses[index % statuses.length]!
67
+ return {
68
+ id: `PRJ-${String(index + 1).padStart(3, '0')}`,
69
+ name,
70
+ owner: owners[index % owners.length]!,
71
+ status,
72
+ priority: priorities[index % priorities.length]!,
73
+ progress: status === 'completed' ? 100 : status === 'planning' ? 0 : 24 + ((index * 13) % 65),
74
+ dueDate: `2026-${index < 12 ? '09' : '10'}-${String(10 + (index % 12)).padStart(2, '0')}`,
75
+ description: `围绕${name}完善团队协作与交付体验。`,
76
+ }
77
+ })
@@ -0,0 +1,140 @@
1
+ import {
2
+ ModalForm,
3
+ ProFormDatePicker,
4
+ ProFormDigit,
5
+ ProFormSelect,
6
+ ProFormText,
7
+ ProFormTextArea,
8
+ type ProFormInstance,
9
+ } from '@ant-design/pro-components'
10
+ import { Button } from 'antd'
11
+ import { useImperativeHandle, useRef, useState, type Ref } from 'react'
12
+ import { owners, priorityOptions, statusOptions, type ProjectFormValues } from '../../config'
13
+ import { currentUser, type DraftProjectRecord, type SaveProjectInput } from '../config'
14
+
15
+ export interface DraftProjectFormDialogRef {
16
+ show: (record?: DraftProjectRecord) => void
17
+ hide: () => void
18
+ }
19
+
20
+ interface DraftProjectFormDialogProps {
21
+ ref: Ref<DraftProjectFormDialogRef>
22
+ onSave: (input: SaveProjectInput) => boolean
23
+ }
24
+
25
+ export default function DraftProjectFormDialog({ ref, onSave }: DraftProjectFormDialogProps) {
26
+ const formRef = useRef<ProFormInstance<ProjectFormValues>>(undefined)
27
+ const [open, setOpen] = useState(false)
28
+ const [record, setRecord] = useState<DraftProjectRecord>()
29
+ const isPublished = record?.stage === 'published'
30
+
31
+ function hide() {
32
+ setOpen(false)
33
+ }
34
+
35
+ useImperativeHandle(ref, () => ({
36
+ show(project) {
37
+ setRecord(project)
38
+ setOpen(true)
39
+ },
40
+ hide,
41
+ }))
42
+
43
+ return (
44
+ <ModalForm<ProjectFormValues>
45
+ name="draft-project-editor"
46
+ formRef={formRef}
47
+ title={isPublished ? '编辑项目' : record ? '继续编辑草稿' : '新增项目'}
48
+ open={open}
49
+ width={600}
50
+ grid
51
+ rowProps={{ gutter: 16 }}
52
+ dateFormatter="string"
53
+ initialValues={
54
+ record ?? { owner: currentUser, status: 'planning', priority: 'medium', progress: 0 }
55
+ }
56
+ modalProps={{ destroyOnHidden: true, onCancel: hide }}
57
+ onOpenChange={(visible) => {
58
+ if (!visible) hide()
59
+ }}
60
+ submitter={{
61
+ render: (props) => [
62
+ <Button key="cancel" onClick={hide}>
63
+ 取消
64
+ </Button>,
65
+ !isPublished && (
66
+ <Button
67
+ key="draft"
68
+ onClick={() => {
69
+ const values = formRef.current?.getFieldsFormatValue?.()
70
+ if (values && onSave({ values, original: record, stage: 'draft' })) hide()
71
+ }}
72
+ >
73
+ 保存草稿
74
+ </Button>
75
+ ),
76
+ <Button key="submit" type="primary" onClick={() => props.submit()}>
77
+ {isPublished ? '保存项目' : '提交'}
78
+ </Button>,
79
+ ],
80
+ }}
81
+ onFinish={async (values) => {
82
+ const saved = onSave({ values, original: record, stage: 'published' })
83
+ if (saved) hide()
84
+ return saved
85
+ }}
86
+ >
87
+ <ProFormText
88
+ name="name"
89
+ label="项目名称"
90
+ placeholder="例如:客户服务工作台"
91
+ rules={[{ required: true, whitespace: true, message: '请输入项目名称' }]}
92
+ fieldProps={{ maxLength: 40, showCount: true }}
93
+ extra={!isPublished ? '信息未填完也可以保存草稿,提交时需补齐必填项。' : undefined}
94
+ />
95
+ <ProFormSelect
96
+ name="owner"
97
+ label="负责人"
98
+ options={owners}
99
+ colProps={{ xs: 24, sm: 12 }}
100
+ rules={[{ required: true, message: '请选择负责人' }]}
101
+ />
102
+ <ProFormDatePicker
103
+ name="dueDate"
104
+ label="截止日期"
105
+ colProps={{ xs: 24, sm: 12 }}
106
+ rules={[{ required: true, message: '请选择截止日期' }]}
107
+ fieldProps={{ style: { width: '100%' } }}
108
+ />
109
+ <ProFormSelect
110
+ name="status"
111
+ label="项目状态"
112
+ valueEnum={statusOptions}
113
+ colProps={{ xs: 24, sm: 12 }}
114
+ rules={[{ required: true, message: '请选择项目状态' }]}
115
+ />
116
+ <ProFormSelect
117
+ name="priority"
118
+ label="优先级"
119
+ valueEnum={priorityOptions}
120
+ colProps={{ xs: 24, sm: 12 }}
121
+ rules={[{ required: true, message: '请选择优先级' }]}
122
+ />
123
+ <ProFormDigit
124
+ name="progress"
125
+ label="完成进度(%)"
126
+ min={0}
127
+ max={100}
128
+ fieldProps={{ precision: 0 }}
129
+ rules={[{ required: true, message: '请输入完成进度' }]}
130
+ extra="待启动项目保存为 0%,已完成项目保存为 100%。"
131
+ />
132
+ <ProFormTextArea
133
+ name="description"
134
+ label="项目说明"
135
+ placeholder="补充项目目标或备注(选填)"
136
+ fieldProps={{ maxLength: 200, showCount: true, rows: 3 }}
137
+ />
138
+ </ModalForm>
139
+ )
140
+ }
@@ -0,0 +1,127 @@
1
+ import { FolderOutlined } from '@ant-design/icons'
2
+ import type { ProColumns } from '@ant-design/pro-components'
3
+ import { Avatar, Badge, Button, Divider, Popconfirm, Space, Tag, Tooltip } from 'antd'
4
+ import dayjs from 'dayjs'
5
+ import { owners, statusOptions } from '../../config'
6
+ import type { DraftProjectRecord } from '.'
7
+ import styles from '../index.module.css'
8
+
9
+ interface ProjectColumnOptions {
10
+ onEdit: (project: DraftProjectRecord) => void
11
+ onDelete: (project: DraftProjectRecord) => void
12
+ }
13
+
14
+ export function createDraftProjectColumns({
15
+ onEdit,
16
+ onDelete,
17
+ }: ProjectColumnOptions): ProColumns<DraftProjectRecord>[] {
18
+ return [
19
+ {
20
+ title: '项目名称',
21
+ dataIndex: 'name',
22
+ width: 360,
23
+ fieldProps: { placeholder: '搜索项目名称或编号', allowClear: true },
24
+ render: (_, project) => (
25
+ <div className={styles.project}>
26
+ <span className={styles.projectIcon}>
27
+ <FolderOutlined />
28
+ </span>
29
+ <div className={styles.projectInfo}>
30
+ <Space size={8}>
31
+ <Button type="link" className={styles.projectName} onClick={() => onEdit(project)}>
32
+ {project.name || '未命名项目'}
33
+ </Button>
34
+ {project.stage === 'draft' && (
35
+ <Tag color="orange" variant="filled">
36
+ 草稿
37
+ </Tag>
38
+ )}
39
+ </Space>
40
+ <span className={styles.projectId}>{project.id}</span>
41
+ </div>
42
+ </div>
43
+ ),
44
+ },
45
+ {
46
+ title: '项目状态',
47
+ dataIndex: 'status',
48
+ width: 140,
49
+ valueType: 'select',
50
+ valueEnum: statusOptions,
51
+ fieldProps: { placeholder: '全部状态' },
52
+ render: (dom, project) =>
53
+ project.status ? (
54
+ dom
55
+ ) : (
56
+ <Badge status="default" text={<span className={styles.muted}>未填写</span>} />
57
+ ),
58
+ },
59
+ {
60
+ title: '负责人',
61
+ dataIndex: 'owner',
62
+ width: 150,
63
+ valueType: 'select',
64
+ fieldProps: {
65
+ placeholder: '全部负责人',
66
+ options: owners.map((owner) => ({ label: owner, value: owner })),
67
+ },
68
+ render: (_, project) =>
69
+ project.owner ? (
70
+ <Space size={8}>
71
+ <Avatar size={28} className={styles.avatar}>
72
+ {project.owner.slice(0, 1)}
73
+ </Avatar>
74
+ {project.owner}
75
+ </Space>
76
+ ) : (
77
+ <span className={styles.muted}>未填写</span>
78
+ ),
79
+ },
80
+ {
81
+ title: '最后保存时间',
82
+ dataIndex: 'savedAt',
83
+ width: 200,
84
+ search: false,
85
+ sorter: (first, second) => first.savedAt.localeCompare(second.savedAt),
86
+ render: (_, project) => {
87
+ const date = dayjs(project.savedAt)
88
+ const prefix = date.isSame(dayjs(), 'day')
89
+ ? '今天'
90
+ : date.isSame(dayjs().subtract(1, 'day'), 'day')
91
+ ? '昨天'
92
+ : date.format('YYYY-MM-DD')
93
+ return (
94
+ <Tooltip title={date.format('YYYY-MM-DD HH:mm:ss')}>
95
+ <span className={styles.muted}>
96
+ {prefix} {date.format('HH:mm')}
97
+ </span>
98
+ </Tooltip>
99
+ )
100
+ },
101
+ },
102
+ {
103
+ title: '操作',
104
+ valueType: 'option',
105
+ width: 170,
106
+ fixed: 'right',
107
+ render: (_, project) => (
108
+ <Space size={0} separator={<Divider orientation="vertical" />}>
109
+ <Button type="link" size="small" onClick={() => onEdit(project)}>
110
+ {project.stage === 'draft' ? '继续编辑' : '编辑'}
111
+ </Button>
112
+ <Popconfirm
113
+ title={project.stage === 'draft' ? '删除草稿' : '删除项目'}
114
+ description={`确定删除「${project.name || '未命名项目'}」吗?`}
115
+ onConfirm={() => onDelete(project)}
116
+ okText="删除"
117
+ okButtonProps={{ danger: true }}
118
+ >
119
+ <Button type="link" danger size="small">
120
+ 删除
121
+ </Button>
122
+ </Popconfirm>
123
+ </Space>
124
+ ),
125
+ },
126
+ ]
127
+ }
@@ -0,0 +1,39 @@
1
+ import dayjs from 'dayjs'
2
+ import { initialProjects, owners, type ProjectFormValues } from '../../config'
3
+
4
+ export const currentUser = owners[0]!
5
+ export const storageKey = 'pro-table-draft-demo:v1'
6
+
7
+ // 本地演示数据,草稿允许字段不完整;stage 与项目业务 status 分开。
8
+ export type DraftProjectRecord = Partial<ProjectFormValues> & {
9
+ id: string
10
+ stage: 'draft' | 'published'
11
+ createdBy: string
12
+ savedAt: string
13
+ }
14
+
15
+ export interface SaveProjectInput {
16
+ values: Partial<ProjectFormValues>
17
+ original?: DraftProjectRecord
18
+ stage: DraftProjectRecord['stage']
19
+ }
20
+
21
+ export const initialRecords: DraftProjectRecord[] = [
22
+ ...initialProjects.map((project) => ({
23
+ ...project,
24
+ stage: 'published' as const,
25
+ createdBy: project.owner,
26
+ savedAt: dayjs().subtract(2, 'day').hour(9).minute(0).toISOString(),
27
+ })),
28
+ ...['移动端设计系统', '数据报表导出', '开放接口文档'].map((name, index) => ({
29
+ id: `PRJ-${String(19 + index).padStart(3, '0')}`,
30
+ name,
31
+ stage: 'draft' as const,
32
+ createdBy: currentUser,
33
+ owner: currentUser,
34
+ status: index === 1 ? undefined : ('planning' as const),
35
+ priority: 'medium' as const,
36
+ progress: 0,
37
+ savedAt: dayjs().subtract([1, 3, 20][index]!, 'hour').toISOString(),
38
+ })),
39
+ ]
@@ -0,0 +1,48 @@
1
+ import type { DraftTableView } from '@/components/DraftProTable/DraftProTable'
2
+ import type { ProjectSearchValues } from '../../config'
3
+ import { currentUser, type DraftProjectRecord, type SaveProjectInput } from '.'
4
+
5
+ export function saveProjectRecord(records: DraftProjectRecord[], input: SaveProjectInput) {
6
+ const { values, original, stage } = input
7
+ const nextId =
8
+ Math.max(0, ...records.map((record) => Number(record.id.replace('PRJ-', '')) || 0)) + 1
9
+ const record: DraftProjectRecord = {
10
+ ...values,
11
+ name: values.name?.trim(),
12
+ description: values.description?.trim(),
13
+ progress:
14
+ values.status === 'completed' ? 100 : values.status === 'planning' ? 0 : values.progress,
15
+ id: original?.id ?? `PRJ-${String(nextId).padStart(3, '0')}`,
16
+ stage,
17
+ createdBy: original?.createdBy ?? currentUser,
18
+ savedAt: new Date().toISOString(),
19
+ }
20
+ return [record, ...records.filter((item) => item.id !== record.id)]
21
+ }
22
+
23
+ export function filterProjectRecords({
24
+ records,
25
+ view,
26
+ search,
27
+ }: {
28
+ records: DraftProjectRecord[]
29
+ view: DraftTableView
30
+ search: ProjectSearchValues
31
+ }) {
32
+ const keyword = search.name?.trim().toLowerCase()
33
+ return records
34
+ .filter((record) => {
35
+ if (
36
+ view === 'draft'
37
+ ? record.stage !== 'draft' || record.createdBy !== currentUser
38
+ : record.stage !== 'published'
39
+ )
40
+ return false
41
+ if (keyword && !`${record.name ?? '未命名项目'} ${record.id}`.toLowerCase().includes(keyword))
42
+ return false
43
+ if (search.status && record.status !== search.status) return false
44
+ if (search.owner && record.owner !== search.owner) return false
45
+ return true
46
+ })
47
+ .sort((first, second) => second.savedAt.localeCompare(first.savedAt))
48
+ }