commit dfdcbf7d65b3578a951bdc5aa4d5acc8e7916df1 Author: callmeyan Date: Sun Nov 24 12:56:31 2024 +0800 init and commit diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..c52bb53 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,24 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + '@typescript-eslint/no-explicit-any':[ + 'off' + ], + 'no-mixed-spaces-and-tabs':'off', + // 'no-control-regex':0 + }, +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ac7ef5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +test +_local +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/index.html b/index.html new file mode 100644 index 0000000..e384824 --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + + + 数字人直播 + + + +
+
+
+
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e9b6a9 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "digital-news-live", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "数字人直播间", + "scripts": { + "dev": "vite --host", + "build": "tsc && vite build", + "build-test": "tsc && vite build --mode=test", + "build-relative": "tsc && vite build --mode=relative", + "build-prod": "tsc && vite build --mode=production", + "clean-build": "rm -rf dist", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/modifiers": "^7.0.0", + "@dnd-kit/sortable": "^8.0.0", + "ahooks": "^3.8.1", + "antd": "^5.22.1", + "axios": "^1.7.7", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "file-saver": "^2.0.5", + "qs": "^6.12.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-player": "^2.16.0", + "react-router-dom": "^6.28.0", + "sass": "^1.81.0" + }, + "devDependencies": { + "@types/file-saver": "^2.0.7", + "@types/lodash": "^4.17.1", + "@types/node": "^20.12.11", + "@types/qs": "^6.9.15", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "postcss": "^8.4.40", + "tailwindcss": "^3.4.7", + "typescript": "^5.2.2", + "vite": "^5.2.0" + }, + "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..58cc413 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,8 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: { + + } + } + } \ No newline at end of file diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..bb12aa1 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..11e783a --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,15 @@ +import AppRouter from "@/routes"; +import {ConfigProvider} from "@/contexts/config"; +import {AuthProvider} from "@/contexts/auth"; + +function App() { + return ( + + + + + + ) +} + +export default App diff --git a/src/assets/core.scss b/src/assets/core.scss new file mode 100644 index 0000000..420be76 --- /dev/null +++ b/src/assets/core.scss @@ -0,0 +1,96 @@ +:root { + font-family: -apple-system, "PingFang SC", 'Microsoft YaHei', sans-serif; + line-height: 1.5; + font-weight: 400; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + --main-bg-color: #f6f6f6; + --brand-color: #43ABFF; + --navigation-width: 100vw; + --navigation-active-color: #ffe0e0; + --app-header-header: 90px; + --container-width: 1440px; +} +@tailwind base; +@tailwind components; +@tailwind utilities; + + +.btn { + @apply px-5 py-2 rounded-md bg-white border text-sm; + &:hover { + @apply bg-gray-100; + } + + &.btn-primary { + @apply bg-blue-500 text-white border-blue-500; + &:hover { + @apply bg-blue-600; + } + } +} +.card{ + @apply bg-white rounded-lg p-5 my-10; +} +.radio-icon,.checkbox-icon{ + @apply w-4 h-4 mr-2 border border-gray-400 rounded-2xl inline-block flex items-center justify-center relative; + padding: 3px; + &:after{ + @apply inline-block bg-blue-500 w-full h-full; + content: ' '; + border-radius: 50%; + opacity: 0; + } +} +.checkbox-icon{ + @apply rounded; + padding: 0; + &:before{ + content: ' '; + display: inline-block; + border-left: solid 2px #fff; + border-bottom: solid 2px #fff; + height: 6px; + width: 10px; + position: absolute; + transform: rotate(-45deg) translateY(-1px) translateX(1px); + opacity: 0; + } + &:after{ + border-radius: 2px; + } +} +.ant-select-item-option { + &.ant-select-item-option-selected{ + .radio-icon,.checkbox-icon{ + @apply border-blue-500; + &:after,&:before { + opacity: 1; + } + } + } +} +.select-hide-checked{ + .ant-select-item-option-selected{ + .ant-select-item-option-state{ + opacity: 0 !important; + } + } +} +.select-no-wrap{ + .ant-select-selector{ + .ant-select-selection-overflow{ + @apply flex flex-nowrap; + } + } +} +.simple-pagination{ + .ant-pagination-simple-pager{ + input[type=text]{ + width: auto; + display: inline-block; + } + } +} \ No newline at end of file diff --git a/src/assets/images/avatar.png b/src/assets/images/avatar.png new file mode 100644 index 0000000..2dfdb93 Binary files /dev/null and b/src/assets/images/avatar.png differ diff --git a/src/assets/images/blank-qr-code.png b/src/assets/images/blank-qr-code.png new file mode 100644 index 0000000..19a01d8 Binary files /dev/null and b/src/assets/images/blank-qr-code.png differ diff --git a/src/assets/images/error/Error404.png b/src/assets/images/error/Error404.png new file mode 100644 index 0000000..e860e76 Binary files /dev/null and b/src/assets/images/error/Error404.png differ diff --git a/src/assets/images/error/Error500.png b/src/assets/images/error/Error500.png new file mode 100644 index 0000000..9d104d6 Binary files /dev/null and b/src/assets/images/error/Error500.png differ diff --git a/src/assets/images/error/TwoCone.png b/src/assets/images/error/TwoCone.png new file mode 100644 index 0000000..92790e1 Binary files /dev/null and b/src/assets/images/error/TwoCone.png differ diff --git a/src/assets/images/error/coming-soon.png b/src/assets/images/error/coming-soon.png new file mode 100644 index 0000000..c13dfe0 Binary files /dev/null and b/src/assets/images/error/coming-soon.png differ diff --git a/src/assets/images/error/under-construction.svg b/src/assets/images/error/under-construction.svg new file mode 100644 index 0000000..576f362 --- /dev/null +++ b/src/assets/images/error/under-construction.svg @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/index.scss b/src/assets/index.scss new file mode 100644 index 0000000..c9f76ac --- /dev/null +++ b/src/assets/index.scss @@ -0,0 +1,91 @@ +@use 'core.scss'; + + +* { + margin: 0; + padding: 0; + outline: none; +} + +.box-sizing { + box-sizing: border-box; +} + +body { + font-size: 16px; + font-family: -apple-system, "PingFang SC", 'Microsoft YaHei', sans-serif; + background-color: var(--main-bg-color); + min-width: 1000px; +} + + +.dashboard-layout { + background-color: var(--main-bg-color); +} + +.navigation-container { + //width: var(--navigation-width); + background-color: #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + + .nav-item { + padding: 0px 30px; + &.active{ + @apply text-blue-500; + } + } +} + +.app-header { + @apply w-full navigation-container flex justify-between items-center p-basic fixed top-0 inset-x-0 z-10; + height: var(--app-header-header); +} +.app-content{ + padding-top: var(--app-header-header); +} + +.logo-container { + svg { + max-height: 100%; + display: block; + width: auto; + } +} + +.container { + max-width: 90%; + width: var(--container-width,1200px); + margin: 0 auto; +} + +.article-action-icon{ + @apply cursor-pointer text-gray-500 hover:text-blue-500 block; +} + +img{ + max-width: 100%; +} + +@media screen and (max-width: 2000px) { + :root { + line-height: 1.2; + --app-header-header: 70px; + } + .logo-container { + height: 36px; + } +} + +@media screen and (max-width: 1280px) { + :root { + --navigation-width: 200px; + } + .container { + max-width: 98%; + padding: 20px 0; + + } +} + +@media screen and (max-width: 800px) { +} \ No newline at end of file diff --git a/src/assets/loading.svg b/src/assets/loading.svg new file mode 100644 index 0000000..02ec790 --- /dev/null +++ b/src/assets/loading.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/article/article.module.scss b/src/components/article/article.module.scss new file mode 100644 index 0000000..7363bc4 --- /dev/null +++ b/src/components/article/article.module.scss @@ -0,0 +1,54 @@ +.blockContainer { + @apply flex mb-5; +} + +.block { + @apply border border-gray-300 border-dashed p-3 rounded flex-1; + + &:last-child { + @apply mb-0; + } + + .blockBody { + @apply flex flex-col gap-3 + } +} + +.blockItem { + +} + +.group { +} + +.image { + @apply border border-blue-200 p-2 flex-1 rounded focus:border-blue-200; + min-height: 100px; + :global{ + .ant-upload-wrapper{ + display: block; + border: none; + padding: 0; + } + .ant-upload{ + display: block; + } + } +} + +.uploadImage { + @apply flex justify-center items-center cursor-pointer; + img { + display: block; + max-width: 100%; + max-height: 200px; + } +} + +.text { + @apply border border-blue-200 overflow-hidden flex-1 rounded focus:border-blue-200; +} + +.textarea { + @apply border-0 +} \ No newline at end of file diff --git a/src/components/article/block.tsx b/src/components/article/block.tsx new file mode 100644 index 0000000..032d316 --- /dev/null +++ b/src/components/article/block.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import clsx from "clsx"; + +import {IconAdd, IconAddImage, IconAddText, IconDelete} from "@/components/icons"; +import {BlockImage, BlockText} from "./item.tsx"; + +import styles from './article.module.scss' +import {Button, Popconfirm} from "antd"; + +type Props = { + children?: React.ReactNode; + className?: string; + blocks: BlockContent[]; + editable?: boolean; + onChange?: (blocks: BlockContent[]) => void; + onRemove?: () => void; + onAdd?: () => void; +} + +export default function ArticleBlock({className, blocks, editable, onRemove, onAdd, onChange}: Props) { + const handleBlockRemove = (index: number) => { + // 删除当前项 + onChange?.(blocks.filter((_, idx) => index !== idx)) + } + // 新增 + const handleAddBlock = (type: 'text' | 'image', insertIndex: number = -1) => { + const newBlock: BlockContent = type === 'text' ? {type: 'text', content: ''} : {type: 'image', content: ''}; + const _blocks = [...blocks] + if (insertIndex == -1 || insertIndex >= blocks.length) { // -1或者越界表示新增 + _blocks.push(newBlock) + } else { + _blocks.splice(insertIndex, 0, newBlock) + } + onChange?.(_blocks) + } + + const handleBlockChange = (index: number, block: BlockContent) => { + const _blocks = [...blocks] + _blocks[index] = block + onChange?.(_blocks) + } + + return
+
+
+ {blocks.map((it, idx) => ( +
+ { + it.type === 'text' + ? handleBlockChange(idx, block)} data={it} + editable={editable}/> + : + } + {editable &&
+ + 请确认删除此{it.type === 'text' ? '文本' : '图片'}? +
} + onConfirm={() => handleBlockRemove(idx)} + okText="删除" + cancelText="取消" + > + + + + +
+ handleAddBlock('text', idx + 1)} + className="article-action-icon" title="新增文本"> + handleAddBlock('image', idx + 1)} + className="article-action-icon mt-1" title="新增图片"> +
+
} +
+ ))} + {editable && blocks.length == 0 && +
+
+ + +
+
+ } +
+ +
+ {editable &&
+ + 请确认删除此删除此分组? +
} + onConfirm={onRemove} + okText="删除" + cancelText="取消" + > + + + + } + +} \ No newline at end of file diff --git a/src/components/article/group.tsx b/src/components/article/group.tsx new file mode 100644 index 0000000..bef3b63 --- /dev/null +++ b/src/components/article/group.tsx @@ -0,0 +1,50 @@ + +import {message} from "antd" +import ArticleBlock from "@/components/article/block.tsx"; + +import styles from './article.module.scss' + +type Props = { + groups: ArticleContentGroup[]; + editable?: boolean; + onChange?: (groups: ArticleContentGroup[]) => void; +} +export default function ArticleGroup({groups, editable, onChange}: Props) { + /** + * 添加一个组 + * @param insertIndex 插入的位置,-1表示插入到末尾 + */ + const handleAddGroup = ( insertIndex: number = -1) => { + const newGroup: ArticleContentGroup = {blocks: []} + const _groups = [...groups] + if (insertIndex == -1 || insertIndex >= groups.length) { // -1或者越界表示新增 + _groups.push(newGroup) + } else { + _groups.splice(insertIndex, 0, newGroup) + } + onChange?.(_groups) + } + return
+ {groups.map((g, index) => ( + { + groups[index].blocks = blocks + onChange?.([...groups]) + }} + onAdd={() => { + handleAddGroup?.(index + 1) + }} + onRemove={async () => { + if (groups.length == 1) { + message.warning('至少保留一个内容块') + return; + } + onChange?.(groups.filter((_, idx) => index !== idx)) + }} + /> + ))} + {groups.length == 0 && editable && + onChange?.([{blocks}])} blocks={[]}/>} +
+} \ No newline at end of file diff --git a/src/components/article/item.tsx b/src/components/article/item.tsx new file mode 100644 index 0000000..02e315d --- /dev/null +++ b/src/components/article/item.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import styles from './article.module.scss' +import {Input, Upload} from "antd"; + +type Props = { + children?: React.ReactNode; + className?: string; + data: BlockContent; + editable?: boolean; + onChange?: (data: BlockContent) => void; +} + +export function BlockImage({data,editable}: Props) { + return
+ {editable ?
+ +
+ +
+
+
:
} +
+} + +export function BlockText({data,editable,onChange}: Props) { + return
+ {editable ?
+ {/*