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.
- package/dist/index.js +15 -0
- package/package.json +1 -2
- package/template-nextjs-vinext-eslint/Dockerfile +4 -4
- package/template-nextjs-vinext-eslint/README.md +6 -2
- package/template-nextjs-vinext-eslint/docker-compose.yaml +4 -4
- package/template-nextjs-vinext-eslint/eslint.config.js +2 -0
- package/template-nextjs-vinext-eslint/package.json +28 -26
- package/template-nextjs-vinext-eslint/pnpm-workspace.yaml +13 -1
- package/template-nextjs-vinext-eslint/src/utils/request/core/index.ts +63 -45
- package/template-nextjs-vinext-eslint/src/utils/request/core/utils.ts +12 -6
- package/template-nextjs-vinext-eslint/vite.config.ts +1 -1
- package/template-react-rsbuild-biome/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-react-rsbuild-biome/AGENTS.md +11 -0
- package/template-react-rsbuild-biome/index.html +2 -2
- package/template-react-rsbuild-biome/package.json +25 -24
- package/template-react-rsbuild-biome/pnpm-workspace.yaml +5 -0
- package/template-react-rsbuild-biome/rsbuild.config.ts +1 -1
- package/template-react-rsbuild-biome/src/assets/icon/logo.svg +1 -1
- package/template-react-rsbuild-biome/src/assets/svg/draft.svg +4 -0
- package/template-react-rsbuild-biome/src/components/Icon/svg-icon/README.md +37 -0
- package/template-react-rsbuild-biome/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-react-rsbuild-biome/src/env.d.ts +7 -0
- package/template-react-rsbuild-biome/src/utils/request/index.ts +3 -4
- package/template-react-rsbuild-biome/tsconfig.json +1 -2
- package/template-taro-react-oxc/.oxlintrc.json +0 -1
- package/template-taro-react-oxc/package.json +53 -52
- package/template-taro-react-oxc/pnpm-workspace.yaml +46 -0
- package/template-taro-vue-eslint/package.json +56 -56
- package/template-vp-monorepo-react-hono/apps/web/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-vp-monorepo-react-hono/apps/web/AGENTS.md +11 -0
- package/template-vp-monorepo-react-hono/apps/web/package.json +1 -0
- package/template-vp-monorepo-react-hono/apps/web/src/assets/svg/draft.svg +4 -0
- package/template-vp-monorepo-react-hono/apps/web/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-monorepo-react-hono/apps/web/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-monorepo-react-hono/apps/web/tsconfig.json +1 -1
- package/template-vp-monorepo-react-hono/apps/web/vite.config.ts +2 -0
- package/template-vp-monorepo-react-hono/pnpm-workspace.yaml +1 -0
- package/template-vp-monorepo-react-nestjs/.agents/rules//344/273/243/347/240/201/350/256/276/350/256/241.md +4 -0
- package/template-vp-monorepo-react-nestjs/.agents/rules//345/205/261/344/272/253/344/273/243/347/240/201.md +4 -0
- 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
- package/template-vp-monorepo-react-nestjs/.spaces/server/04.upload.md +4504 -0
- 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
- 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
- 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
- package/template-vp-monorepo-react-nestjs/AGENTS.md +8 -12
- package/template-vp-monorepo-react-nestjs/apps/server/.agents/rules/error-handling.md +84 -0
- package/template-vp-monorepo-react-nestjs/apps/server/.env.development +60 -0
- package/template-vp-monorepo-react-nestjs/apps/server/.env.production +16 -0
- package/template-vp-monorepo-react-nestjs/apps/server/AGENTS.md +9 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/0000_military_colonel_america.sql +11 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/0001_loud_siren.sql +27 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/0000_snapshot.json +97 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/0001_snapshot.json +354 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/_journal.json +20 -0
- package/template-vp-monorepo-react-nestjs/apps/server/package.json +4 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/app.module.ts +14 -2
- package/template-vp-monorepo-react-nestjs/apps/server/src/common/adapters/fastify.adapter.ts +6 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/config/index.ts +2 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/config/queue.config.ts +105 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/config/storage.config.ts +33 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/database/schema.ts +57 -1
- package/template-vp-monorepo-react-nestjs/apps/server/src/main.ts +1 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.service.ts +1 -3
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/auth-repsponse.dto.ts +2 -2
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/queue-worker.bootstrap.ts +24 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.constants.ts +24 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.contracts.ts +35 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.module.ts +56 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.processor.ts +62 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.service.ts +58 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/error-catelog.spec.ts +34 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/dto/initiate-mutipart-upload.dto.ts +26 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/dto/upload-params.dto.ts +15 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/exact-size.transform.ts +66 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/minio-storage.adapter.ts +239 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/storage.port.ts +61 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.constants.ts +23 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.controller.ts +109 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.errors.ts +80 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.module.ts +20 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.reponsitory.ts +166 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.service.ts +696 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/utils/object-key.ts +10 -0
- package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.integration.spec.ts +120 -0
- package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.processor.spec.ts +63 -0
- package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.service.spec.ts +106 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- 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
- 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
- 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
- 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
- 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
- package/template-vp-monorepo-react-nestjs/apps/web/.env.dev +1 -1
- package/template-vp-monorepo-react-nestjs/apps/web/AGENTS.md +16 -0
- package/template-vp-monorepo-react-nestjs/apps/web/README.MD +153 -0
- package/template-vp-monorepo-react-nestjs/apps/web/index.html +3 -2
- package/template-vp-monorepo-react-nestjs/apps/web/package.json +6 -1
- package/template-vp-monorepo-react-nestjs/apps/web/public/favicon.svg +9 -1
- package/template-vp-monorepo-react-nestjs/apps/web/src/App.module.css +5 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/App.tsx +16 -9
- package/template-vp-monorepo-react-nestjs/apps/web/src/api/auth.ts +9 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/assets/svg/draft.svg +4 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/DraftProTable.module.css +78 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/DraftProTable.tsx +99 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/README.md +34 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/FullHeightProTable/FullHeightProTable.module.css +54 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/FullHeightProTable/FullHeightProTable.tsx +41 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Loading/PageLoading.module.css +7 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Loading/PageLoading.tsx +2 -1
- package/template-vp-monorepo-react-nestjs/apps/web/src/config/theme.ts +20 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/layouts/BasicLayout.module.css +17 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/layouts/BasicLayout.tsx +106 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/i18n/index.module.css +23 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/i18n/index.tsx +53 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/components/ProjectFormDialog.tsx +132 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/columns.tsx +121 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/index.ts +77 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/components/DraftProjectFormDialog.tsx +140 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/columns.tsx +127 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/index.ts +39 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/projects.ts +48 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/index.module.css +49 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/index.tsx +163 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/index.module.css +53 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/index.tsx +135 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/home/index.module.css +23 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/home/index.tsx +26 -43
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/api.ts +11 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/index.tsx +183 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/login.css +164 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/router/index.tsx +36 -12
- package/template-vp-monorepo-react-nestjs/apps/web/src/styles/index.css +11 -4
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/request/core/index.ts +55 -47
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/request/index.ts +10 -5
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/storage/cookie.ts +50 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/storage/session.ts +33 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/draft-projects.spec.ts +89 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/request.spec.ts +145 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/storage.spec.ts +108 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/tsconfig.json +7 -0
- package/template-vp-monorepo-react-nestjs/apps/web/tsconfig.json +1 -1
- package/template-vp-monorepo-react-nestjs/apps/web/vite.config.ts +7 -0
- package/template-vp-monorepo-react-nestjs/apps/web-vue/tsconfig.tsbuildinfo +1 -0
- package/template-vp-monorepo-react-nestjs/docker-compose.yml +19 -0
- package/template-vp-monorepo-react-nestjs/mise.toml +1 -1
- package/template-vp-monorepo-react-nestjs/package.json +1 -6
- package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +9 -1
- package/template-vp-react/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-vp-react/AGENTS.md +11 -0
- package/template-vp-react/commitlint.config.js +0 -0
- package/template-vp-react/package.json +21 -26
- package/template-vp-react/pnpm-workspace.yaml +6 -0
- package/template-vp-react/src/assets/svg/draft.svg +4 -0
- package/template-vp-react/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-react/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-react/src/utils/request/index.ts +18 -5
- package/template-vp-react/tsconfig.json +2 -3
- package/template-vp-react/vite.config.ts +2 -0
- package/template-vp-react-shadcn/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-vp-react-shadcn/.vscode/settings.json +2 -2
- package/template-vp-react-shadcn/{agents.md → AGENTS.md} +12 -2
- package/template-vp-react-shadcn/README.md +41 -0
- package/template-vp-react-shadcn/commitlint.config.js +1 -1
- package/template-vp-react-shadcn/components.json +1 -1
- package/template-vp-react-shadcn/package.json +25 -32
- package/template-vp-react-shadcn/pnpm-workspace.yaml +10 -0
- package/template-vp-react-shadcn/src/App.tsx +10 -10
- package/template-vp-react-shadcn/src/assets/svg/draft.svg +4 -0
- package/template-vp-react-shadcn/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-react-shadcn/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-react-shadcn/src/components/Loading/PageLoading.tsx +4 -4
- package/template-vp-react-shadcn/src/components/Player/VideoJS/index.tsx +94 -78
- package/template-vp-react-shadcn/src/components/Player/index.ts +1 -1
- package/template-vp-react-shadcn/src/components/ui/button.tsx +25 -32
- package/template-vp-react-shadcn/src/components/ui/spinner.tsx +8 -7
- package/template-vp-react-shadcn/src/components/ui/toast.tsx +229 -0
- package/template-vp-react-shadcn/src/lib/utils.ts +3 -3
- package/template-vp-react-shadcn/src/main.tsx +5 -4
- package/template-vp-react-shadcn/src/pages/home/index.tsx +43 -35
- package/template-vp-react-shadcn/src/router/index.tsx +15 -15
- package/template-vp-react-shadcn/src/styles/index.css +4 -4
- package/template-vp-react-shadcn/src/styles/shadcn.css +2 -2
- package/template-vp-react-shadcn/src/utils/env/index.tsx +2 -2
- package/template-vp-react-shadcn/src/utils/request/alova-core/index.ts +102 -30
- package/template-vp-react-shadcn/src/utils/request/alova-core/utils.ts +5 -2
- package/template-vp-react-shadcn/src/utils/request/index.ts +32 -19
- package/template-vp-react-shadcn/src/utils/request/readme.md +41 -38
- package/template-vp-react-shadcn/tsconfig.json +2 -3
- package/template-vp-react-shadcn/vite.config.ts +29 -15
- package/template-vp-vue-eslint-vapor/package.json +26 -33
- package/template-vp-vue-eslint-vapor/pnpm-workspace.yaml +29 -1
- package/template-vue-vp-eslint/README.md +3 -10
- package/template-vue-vp-eslint/package.json +27 -34
- package/template-vue-vp-eslint/pnpm-workspace.yaml +27 -2
- package/template-vue-vp-eslint/src/layout/default/index.vue +4 -3
- package/dist/index.mjs +0 -422
- package/template-vp-monorepo-react-nestjs/.agents/auth.config.ts +0 -0
- package/template-vp-monorepo-react-nestjs/.spaces/server/todo.md +0 -991
- package/template-vp-monorepo-react-nestjs/apps/server/README.md +0 -3
- package/template-vp-react-shadcn/.oxfmtrc.jsonc +0 -8
- package/template-vp-react-shadcn/.oxlintrc.jsonc +0 -3
- 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
|
+
}
|
package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/columns.tsx
ADDED
|
@@ -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
|
+
}
|
package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/index.ts
ADDED
|
@@ -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
|
+
}
|