diff --git a/package.json b/package.json index 0735c8c..c88bb3f 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,14 @@ "@ant-design/icons": "^6.0.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@kevisual/cache": "^0.0.1", "@kevisual/codemirror": "workspace:*", "@kevisual/components": "workspace:*", "@kevisual/container": "1.0.0", "@kevisual/query": "^0.0.15", "@kevisual/query-config": "workspace:*", "@kevisual/query-login": "workspace:*", + "@kevisual/query-upload": "workspace:*", "@kevisual/resources": "workspace:*", "@monaco-editor/react": "^4.7.0", "@mui/material": "^7.0.1", @@ -47,6 +49,7 @@ "qrcode": "^1.5.4", "react": "19.1.0", "react-dom": "19.1.0", + "react-dropzone": "^14.3.8", "react-hook-form": "^7.55.0", "react-i18next": "^15.4.1", "react-resizable-panels": "^2.1.7", diff --git a/packages/codemirror/src/editor/editor.ts b/packages/codemirror/src/editor/editor.ts index ac26f7d..83d6e9b 100644 --- a/packages/codemirror/src/editor/editor.ts +++ b/packages/codemirror/src/editor/editor.ts @@ -20,6 +20,7 @@ type BaseEditorOpts = { open?: boolean; overrideList?: Completion[]; }; + onChange?: (value: string) => void; }; export class BaseEditor { editor?: EditorView; @@ -32,7 +33,7 @@ export class BaseEditor { open?: boolean; overrideList?: Completion[]; }; - + onChange?: (value: string) => void; constructor(opts?: BaseEditorOpts) { this.filename = opts?.filename || ''; this.language = opts?.language || 'typescript'; @@ -43,6 +44,7 @@ export class BaseEditor { } this.autoComplete = opts?.autoComplete || { open: true, overrideList: [] }; this.onChangeCompartment = new Compartment(); // 创建一个用于 onChange 的独立扩展 + this.onChange = opts?.onChange; } onEditorChange(view: EditorView) { console.log('onChange', view); @@ -54,6 +56,7 @@ export class BaseEditor { this.el = el; const onChangeCompartment = this.onChangeCompartment!; const language = this.language; + const onChange = this.onChange; const extensions: Extension[] = [ vscodeLight, formatKeymap, @@ -91,7 +94,15 @@ export class BaseEditor { parent: el, extensions: [ ...extensions, // - onChangeCompartment.of([]), + onChangeCompartment.of([ + [ + EditorView.updateListener.of((update) => { + if (update.changes.length > 0) { + onChange?.(update.state.doc.toString()); + } + }), + ], + ]), ], }); } diff --git a/packages/resources/src/index.ts b/packages/resources/src/index.ts index fb079bd..268d89a 100644 --- a/packages/resources/src/index.ts +++ b/packages/resources/src/index.ts @@ -3,7 +3,7 @@ export { PermissionManager } from './pages/file/modules/PermissionManager.tsx'; export { PermissionModal, usePermissionModal } from './pages/file/modules/PermissionModal.tsx'; export { iText } from './i-text/index.ts'; -export { uploadFiles, uploadFileChunked, getDirectoryAndName, toFile, createDirectory } from './pages/upload/app'; +export { uploadFiles, uploadFileChunked, getDirectoryAndName, createDirectory } from './pages/upload/app'; export { DialogDirectory, DialogDeleteDirectory } from './pages/upload/DialogDirectory'; -export { uploadChunkV2 } from './pages/upload/v2/upload-chunk'; +export { uploadChunkV2, toFile } from './pages/upload/v2/upload-chunk'; diff --git a/packages/resources/src/pages/upload/v2/upload-chunk.ts b/packages/resources/src/pages/upload/v2/upload-chunk.ts index 196a2e6..14b7b48 100644 --- a/packages/resources/src/pages/upload/v2/upload-chunk.ts +++ b/packages/resources/src/pages/upload/v2/upload-chunk.ts @@ -4,7 +4,7 @@ import { Id, toast } from 'react-toastify'; import { nanoid } from 'nanoid'; import { toastLogin } from '@kevisual/resources/pages/message/ToastLogin'; import { uploadFileChunked, UploadProgress } from '@kevisual/query-upload/query-upload'; - +import { toFile } from '@kevisual/query-upload/query-upload'; export type ConvertOpts = { appKey?: string; version?: string; @@ -12,6 +12,10 @@ export type ConvertOpts = { directory?: string; isPublic?: boolean; filename?: string; + /** + * 是否不检查应用文件, 默认 true,默认不检测 + */ + noCheckAppFiles?: boolean; }; export const uploadChunkV2 = async (file: File, opts: ConvertOpts) => { @@ -53,3 +57,4 @@ export const uploadChunkV2 = async (file: File, opts: ConvertOpts) => { }); return result; }; +export { toFile }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 177950a..21a1b4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@emotion/styled': specifier: ^11.14.0 version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0) + '@kevisual/cache': + specifier: ^0.0.1 + version: 0.0.1(rollup@4.37.0)(tslib@2.8.1)(typescript@5.8.2) '@kevisual/codemirror': specifier: workspace:* version: link:packages/codemirror @@ -35,6 +38,9 @@ importers: '@kevisual/query-login': specifier: workspace:* version: link:submodules/query-login + '@kevisual/query-upload': + specifier: workspace:* + version: link:submodules/query-upload '@kevisual/resources': specifier: workspace:* version: link:packages/resources @@ -98,6 +104,9 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) + react-dropzone: + specifier: ^14.3.8 + version: 14.3.8(react@19.1.0) react-hook-form: specifier: ^7.55.0 version: 7.55.0(react@19.1.0) @@ -339,29 +348,29 @@ importers: dependencies: '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@19.1.0)(react@19.0.0) + version: 11.14.0(@types/react@19.1.0)(react@19.1.0) '@emotion/styled': specifier: ^11.14.0 - version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0) '@mui/material': - specifier: ^6.4.7 - version: 6.4.7(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^7.0.1 + version: 7.0.1(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: - specifier: 19.0.0 - version: 19.0.0 + specifier: 19.1.0 + version: 19.1.0 react-dom: - specifier: 19.0.0 - version: 19.0.0(react@19.0.0) + specifier: 19.1.0 + version: 19.1.0(react@19.1.0) react-hook-form: - specifier: ^7.54.2 - version: 7.54.2(react@19.0.0) + specifier: ^7.55.0 + version: 7.55.0(react@19.1.0) devDependencies: clsx: specifier: ^2.1.1 version: 2.1.1 tailwind-merge: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^3.1.0 + version: 3.1.0 packages/kevisual-official: dependencies: @@ -1548,35 +1557,12 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@mui/core-downloads-tracker@6.4.7': - resolution: {integrity: sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==} - '@mui/core-downloads-tracker@6.4.8': resolution: {integrity: sha512-vjP4+A1ybyCRhDZC7r5EPWu/gLseFZxaGyPdDl94vzVvk6Yj6gahdaqcjbhkaCrJjdZj90m3VioltWPAnWF/zw==} '@mui/core-downloads-tracker@7.0.1': resolution: {integrity: sha512-T5DNVnSD9pMbj4Jk/Uphz+yvj9dfpl2+EqsOuJtG12HxEihNG5pd3qzX5yM1Id4dDwKRvM3dPVcxyzavTFhJeA==} - '@mui/material@6.4.7': - resolution: {integrity: sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@mui/material-pigment-css': ^6.4.7 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@mui/material-pigment-css': - optional: true - '@types/react': - optional: true - '@mui/material@6.4.8': resolution: {integrity: sha512-5S9UTjKZZBd9GfbcYh/nYfD9cv6OXmj5Y7NgKYfk7JcSoshp8/pW5zP4wecRiroBSZX8wcrywSgogpVNO+5W0Q==} engines: {node: '>=14.0.0'} @@ -1617,16 +1603,6 @@ packages: '@types/react': optional: true - '@mui/private-theming@6.4.6': - resolution: {integrity: sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@mui/private-theming@6.4.8': resolution: {integrity: sha512-sWwQoNSn6elsPTAtSqCf+w5aaGoh7AASURNmpy+QTTD/zwJ0Jgwt0ZaaP6mXq2IcgHxYnYloM/+vJgHPMkRKTQ==} engines: {node: '>=14.0.0'} @@ -1647,19 +1623,6 @@ packages: '@types/react': optional: true - '@mui/styled-engine@6.4.6': - resolution: {integrity: sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@mui/styled-engine@6.4.8': resolution: {integrity: sha512-oyjx1b1FvUCI85ZMO4trrjNxGm90eLN3Ohy0AP/SqK5gWvRQg1677UjNf7t6iETOKAleHctJjuq0B3aXO2gtmw==} engines: {node: '>=14.0.0'} @@ -1686,22 +1649,6 @@ packages: '@emotion/styled': optional: true - '@mui/system@6.4.7': - resolution: {integrity: sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - '@mui/system@6.4.8': resolution: {integrity: sha512-gV7iBHoqlsIenU2BP0wq14BefRoZcASZ/4LeyuQglayBl+DfLX5rEd3EYR3J409V2EZpR0NOM1LATAGlNk2cyA==} engines: {node: '>=14.0.0'} @@ -1734,14 +1681,6 @@ packages: '@types/react': optional: true - '@mui/types@7.2.21': - resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@mui/types@7.2.24': resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==} peerDependencies: @@ -1758,16 +1697,6 @@ packages: '@types/react': optional: true - '@mui/utils@6.4.6': - resolution: {integrity: sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@mui/utils@6.4.8': resolution: {integrity: sha512-C86gfiZ5BfZ51KqzqoHi1WuuM2QdSKoFhbkZeAfQRB+jCc4YNhhj11UXFVMMsqBgZ+Zy8IHNJW3M9Wj/LOwRXQ==} engines: {node: '>=14.0.0'} @@ -6280,33 +6209,10 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - '@mui/core-downloads-tracker@6.4.7': {} - '@mui/core-downloads-tracker@6.4.8': {} '@mui/core-downloads-tracker@7.0.1': {} - '@mui/material@6.4.7(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@babel/runtime': 7.26.0 - '@mui/core-downloads-tracker': 6.4.7 - '@mui/system': 6.4.7(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0) - '@mui/types': 7.2.21(@types/react@19.1.0) - '@mui/utils': 6.4.6(@types/react@19.1.0)(react@19.0.0) - '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.1.0) - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - react-is: 19.1.0 - react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.0)(react@19.0.0) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0) - '@types/react': 19.1.0 - '@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@19.0.12)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.12)(react@19.0.0))(@types/react@19.0.12)(react@19.0.0))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.10 @@ -6370,15 +6276,6 @@ snapshots: '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0) '@types/react': 19.1.0 - '@mui/private-theming@6.4.6(@types/react@19.1.0)(react@19.0.0)': - dependencies: - '@babel/runtime': 7.26.10 - '@mui/utils': 6.4.8(@types/react@19.1.0)(react@19.0.0) - prop-types: 15.8.1 - react: 19.0.0 - optionalDependencies: - '@types/react': 19.1.0 - '@mui/private-theming@6.4.8(@types/react@19.0.12)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.10 @@ -6406,19 +6303,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.0 - '@mui/styled-engine@6.4.6(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0))(react@19.0.0)': - dependencies: - '@babel/runtime': 7.26.10 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/sheet': 1.4.0 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 19.0.0 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.0)(react@19.0.0) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0) - '@mui/styled-engine@6.4.8(@emotion/react@11.14.0(@types/react@19.0.12)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.12)(react@19.0.0))(@types/react@19.0.12)(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.10 @@ -6458,22 +6342,6 @@ snapshots: '@emotion/react': 11.14.0(@types/react@19.1.0)(react@19.1.0) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0) - '@mui/system@6.4.7(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0)': - dependencies: - '@babel/runtime': 7.26.10 - '@mui/private-theming': 6.4.6(@types/react@19.1.0)(react@19.0.0) - '@mui/styled-engine': 6.4.6(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0))(react@19.0.0) - '@mui/types': 7.2.24(@types/react@19.1.0) - '@mui/utils': 6.4.8(@types/react@19.1.0)(react@19.0.0) - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 19.0.0 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.0)(react@19.0.0) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.0.0))(@types/react@19.1.0)(react@19.0.0) - '@types/react': 19.1.0 - '@mui/system@6.4.8(@emotion/react@11.14.0(@types/react@19.0.12)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.12)(react@19.0.0))(@types/react@19.0.12)(react@19.0.0))(@types/react@19.0.12)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.10 @@ -6522,10 +6390,6 @@ snapshots: '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0) '@types/react': 19.1.0 - '@mui/types@7.2.21(@types/react@19.1.0)': - optionalDependencies: - '@types/react': 19.1.0 - '@mui/types@7.2.24(@types/react@19.0.12)': optionalDependencies: '@types/react': 19.0.12 @@ -6540,18 +6404,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.0 - '@mui/utils@6.4.6(@types/react@19.1.0)(react@19.0.0)': - dependencies: - '@babel/runtime': 7.26.10 - '@mui/types': 7.2.24(@types/react@19.1.0) - '@types/prop-types': 15.7.14 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 19.0.0 - react-is: 19.1.0 - optionalDependencies: - '@types/react': 19.1.0 - '@mui/utils@6.4.8(@types/react@19.0.12)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.10 @@ -9603,6 +9455,13 @@ snapshots: prop-types: 15.8.1 react: 19.0.0 + react-dropzone@14.3.8(react@19.1.0): + dependencies: + attr-accept: 2.2.5 + file-selector: 2.1.2 + prop-types: 15.8.1 + react: 19.1.0 + react-hook-form@7.54.2(react@19.0.0): dependencies: react: 19.0.0 diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 97c9378..d8e3c61 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -87,5 +87,9 @@ "delete_directory_success": "Delete directory success", "create_directory": "Create Directory", "create_directory_success": "Create directory success", - "Apply Change Name": "Apply Change Name" + "Apply Change Name": "Apply Change Name", + "Tips": "Tips", + "Check username introduction": "Check your username, if the username is not available, you can contact the customer service to modify.", + "Delete and remove file": "Delete and remove file", + "Delete App Introduce": "Delete App, when you click the delete button, the file will be deleted from the file management system." } \ No newline at end of file diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 2009e41..5649116 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -87,5 +87,9 @@ "delete_directory_success": "删除目录成功", "create_directory": "创建目录", "create_directory_success": "创建目录成功", - "Apply Change Name": "申请修改名称" + "Apply Change Name": "申请修改名称", + "Tips": "提示", + "Check username introduction": "查看是否可以取名,如果当前用户名已存在,请更换其他用户名。其中用户名以@开头", + "Delete and remove file": "删除并删除文件", + "Delete App Introduce": "删除应用, 当点击同时删除文件,会把文件管理中,应用相关的存储的资源同步删除。" } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index c1613f8..decf0d0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { App as OrgApp } from './pages/org'; import { App as ConfigApp } from './pages/config'; import { App as PayApp } from './pages/pay'; import { App as DomainApp } from './pages/domain'; +import { App as HomeApp } from './pages/home'; import { basename } from './modules/basename'; import { Redirect } from './modules/Redirect'; import { CustomThemeProvider } from '@kevisual/components/theme/index.tsx'; @@ -82,6 +83,7 @@ export const App = () => { } /> } /> } /> + } /> 404} /> 404} /> diff --git a/src/modules/layout/LayoutUser.tsx b/src/modules/layout/LayoutUser.tsx index 5af25fe..7a4e250 100644 --- a/src/modules/layout/LayoutUser.tsx +++ b/src/modules/layout/LayoutUser.tsx @@ -12,6 +12,7 @@ import { useNewNavigate } from '../navicate'; import { LogOut, Map, SquareUser, Users, X, ArrowDownLeftFromSquareIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import React from 'react'; +import { useQuickMenu } from './Menu'; export const LayoutUser = () => { const { open, setOpen, isAdmin, ...store } = useLayoutStore( @@ -77,7 +78,6 @@ export const LayoutUser = () => { const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; - const handleClose = () => { setAnchorEl(null); }; diff --git a/src/modules/layout/Menu.tsx b/src/modules/layout/Menu.tsx index e2792b0..897e755 100644 --- a/src/modules/layout/Menu.tsx +++ b/src/modules/layout/Menu.tsx @@ -12,14 +12,29 @@ import SmileOutlined from '@ant-design/icons/SmileOutlined'; import { X, Settings } from 'lucide-react'; import { useNewNavigate } from '../navicate'; import { useTranslation } from 'react-i18next'; - +import { Map } from 'lucide-react'; +export const useQuickMenu = () => { + const { t } = useTranslation(); + return [ + { + title: t('User App'), + icon: , + link: '/app/edit/list', + }, + { + title: t('File App'), + icon: , + link: '/file/edit/list', + }, + ]; +}; export const LayoutMenu = () => { const { t } = useTranslation(); const meun = [ { title: t('Home'), icon: , - link: '/map', + link: '/home', }, { title: t('User App'), @@ -37,6 +52,7 @@ export const LayoutMenu = () => { link: '/container/edit/list', }, { title: t('Config'), icon: , link: '/config/edit/list' }, + { title: t('Map'), icon: , link: '/map' }, { title: t('About'), icon: , diff --git a/src/modules/layout/index.tsx b/src/modules/layout/index.tsx index 5ba48bd..41445ad 100644 --- a/src/modules/layout/index.tsx +++ b/src/modules/layout/index.tsx @@ -1,7 +1,7 @@ import { MenuOutlined, SwapOutlined } from '@ant-design/icons'; import { Tooltip } from '@mui/material'; -import { Outlet } from 'react-router-dom'; -import { LayoutMenu } from './Menu'; +import { Outlet, useNavigate } from 'react-router-dom'; +import { LayoutMenu, useQuickMenu } from './Menu'; import { useLayoutStore, usePlatformStore } from './store'; import { useShallow } from 'zustand/react/shallow'; import { useEffect, useLayoutEffect, useState } from 'react'; @@ -46,6 +46,8 @@ export const LayoutMain = (props: LayoutMainProps) => { }), ); const { isMac, mount, isElectron } = platformStore; + const navigate = useNavigate(); + const quickMenu = useQuickMenu(); useLayoutEffect(() => { platformStore.init(); @@ -88,7 +90,26 @@ export const LayoutMain = (props: LayoutMainProps) => {
- {props.title} +
+
{props.title}
+
+ {quickMenu.map((item, index) => { + const isActive = location.pathname.includes(item.link); + return ( +
{ + navigate(item.link); + }}> + +
{item.icon}
+
+
+ ); + })} +
+
diff --git a/src/modules/layout/store/index.ts b/src/modules/layout/store/index.ts index 8c78376..4d7bcbb 100644 --- a/src/modules/layout/store/index.ts +++ b/src/modules/layout/store/index.ts @@ -64,8 +64,9 @@ export type LayoutStore = { switchOrg: (username?: string, type?: 'user' | 'org') => Promise; isAdmin: boolean; setIsAdmin: (isAdmin: boolean) => void; + checkHasOrg: () => boolean; }; -export const useLayoutStore = create((set) => ({ +export const useLayoutStore = create((set, get) => ({ open: false, setOpen: (open) => set({ open }), me: {}, @@ -92,4 +93,11 @@ export const useLayoutStore = create((set) => ({ }, isAdmin: false, setIsAdmin: (isAdmin) => set({ isAdmin }), + checkHasOrg: () => { + const user = get().me || {}; + if (!user.orgs) { + return false; + } + return user?.orgs?.length > 0; + }, })); diff --git a/src/pages/app/edit/AppVersionList.tsx b/src/pages/app/edit/AppVersionList.tsx index 8ac4259..9151f87 100644 --- a/src/pages/app/edit/AppVersionList.tsx +++ b/src/pages/app/edit/AppVersionList.tsx @@ -8,7 +8,6 @@ import FileOutlined from '@ant-design/icons/FileOutlined'; import LeftOutlined from '@ant-design/icons/LeftOutlined'; import LinkOutlined from '@ant-design/icons/LinkOutlined'; import PlusOutlined from '@ant-design/icons/PlusOutlined'; -import { useModal } from '@kevisual/components/modal/Confirm.tsx'; import { Tooltip } from '@mui/material'; import { isObjectNull } from '@/utils/is-null'; import { FileUpload } from '../modules/FileUpload'; @@ -22,6 +21,7 @@ import { IconButton } from '@kevisual/components/button/index.tsx'; import { useForm, Controller } from 'react-hook-form'; import { TextField } from '@mui/material'; import { pick } from 'lodash-es'; +import { useAppDeleteModalStore, AppDeleteModal } from '../modules/AppDeleteModal'; const FormModal = () => { const { t } = useTranslation(); @@ -108,8 +108,14 @@ export const AppVersionList = () => { }; }), ); + const appDeleteModalStore = useAppDeleteModalStore( + useShallow((state) => { + return { + onClickDelete: state.onClickDelete, + }; + }), + ); const navigate = useNewNavigate(); - const [modal, contextHolder] = useModal(); const [isUpload, setIsUpload] = useState(false); useEffect(() => { // fetch app version list @@ -178,13 +184,7 @@ export const AppVersionList = () => {
- {contextHolder}
{isUpload && (
@@ -249,6 +248,7 @@ export const AppVersionList = () => { )}
+
); }; @@ -268,8 +268,8 @@ export const AppVersionFile = () => { return ( <>
version: {versionStore.formData.version}
-
-
+
+
Files
@@ -284,7 +284,7 @@ export const AppVersionFile = () => { const _path = file.path || ''; const path = _path.replace(prefix, ''); return ( -
+
{/*
{file.name}
*/}
diff --git a/src/pages/app/edit/List.tsx b/src/pages/app/edit/List.tsx index 1dc7767..69c2a64 100644 --- a/src/pages/app/edit/List.tsx +++ b/src/pages/app/edit/List.tsx @@ -1,15 +1,11 @@ import { useShallow } from 'zustand/react/shallow'; -import { useUserAppStore } from '../store'; +import { useAppVersionStore, useUserAppStore } from '../store'; import { useEffect, useMemo, useState } from 'react'; import { useModal } from '@kevisual/components/modal/Confirm.tsx'; -import DeleteOutlined from '@ant-design/icons/DeleteOutlined'; -import EditOutlined from '@ant-design/icons/EditOutlined'; import LinkOutlined from '@ant-design/icons/LinkOutlined'; import PlusOutlined from '@ant-design/icons/PlusOutlined'; -import UnorderedListOutlined from '@ant-design/icons/UnorderedListOutlined'; import CodeOutlined from '@ant-design/icons/CodeOutlined'; -import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined'; import { FormControlLabel, Switch, useTheme } from '@mui/material'; import { isObjectNull } from '@/utils/is-null'; import { queryLogin, useNewNavigate } from '@/modules'; @@ -29,6 +25,8 @@ import { useForm, Controller } from 'react-hook-form'; import { pick } from 'lodash-es'; import copy from 'copy-to-clipboard'; import { useLayoutStore } from '@/modules/layout/store'; +import { useAppDeleteModalStore, AppDeleteModal } from '../modules/AppDeleteModal'; +import { AppWindow, Edit, Link, RefreshCcw, Share2, Trash } from 'lucide-react'; const FormModal = () => { const defaultValues = { @@ -57,7 +55,6 @@ const FormModal = () => { const open = containerStore.showEdit; if (open) { const isNull = isObjectNull(containerStore.userApp); - console.log('isNull', containerStore.userApp); if (isNull) { reset(defaultValues); } else { @@ -152,7 +149,7 @@ const ShareModal = () => { const permission = containerStore.userApp?.data?.permission || {}; const runtime = containerStore.userApp?.data?.runtime || []; if (isObjectNull(permission)) { - setPermission(null); + setPermission({ share: 'private' }); } else { setPermission(permission); } @@ -173,9 +170,9 @@ const ShareModal = () => { containerStore.setShowEdit(false); }; const { t } = useTranslation(); - console.log('runtime', runtime); const theme = useTheme(); const defaultProps = theme.components?.MuiTextField?.defaultProps as any; + const isAdmin = useLayoutStore(useShallow((state) => state.isAdmin)); return ( { setPermission(value); }} /> - { - setRuntime(e.target.value as string[]); - }} - options={[ - { - label: 'Node', - value: 'node', - }, - { - label: 'Browser', - value: 'browser', - }, - ]} - /> - } - /> + {isAdmin && ( + { + setRuntime(e.target.value as string[]); + }} + options={[ + { + label: 'Node', + value: 'node', + }, + { + label: 'Browser', + value: 'browser', + }, + ]} + /> + } + /> + )}
@@ -250,6 +249,20 @@ export const List = () => { }; }), ); + const appVersionStore = useAppVersionStore( + useShallow((state) => { + return { + publishVersion: state.publishVersion, + }; + }), + ); + const appDeleteModalStore = useAppDeleteModalStore( + useShallow((state) => { + return { + onClickDelete: state.onClickDelete, + }; + }), + ); const navicate = useNewNavigate(); useEffect(() => { userAppStore.getList(); @@ -299,11 +312,22 @@ export const List = () => { {userAppStore.list.map((item) => { const isRunning = item.status === 'running'; const hasDescription = !!item.description; - const content = marked.parse(item.description); + // const content = marked.parse(item.description); + const content = item.description; return (
{}}> - {item.title} + + {item.title} + {item.key} + + }> +
+ {item.title} {item.key} +
+
@@ -329,8 +353,10 @@ export const List = () => {
{t('app.version')}: {item.version}
+
-
+ {/*
*/} +
{content}
@@ -346,7 +372,7 @@ export const List = () => { userAppStore.setFormData(item); userAppStore.setShowEdit(true); }}> - + @@ -354,7 +380,7 @@ export const List = () => { onClick={() => { navicate(`/app/${item.key}/version/list`); }}> - + @@ -364,7 +390,15 @@ export const List = () => { userAppStore.setFormData(item); userAppStore.setShowShareEdit(true); }}> - + + + + + @@ -396,23 +430,16 @@ export const List = () => { message.error('The app is not running'); } }}> - + @@ -427,6 +454,7 @@ export const List = () => { {contextHolder} +
); }; diff --git a/src/pages/app/modules/AppDeleteModal.tsx b/src/pages/app/modules/AppDeleteModal.tsx new file mode 100644 index 0000000..4491f42 --- /dev/null +++ b/src/pages/app/modules/AppDeleteModal.tsx @@ -0,0 +1,79 @@ +import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { create } from 'zustand'; +import { useAppVersionStore, useUserAppStore } from '../store'; +import { useShallow } from 'zustand/shallow'; + +type AppDeleteModalStore = { + open: boolean; + setOpen: (open: boolean) => void; + app: any; + setApp: (app: any) => void; + type: 'user-app' | 'app-version'; + setType: (type: 'user-app' | 'app-version') => void; + onClickDelete: (type: 'user-app' | 'app-version', data: any) => void; +}; +export const useAppDeleteModalStore = create((set) => ({ + open: false, + setOpen: (open) => set({ open }), + app: null, + setApp: (app) => set({ app }), + type: 'user-app', + setType: (type) => set({ type }), + onClickDelete: (type, data) => { + set({ open: true, type, app: data }); + }, +})); + +export const AppDeleteModal = () => { + const { t } = useTranslation(); + const { open, setOpen, app, type, setType } = useAppDeleteModalStore(); + const userAppStore = useUserAppStore( + useShallow((state) => { + return { + deleteData: state.deleteData, + }; + }), + ); + const appVersionStore = useAppVersionStore( + useShallow((state) => { + return { + deleteData: state.deleteData, + }; + }), + ); + const onClose = () => { + setOpen(false); + }; + const onDelete = (deleteFile = false) => { + if (type === 'user-app') { + userAppStore.deleteData(app.id, deleteFile); + } else { + appVersionStore.deleteData(app.id, deleteFile); + } + setOpen(false); + }; + + return ( + + {t('Tips')} + +
+

{t('Delete App Introduce')}

+
+
+ + + + + +
+ ); +}; diff --git a/src/pages/app/store/app-version.ts b/src/pages/app/store/app-version.ts index 2bff530..1be8150 100644 --- a/src/pages/app/store/app-version.ts +++ b/src/pages/app/store/app-version.ts @@ -17,8 +17,14 @@ type AppVersionStore = { app: any; getApp: (key: string, force?: boolean) => Promise; updateData: (data: any) => Promise; - deleteData: (id: string) => Promise; - publishVersion: (data: any) => Promise; + /** + * 删除应用版本 + * @param id 应用版本id + * @param deleteFile 是否删除文件 + * @returns + */ + deleteData: (id: string, deleteFile?: boolean) => Promise; + publishVersion: (data: { id?: string; appKey?: string; version?: string }, opts?: { showToast?: boolean }) => Promise; }; export const useAppVersionStore = create((set, get) => { return { @@ -94,12 +100,13 @@ export const useAppVersionStore = create((set, get) => { message.error(res.message || 'Request failed'); } }, - deleteData: async (id) => { + deleteData: async (id, deleteFile = false) => { const { getList } = get(); const res = await query.post({ path: 'app', key: 'delete', id, + deleteFile, }); if (res.code === 200) { getList(); @@ -108,18 +115,24 @@ export const useAppVersionStore = create((set, get) => { message.error(res.message || 'Request failed'); } }, - publishVersion: async (data) => { + publishVersion: async (data, opts) => { + const showToast = opts?.showToast ?? true; const res = await query.post({ path: 'app', key: 'publish', data, }); if (res.code === 200) { - message.success('Success'); - get().getApp(get().key, true); + if (showToast) { + message.success('Success'); + get().getApp(get().key, true); + } } else { - message.error(res.message || 'Request failed'); + if (showToast) { + message.error(res.message || 'Request failed'); + } } + return res; }, }; }); diff --git a/src/pages/app/store/user-app.ts b/src/pages/app/store/user-app.ts index b808375..c54dbd8 100644 --- a/src/pages/app/store/user-app.ts +++ b/src/pages/app/store/user-app.ts @@ -11,7 +11,13 @@ type UserAppStore = { list: any[]; getList: () => Promise; updateData: (data: any) => Promise; - deleteData: (id: string) => Promise; + /** + * 删除用户应用 + * @param id 用户应用id + * @param deleteFile 是否删除文件 + * @returns + */ + deleteData: (id: string, deleteFile?: boolean) => Promise; showShareEdit: boolean; setShowShareEdit: (showShareEdit: boolean) => void; userApp: any; @@ -56,12 +62,13 @@ export const useUserAppStore = create((set, get) => { message.error(res.message || 'Request failed'); } }, - deleteData: async (id) => { + deleteData: async (id, deleteFile = false) => { const { getList } = get(); const res = await query.post({ path: 'user-app', key: 'delete', id, + deleteFile, }); if (res.code === 200) { getList(); diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx new file mode 100644 index 0000000..0c8429f --- /dev/null +++ b/src/pages/home/Home.tsx @@ -0,0 +1,107 @@ +import CloudUploadOutlined from '@ant-design/icons/CloudUploadOutlined'; + +import { BaseEditor } from '@kevisual/codemirror/editor/editor.ts'; +import { IconButton } from '@kevisual/components/button/index.tsx'; +import { Button, Tooltip } from 'antd'; +import { UploadIcon } from 'lucide-react'; +import { useEffect, useRef, useState } from 'react'; +import { useDropzone } from 'react-dropzone'; +import { CacheWorkspace } from '@kevisual/cache'; +import { useHomeStore } from './store/index.ts'; +import { UploadModal } from './module/UploadModal.tsx'; +import { useShallow } from 'zustand/shallow'; +import { toast } from 'react-toastify'; +import { SuccessModal } from './module/SuccessModal.tsx'; + +export const Home = () => { + const editorElRef = useRef(null); + const editorRef = useRef(null); + const { initApp, setOpenUploadModal, setText, filename } = useHomeStore( + useShallow((state) => ({ initApp: state.initApp, setOpenUploadModal: state.setOpenUploadModal, setText: state.setText, filename: state.filename })), + ); + const onDrop = (acceptedFiles) => { + console.log(acceptedFiles); + const file = acceptedFiles[0]; + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target?.result as string; + editorRef.current!.setContent(content); + }; + reader.readAsText(file); + }; + const { getRootProps, getInputProps } = useDropzone({ onDrop, accept: { 'text/html': ['.html'], 'text/javascript': ['.js'], 'text/css': ['.css'] } }); + + useEffect(() => { + initApp(); + initEditor(); + return () => { + if (editorRef.current) { + editorRef.current.destroyEditor(); + } + }; + }, []); + + const initEditor = async () => { + if (!editorElRef.current) return; + const cache = new CacheWorkspace(); + let cacheData = ''; + try { + cacheData = (await cache.storage.get('html-editor')) || ''; + } catch (error) { + console.error(error); + } + editorRef.current = new BaseEditor({ + filename: filename || 'index.html', + onChange: (value) => { + cache.storage.set('html-editor', value); + }, + }); + editorRef.current.createEditor(editorElRef.current); + setTimeout(() => { + editorRef.current!.setContent(cacheData); + }, 300); + }; + + return ( +
+
+
+ + { + const editorContent = editorRef.current?.getContent(); + if (editorContent) { + setOpenUploadModal(true); + setText(editorContent); + } else { + toast.error('请先输入代码'); + } + }}> + + + +
+
+ + + + + + +
+
+
+
+ + {'>'} 快速部署html小应用, 粘贴前端html代码。点击部署。(这个页面内容自动缓存到本地) + +
+
+
+
+
+ + +
+ ); +}; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx new file mode 100644 index 0000000..d6035b1 --- /dev/null +++ b/src/pages/home/index.tsx @@ -0,0 +1,12 @@ +import { Route, Routes } from 'react-router-dom'; +import { Main } from './layouts'; +import { Home } from './Home'; +export const App = () => { + return ( + + }> + }> + + + ); +}; diff --git a/src/pages/home/layouts/index.tsx b/src/pages/home/layouts/index.tsx new file mode 100644 index 0000000..e59862f --- /dev/null +++ b/src/pages/home/layouts/index.tsx @@ -0,0 +1,5 @@ +import { LayoutMain } from '@/modules/layout'; + +export const Main = () => { + return ; +}; diff --git a/src/pages/home/module/SuccessModal.tsx b/src/pages/home/module/SuccessModal.tsx new file mode 100644 index 0000000..5d18cca --- /dev/null +++ b/src/pages/home/module/SuccessModal.tsx @@ -0,0 +1,55 @@ +import { Dialog, DialogContent, DialogTitle } from '@mui/material'; +import { useShallow } from 'zustand/shallow'; +import { useHomeStore } from '../store'; +import { useMemo } from 'react'; +import { useLayoutStore } from '@/modules/layout/store'; +export const Label = ({ label, children }: { label: string; children: React.ReactNode }) => { + return ( +
+
{label}
+
{children}
+
+ ); +}; +export const SuccessModal = () => { + const { openSuccessModal, setOpenSuccessModal, appKey, version, filename } = useHomeStore( + useShallow((state) => ({ + openSuccessModal: state.openSuccessModal, + setOpenSuccessModal: state.setOpenSuccessModal, + appKey: state.appKey, // + version: state.version, // + filename: state.filename, // + })), + ); + const { me } = useLayoutStore(useShallow((state) => ({ me: state.me }))); + const link = useMemo(() => { + const _currentHref = new URL(window.location.href); + const username = me?.username; + const newHref = new URL(`/${username}/${appKey}/`, _currentHref.origin); + return newHref.toString(); + }, [me, appKey]); + return ( + setOpenSuccessModal(false)}> + 部署成功 + +
+ + + + +
+ 注: 如果需要其他人访问,需要设置共享。 +
+
+
+
+ ); +}; diff --git a/src/pages/home/module/UploadModal.tsx b/src/pages/home/module/UploadModal.tsx new file mode 100644 index 0000000..1b332f5 --- /dev/null +++ b/src/pages/home/module/UploadModal.tsx @@ -0,0 +1,98 @@ +import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import { useHomeStore } from '../store'; +import { Controller, useForm } from 'react-hook-form'; +import { TextField } from '@mui/material'; +import { useEffect } from 'react'; +import { customAlphabet } from 'nanoid'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { uploadFile } from './upload-file'; +import { useAppVersionStore } from '@/pages/app/store'; +import { useShallow } from 'zustand/shallow'; + +export const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10); +export const UploadModal = () => { + const { appKey, version, filename, openUploadModal, text, setOpenUploadModal, setAppKey, setVersion, setFilename, setOpenSuccessModal } = useHomeStore(); + const { control, handleSubmit, reset } = useForm(); + const { publishVersion } = useAppVersionStore(useShallow((state) => ({ publishVersion: state.publishVersion }))); + useEffect(() => { + if (openUploadModal) { + const randomAppKey = nanoid(4) + nanoid(4); + reset({ appKey: appKey || randomAppKey, version: version || '1.0.0', filename: filename || 'index.html' }); + } + }, [openUploadModal]); + const onSubmit = async (data: any) => { + console.log(data); + if (!text) { + toast.error('代码不能为空'); + return; + } + if (!data.appKey) { + toast.error('应用key不能为空'); + return; + } + if (!data.version) { + toast.error('版本不能为空'); + return; + } + if (!data.filename) { + toast.error('文件名不能为空'); + return; + } + setAppKey(data.appKey); + setVersion(data.version); + setFilename(data.filename); + const res = await uploadFile({ + appKey: data.appKey, + version: data.version, + filename: data.filename, + text, + }); + if (res?.code === 200) { + toast.success('部署成功'); + const toastId = toast.loading('发布中...'); + await new Promise((resolve) => setTimeout(resolve, 2000)); + const res = await publishVersion({ appKey: data.appKey, version: data.version }, { showToast: false }); + toast.dismiss(toastId); + if (res?.code === 200) { + toast.success('发布成功'); + setOpenSuccessModal(true); + } else { + toast.error(res?.message || '发布失败'); + } + } else { + toast.error(res?.message || '部署失败'); + } + }; + const { t } = useTranslation(); + return ( + setOpenUploadModal(false)}> + 部署页面 + +
+
+ + } /> +
+
+ + } /> +
+
+ + } /> +
+ + + + +
+
+
+ ); +}; diff --git a/src/pages/home/module/upload-file.ts b/src/pages/home/module/upload-file.ts new file mode 100644 index 0000000..80aa227 --- /dev/null +++ b/src/pages/home/module/upload-file.ts @@ -0,0 +1,29 @@ +import { uploadChunkV2, toFile } from '@kevisual/resources/index.ts'; +import { toast } from 'react-toastify'; + +type UploadFileOpts = { + appKey: string; + version: string; + filename: string; + text: string; +}; +const getFilenameExtension = (filename: string) => { + return filename.split('.').pop() || ''; +}; +const allowFilesName = ['js', 'css', 'json', 'html']; +export const uploadFile = async (uploadFileOpts: UploadFileOpts) => { + const { appKey, version, filename, text } = uploadFileOpts; + const extension = getFilenameExtension(filename); + if (!allowFilesName.includes(extension)) { + toast.error('文件类型不支持'); + return; + } + const file = toFile(text, filename); + const res = await uploadChunkV2(file, { + appKey, + version, + filename, + noCheckAppFiles: false, + }); + return res as any; +}; diff --git a/src/pages/home/store/index.ts b/src/pages/home/store/index.ts new file mode 100644 index 0000000..2e75627 --- /dev/null +++ b/src/pages/home/store/index.ts @@ -0,0 +1,51 @@ +import { create } from 'zustand'; +export type HomeStore = { + appKey: string; + version: string; + setAppKey: (appKey: string) => void; + setVersion: (version: string) => void; + filename: string; + setFilename: (filename: string) => void; + initApp: () => void; + openUploadModal: boolean; + setOpenUploadModal: (open: boolean) => void; + text: string; + setText: (text: string) => void; + openSuccessModal: boolean; + setOpenSuccessModal: (open: boolean) => void; +}; +export const useHomeStore = create((set) => ({ + appKey: '', + version: '', + setAppKey: (appKey: string) => { + set({ appKey }); + localStorage.setItem('home-app-key', appKey); + }, + setVersion: (version: string) => { + set({ version }); + localStorage.setItem('home-app-version', version); + }, + filename: '', + setFilename: (filename: string) => { + set({ filename }); + localStorage.setItem('home-file-name', filename); + }, + initApp: () => { + const appKey = localStorage.getItem('home-app-key') || ''; + const version = localStorage.getItem('home-app-version') || ''; + const filename = localStorage.getItem('home-file-name') || ''; + set({ appKey, version, filename }); + }, + openUploadModal: false, + setOpenUploadModal: (open: boolean) => { + set({ openUploadModal: open }); + }, + text: '', + setText: (text: string) => { + set({ text }); + }, + openSuccessModal: false, + setOpenSuccessModal: (open: boolean) => { + set({ openSuccessModal: open }); + }, +})); diff --git a/src/pages/map/index.tsx b/src/pages/map/index.tsx index b9f0487..f9fc497 100644 --- a/src/pages/map/index.tsx +++ b/src/pages/map/index.tsx @@ -1,10 +1,21 @@ import clsx from 'clsx'; import { useNewNavigate } from '@/modules'; import { useTranslation } from 'react-i18next'; +import { useLayoutStore } from '@/modules/layout/store'; +import { useShallow } from 'zustand/shallow'; +import { useMemo } from 'react'; const ServerPath = () => { const navigate = useNewNavigate(); const { t } = useTranslation(); + const layoutStore = useLayoutStore( + useShallow((state) => { + return { + isAdmin: state.isAdmin, + checkHasOrg: state.checkHasOrg, + }; + }), + ); const serverPath = [ { path: 'container', @@ -28,9 +39,9 @@ const ServerPath = () => { }, ]; return ( -
-

{t('Site Map')}

-
+
+

{t('Site Map')}

+
{serverPath.map((item) => { const links = item.links.map((link) => { @@ -54,7 +65,7 @@ const ServerPath = () => { navigate(`/${item.path}`); } }}> -
{_path}
+
{_path}
); }); diff --git a/src/pages/org/layouts/index.tsx b/src/pages/org/layouts/index.tsx index be7811e..a3006cc 100644 --- a/src/pages/org/layouts/index.tsx +++ b/src/pages/org/layouts/index.tsx @@ -1,4 +1,4 @@ import { LayoutMain } from '@/modules/layout'; export const Main = () => { - return ; + return ; }; diff --git a/src/pages/user/edit/Profile.tsx b/src/pages/user/edit/Profile.tsx index 9aaf1e7..9d31e22 100644 --- a/src/pages/user/edit/Profile.tsx +++ b/src/pages/user/edit/Profile.tsx @@ -1,4 +1,4 @@ -import { InputAdornment, TextField, Tooltip } from '@mui/material'; +import { Dialog, DialogContent, DialogTitle, InputAdornment, TextField, Tooltip } from '@mui/material'; import { useForm, Controller } from 'react-hook-form'; import { Button } from '@mui/material'; import { useUserStore } from '../store'; @@ -12,6 +12,56 @@ import { FileUpload } from '../module/FileUpload'; import { useTranslation } from 'react-i18next'; import { Edit } from 'lucide-react'; import { toast } from 'react-toastify'; +import { useAdminStore } from '../admin/store/admin-store'; +export const CheckUserExistModal = () => { + const { t } = useTranslation(); + const userStore = useUserStore( + useShallow((state) => { + return { + showCheckUserExist: state.showCheckUserExist, + setShowCheckUserExist: state.setShowCheckUserExist, + }; + }), + ); + const adminStore = useAdminStore( + useShallow((state) => { + return { + checkUserExist: state.checkUserExist, + }; + }), + ); + const onClose = () => { + userStore.setShowCheckUserExist(false); + }; + const [username, setUsername] = useState('@'); + + const onCheck = async () => { + const res = await adminStore.checkUserExist(username); + console.log(res); + if (res === false) { + toast.info('当前用户名可以修改,点击右上角关注公众号联系客服修改。', { + autoClose: 20000, + }); + // toast.success(t('Check success')); + } else { + toast.error('当前用户名已存在,请更换其他用户名。'); + } + }; + return ( + + {t('Tips')} + +
+
{t('Check username introduction')}
+ setUsername(e.target.value)} /> + +
+
+
+ ); +}; export const Profile = () => { const { t } = useTranslation(); @@ -32,6 +82,7 @@ export const Profile = () => { updateData: state.updateData, setFormData: state.setFormData, updateSelf: state.updateSelf, + setShowCheckUserExist: state.setShowCheckUserExist, }; }), ); @@ -104,9 +155,7 @@ export const Profile = () => { className='w-4 h-4 cursor-pointer text-primary' onClick={() => { console.log('onClick edit'); - toast.info('联系客服修改,因为名称和上传文件绑定了。', { - autoClose: 20000, - }); + userStore.setShowCheckUserExist(true); }} /> @@ -153,6 +202,7 @@ export const Profile = () => {
+
); diff --git a/src/pages/user/store/index.ts b/src/pages/user/store/index.ts index 0bbb023..7ac4307 100644 --- a/src/pages/user/store/index.ts +++ b/src/pages/user/store/index.ts @@ -6,6 +6,8 @@ type UserStore = { setShowEdit: (showEdit: boolean) => void; showNameEdit: boolean; setShowNameEdit: (showNameEdit: boolean) => void; + showCheckUserExist: boolean; + setShowCheckUserExist: (showCheckUserExist: boolean) => void; formData: any; setFormData: (formData: any) => void; loading: boolean; @@ -22,6 +24,8 @@ export const useUserStore = create((set, get) => { setShowEdit: (showEdit) => set({ showEdit }), showNameEdit: false, setShowNameEdit: (showNameEdit) => set({ showNameEdit }), + showCheckUserExist: false, + setShowCheckUserExist: (showCheckUserExist) => set({ showCheckUserExist }), formData: {}, setFormData: (formData) => set({ formData }), loading: false,