diff --git a/package-lock.json b/package-lock.json index f233081..31615e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,8 @@ "react-dom": "^18", "react-virtualized": "^9.22.6", "sass": "^1.77.3", - "tailwindcss": "3.3.3" + "tailwindcss": "3.3.3", + "uuid": "^11.1.0" }, "devDependencies": { "@types/js-cookie": "^3.0.6", @@ -33,6 +34,7 @@ "@types/react-big-calendar": "^1.8.9", "@types/react-dom": "^18", "@types/react-virtualized": "^9.22.2", + "@types/uuid": "^10.0.0", "@vercel/style-guide": "^5.0.1", "eslint": "^8", "eslint-config-next": "14.2.29", @@ -1670,6 +1672,12 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, "node_modules/@types/warning": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/@types/warning/-/warning-3.0.3.tgz", @@ -7534,6 +7542,18 @@ "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -8969,6 +8989,12 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, "@types/warning": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/@types/warning/-/warning-3.0.3.tgz", @@ -13420,6 +13446,11 @@ "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 357f44d..f518bfc 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "react-dom": "^18", "react-virtualized": "^9.22.6", "sass": "^1.77.3", - "tailwindcss": "3.3.3" + "tailwindcss": "3.3.3", + "uuid": "^11.1.0" }, "devDependencies": { "@types/js-cookie": "^3.0.6", @@ -34,6 +35,7 @@ "@types/react-big-calendar": "^1.8.9", "@types/react-dom": "^18", "@types/react-virtualized": "^9.22.2", + "@types/uuid": "^10.0.0", "@vercel/style-guide": "^5.0.1", "eslint": "^8", "eslint-config-next": "14.2.29", diff --git a/public/static/pc-Web.png b/public/static/pc-Web.png new file mode 100644 index 0000000..c526d4b Binary files /dev/null and b/public/static/pc-Web.png differ diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 52d6a8a..f4f7360 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,15 +1,14 @@ 'use client' import {ProConfigProvider} from "@ant-design/pro-components"; import React, {lazy, Suspense} from "react"; -import Page from "@/ui/login/Page"; import Loading from "@/app/loading"; -// const Page = lazy(() => import('@/ui/login/Page')); +import XcxLoginPage from "@/ui/login/XcxLoginPage"; export default function Login() { return ( }> - + ); diff --git a/src/components/DiaryOption.tsx b/src/components/DiaryOption.tsx new file mode 100644 index 0000000..916f561 --- /dev/null +++ b/src/components/DiaryOption.tsx @@ -0,0 +1,100 @@ +import React, {useEffect, useState} from 'react'; +import { Avatar, List, message } from 'antd'; +import VirtualList from 'rc-virtual-list'; +import { Button, Drawer } from 'antd'; +interface UserItem { + email: string; + gender: string; + name: string; + avatar: string; +} + +const CONTAINER_HEIGHT = 400; +const PAGE_SIZE = 20; + +const DiaryOption = () => { + // 抽屉 start + const [open, setOpen] = useState(false); + const showDrawer = () => { + setOpen(true); + }; + const onClose = () => { + setOpen(false); + }; + // 抽屉 end + + // 数据 start + const [data, setData] = useState([]); + const [page, setPage] = useState(1); + + const appendData = (showMessage = true) => { + const fakeDataUrl = `https://660d2bd96ddfa2943b33731c.mockapi.io/api/users/?page=${page}&limit=${PAGE_SIZE}`; + fetch(fakeDataUrl) + .then((res) => res.json()) + .then((body) => { + const results = Array.isArray(body) ? body : []; + setData(data.concat(results)); + setPage(page + 1); + showMessage && message.success(`${results.length} more items loaded!`); + }); + }; + + useEffect(() => { + appendData(false); + }, []); + + const onScroll = (e: React.UIEvent) => { + // Refer to: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#problems_and_solutions + if ( + Math.abs(e.currentTarget.scrollHeight - e.currentTarget.scrollTop - CONTAINER_HEIGHT) <= 1 + ) { + appendData(); + } + }; + + // 数据 end + + return ( + <> + + +
+ + + +
+
+ + + {item => ( + + } + title={{item.name}} + description={item.email} + /> +
Content
+
+ )} +
+
+
+
+ + ); +}; +export default DiaryOption; \ No newline at end of file diff --git a/src/lib/copyToClipboard.ts b/src/lib/copyToClipboard.ts new file mode 100644 index 0000000..94e7ee9 --- /dev/null +++ b/src/lib/copyToClipboard.ts @@ -0,0 +1,55 @@ +/** + * 复制文本到剪贴板 + * // 使用示例 + * copyToClipboard('要复制的文本') + * .then(() => console.log('复制成功')) + * .catch(() => console.error('复制失败')); + * @param text 要复制的文本 + * @returns 返回 Promise,成功时 resolve(true),失败时 reject(false) + */ +export const copyToClipboard = (text: string): Promise => { + return new Promise((resolve, reject) => { + // 方案1: 使用现代 Clipboard API + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text) + .then(() => resolve(true)) + .catch(() => { + // 现代 API 失败时回退到传统方法 + fallbackCopy(text) ? resolve(true) : reject(false); + }); + } + // 方案2: 传统 document.execCommand 方法 + else { + fallbackCopy(text) ? resolve(true) : reject(false); + } + }); +}; + +// 传统复制方法兼容方案 +const fallbackCopy = (text: string): boolean => { + try { + // 创建临时文本域 + const textArea = document.createElement('textarea'); + textArea.value = text; + + // 隐藏文本域(不在视口中显示) + textArea.style.position = 'fixed'; + textArea.style.top = '-9999px'; + textArea.style.left = '-9999px'; + textArea.style.opacity = '0'; + + document.body.appendChild(textArea); + + // 选择并复制 + textArea.select(); + const successful = document.execCommand('copy'); + + // 清理DOM + document.body.removeChild(textArea); + + return successful; + } catch (err) { + console.error('复制失败:', err); + return false; + } +}; \ No newline at end of file diff --git a/src/lib/login/service.ts b/src/lib/login/service.ts new file mode 100644 index 0000000..8785db3 --- /dev/null +++ b/src/lib/login/service.ts @@ -0,0 +1,6 @@ +import {httpReq} from "@/utils/axiosReq"; + +export const generateQrcodeAPI = (data:{}) => { + return httpReq.post(process.env.NEXT_PUBLIC_SECURITY_REQUEST_URL + "/V2/wx/login/generate/qrcode", + data) +} \ No newline at end of file diff --git a/src/ui/login/XcxLoginPage.tsx b/src/ui/login/XcxLoginPage.tsx new file mode 100644 index 0000000..723e7b3 --- /dev/null +++ b/src/ui/login/XcxLoginPage.tsx @@ -0,0 +1,111 @@ +'use client' +import { + LoginFormPage, +} from '@ant-design/pro-components'; + +import {message, theme, Modal, Button, Skeleton, Image, QRCode} from 'antd'; +import React, {useLayoutEffect} from 'react'; +import {useState} from 'react'; +import {CaptchaLoginSuccess, LoginObject} from "@/lib/login/definitions"; +import {useRouter} from 'next/navigation' +import { v4 as uuidv4 } from 'uuid'; +import {generateQrcodeAPI} from "@/lib/login/service"; + +export default function XcxLoginPage() { + const [loaded, setLoaded] = useState(false); + useLayoutEffect(() => { + setLoaded(true); + }) + // 二维码 start + const [qrCodeShow, setQrCodeShow] = useState(false); + const [qrCodeStatus, setQrCodeStatus] = useState<'active' | 'expired' | 'loading' | 'scanned'>(); + const [qrCodeValue, setQrCodeValue] = useState("-"); + const generateQrcode = () => { + // 不在二维码页面直接返回 + if (!qrCodeShow) { + return; + } + if (qrCodeStatus=='loading') { + message.info({content:"请耐心等待"}) + }else if (qrCodeStatus=='expired') { + generateQrCode() + }else if (qrCodeStatus=='active') { + + }else if (qrCodeStatus=='scanned') { + + }else { + generateQrCode() + } + } + function generateQrCode(){ + // 生成唯一id + const clientId: string = uuidv4(); + generateQrcodeAPI({clientId}).then(res=>{ + setQrCodeValue(JSON.stringify({ + clientId,serverId:res.data.data + })) + }) + } + // 二维码 end + + const {token} = theme.useToken(); + const router = useRouter() + const [messageApi, contextHolder] = message.useMessage(); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [captchaLoginSuccessList, setCaptchaLoginSuccessList] = useState([]); + const captchaUserNameConfirm = (captchaLoginSuccess: CaptchaLoginSuccess) => { + messageApi.open({ + type: 'info', + content: "使用帐号" + captchaLoginSuccess.username + "登录成功" + }) + localStorage.setItem('platform-security', captchaLoginSuccess.token) + router.push('/task/project') + setOpen(false) + setLoading(false) + } + return ( +
+ {contextHolder} + ( + + )) + } + > + + {loaded ? { + // 二维码和小程序扫码来回切换 + setQrCodeShow(!qrCodeShow) + // 生成二维码 + generateQrcode() + }} + submitter={{ searchConfig: { submitText: qrCodeShow?"在我的-PC扫码登录或者返回":"已打开微信小程序,生成登录码。",resetText: '重置2'}}} + > +
+ { + qrCodeShow? console.log('refresh')} />: + + } +
+
: } +
+ ); +}; \ No newline at end of file diff --git a/src/ui/task/drag/DroppableTable.tsx b/src/ui/task/drag/DroppableTable.tsx index 991b5bc..b22caa0 100644 --- a/src/ui/task/drag/DroppableTable.tsx +++ b/src/ui/task/drag/DroppableTable.tsx @@ -9,6 +9,7 @@ import dayjs from "dayjs"; import {getTaskState, taskPriorityList} from "@/lib/task/project/data"; import 'react-virtualized/styles.css'; import RightOption from "@/ui/task/RightOption"; +import {CopyOutlined} from "@ant-design/icons"; interface DroppableTableProps { tableCode: string, @@ -70,7 +71,7 @@ export const DroppableTable = React.memo((props: DroppableTableProps) => { {/* 表头 */}
-
{stateName}
+
{stateName}
任务描述
任务状态
期望时间
@@ -89,7 +90,7 @@ export const DroppableTable = React.memo((props: DroppableTableProps) => { style={getItemStyle(snapshot.isDragging, {...provided.draggableProps.style})} className="virtualized-row displayFlexRow" > -
+
{record.name}
diff --git a/src/ui/task/project/DetailModelForm.tsx b/src/ui/task/project/DetailModelForm.tsx index b05b88d..d281e6d 100644 --- a/src/ui/task/project/DetailModelForm.tsx +++ b/src/ui/task/project/DetailModelForm.tsx @@ -17,37 +17,38 @@ import { } from "@/lib/task/project/data"; import {DataType} from "@/lib/definitions"; import dayjs, {Dayjs} from "dayjs"; +import DiaryOption from "@/components/DiaryOption"; -export type DetailModelFormProps={ +export type DetailModelFormProps = { // 当前内容id itemId?: string, - pid?:string, + pid?: string, // 祖宗任务id - pPid?:string, + pPid?: string, // 操作id operationId: OPERATION_BUTTON_TYPE, // 标题描述 - description:string, + description: string, // 是否打开界面,用于非按钮操作 - open:boolean, + open: boolean, // 使用按钮操作 - haveButton:boolean, - expectedStartTime?:Dayjs, - expectedEndTime?:Dayjs, + haveButton: boolean, + expectedStartTime?: Dayjs, + expectedEndTime?: Dayjs, // 重新加载数据 reloadData?: () => void } -export type PidSelectTree= { label: string; value: string;pid:string; children?: PidSelectTree[] } +export type PidSelectTree = { label: string; value: string; pid: string; children?: PidSelectTree[] } export const DetailModelForm: React.FC = (props) => { - console.log("DetailModelForm:props:",props) + console.log("DetailModelForm:props:", props) const [form] = Form.useForm(); - const [pid, setPid] = useState(props.pid?props.pid:'0'); + const [pid, setPid] = useState(props.pid ? props.pid : '0'); const [editFormDisable, setEditFormDisable] = useState(props.operationId === OPERATION_BUTTON_TYPE.DETAIL) // 团队第一层 pid必须为0 const [taskType, setTaskType] = useState('0') useEffect(() => { - if (props.itemId!=undefined&&( + if (props.itemId != undefined && ( props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE)) { getTask(props.itemId).then(task => { console.log('DetailModelForm:getTask(props.itemId)', props.itemId, task); @@ -60,38 +61,43 @@ export const DetailModelForm: React.FC = (props) => { task.data.expectedTimeRange = [task.data.expectedStartTime ? dayjs(task.data.expectedStartTime) : undefined, task.data.expectedEndTime ? dayjs(task.data.expectedEndTime) : undefined]; form.setFieldsValue(task.data) - console.log("form.setFieldsValue(task.data)"+JSON.stringify(task.data)) + console.log("form.setFieldsValue(task.data)" + JSON.stringify(task.data)) } else { message.error(task.status.message); props.reloadData?.() } }) - }else if(props.operationId === OPERATION_BUTTON_TYPE.ADD|| props.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD){ - let data={'expectedTimeRange':[props.expectedStartTime?props.expectedStartTime:dayjs(), props.expectedEndTime],'pid':props.pid}; + } else if (props.operationId === OPERATION_BUTTON_TYPE.ADD || props.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD) { + let data = { + 'expectedTimeRange': [props.expectedStartTime ? props.expectedStartTime : dayjs(), props.expectedEndTime], + 'pid': props.pid + }; form.setFieldsValue(data) } }, [props]) - function childReduce(child:DataType[]):PidSelectTree[]{ - const result:PidSelectTree[] = []; - child.map(data=> { - const resultData:PidSelectTree = {label:data.name,value:data.id,pid:data.pid}; - if (data.children){ - resultData.children=childReduce(data.children); + + function childReduce(child: DataType[]): PidSelectTree[] { + const result: PidSelectTree[] = []; + child.map(data => { + const resultData: PidSelectTree = {label: data.name, value: data.id, pid: data.pid}; + if (data.children) { + resultData.children = childReduce(data.children); } result.push(resultData); }) return result; } + // 如果不是添加任务需要回显 return ( title={props.description} - open={props.open&&!props.haveButton} - trigger={props.haveButton? + open={props.open && !props.haveButton} + trigger={props.haveButton ? :undefined + : undefined } form={form} autoFocusFirstInput @@ -102,10 +108,10 @@ export const DetailModelForm: React.FC = (props) => { props.reloadData?.(); }, }} - submitter={props.itemId!==undefined&&props.itemId!=='-1'?{ + submitter={props.itemId !== undefined && props.itemId !== '-1' ? { render: (prop, defaultDoms) => { return [ - editFormDisable?:undefined, - props.operationId === OPERATION_BUTTON_TYPE.DETAIL||props.operationId === OPERATION_BUTTON_TYPE.UPDATE?} - okText="确认" - cancelText="取消" - onConfirm={() => { - if (props.itemId!==undefined) { - deleteTask(props.itemId).then((response => { - console.log('response', response) - if (response.status.success) { - message.success("删除任务成功:" + response.data) - props.reloadData?.() - } - })); - } - }} - > - :undefined + : undefined, + props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE ? + } + okText="确认" + cancelText="取消" + onConfirm={() => { + if (props.itemId !== undefined) { + deleteTask(props.itemId).then((response => { + console.log('response', response) + if (response.status.success) { + message.success("删除任务成功:" + response.data) + props.reloadData?.() + } + })); + } + }} + > + + : undefined , + , ...defaultDoms ]; }, - }:undefined} + } : undefined} onFinish={async (values) => { console.log('Received values of form: ', values); - if (values.pid===undefined){ - values.pid='0' + if (values.pid === undefined) { + values.pid = '0' } - if (values.expectedTimeRange?.[0]!=undefined) { - values.expectedStartTime=dayjs(values.expectedTimeRange[0]).format() + if (values.expectedTimeRange?.[0] != undefined) { + values.expectedStartTime = dayjs(values.expectedTimeRange[0]).format() } - if (values.expectedTimeRange?.[1]!=undefined) { - values.expectedEndTime=dayjs(values.expectedTimeRange[1]).format() + if (values.expectedTimeRange?.[1] != undefined) { + values.expectedEndTime = dayjs(values.expectedTimeRange[1]).format() } - if (values.actualTimeRange?.[0]!=undefined) { - values.actualStartTime=dayjs(values.actualTimeRange[0]).toDate() + if (values.actualTimeRange?.[0] != undefined) { + values.actualStartTime = dayjs(values.actualTimeRange[0]).toDate() } - if (values.actualTimeRange?.[1]!=undefined) { - values.actualEndTime=dayjs(values.actualTimeRange[1]).toDate() + if (values.actualTimeRange?.[1] != undefined) { + values.actualEndTime = dayjs(values.actualTimeRange[1]).toDate() } - var result:boolean=false; + var result: boolean = false; let state = taskStateList.find(taskState => taskState.name === values.state?.toString()); if (state) { @@ -169,7 +178,7 @@ export const DetailModelForm: React.FC = (props) => { values.priority = priority.code } // todo 修改 - if (props.operationId === OPERATION_BUTTON_TYPE.UPDATE||(props.operationId === OPERATION_BUTTON_TYPE.DETAIL&&!editFormDisable)) { + if (props.operationId === OPERATION_BUTTON_TYPE.UPDATE || (props.operationId === OPERATION_BUTTON_TYPE.DETAIL && !editFormDisable)) { await updateTask(values).then(response => { console.log('response', response) if (response.status.success) { @@ -177,16 +186,16 @@ export const DetailModelForm: React.FC = (props) => { // 树任务重新刷新 // 四象限任务重新刷新 // 如果可以直接更新列表而不请求。。。。。。 - console.log('props.reloadData?.()',props.reloadData) + console.log('props.reloadData?.()', props.reloadData) props.reloadData?.() - result= true - }else { + result = true + } else { message.error(response.status.message) - result= false + result = false } } ); - }else { + } else { await addTask(values).then(response => { console.log('response', response) if (response.status.success) { @@ -194,12 +203,12 @@ export const DetailModelForm: React.FC = (props) => { // 树任务重新刷新 // 四象限任务重新刷新 // 如果可以直接更新列表而不请求。。。。。。 - console.log('props.reloadData?.()',props.reloadData) + console.log('props.reloadData?.()', props.reloadData) props.reloadData?.() - result= true - }else { + result = true + } else { message.error(response.status.message) - result= false + result = false } } ); @@ -207,9 +216,9 @@ export const DetailModelForm: React.FC = (props) => { return result; }} > -