feat:右键,虚拟列表导致拖拽被阻挡,使用enum
This commit is contained in:
parent
135bf4ff41
commit
55e5468d47
|
@ -2,27 +2,40 @@
|
||||||
* A layout is UI that is shared between multiple routes. On navigation, layouts preserve state, remain interactive, and do not re-render.
|
* A layout is UI that is shared between multiple routes. On navigation, layouts preserve state, remain interactive, and do not re-render.
|
||||||
*/
|
*/
|
||||||
'use client'
|
'use client'
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useContext, useEffect, useState} from 'react';
|
||||||
import {DragDropContext, DropResult} from 'react-beautiful-dnd';
|
import {DragDropContext, DropResult} from 'react-beautiful-dnd';
|
||||||
import {DroppableTable} from "@/ui/task/drag/DroppableTable";
|
import {DroppableTable} from "@/ui/task/drag/DroppableTable";
|
||||||
import {DataType, Request} from "@/lib/definitions";
|
import {DataType, Request} from "@/lib/definitions";
|
||||||
import {TaskSelectVO} from "@/lib/task/drag/data";
|
import {TaskSelectVO} from "@/lib/task/drag/data";
|
||||||
import {selectTaskAPI} from "@/lib/task/drag/service";
|
import {selectTaskAPI} from "@/lib/task/drag/service";
|
||||||
|
import LocalContext from "@/ui/LocalContent";
|
||||||
|
import {useSearchParams} from "next/dist/client/components/navigation";
|
||||||
|
|
||||||
export default function Layout({children}: { children: React.ReactNode }) {
|
export default function Layout({children}: { children: React.ReactNode }) {
|
||||||
const [allTaskList, setAllTaskList] = useState<DataType[]>([]);
|
const [allTaskList, setAllTaskList] = useState<DataType[]>([]);
|
||||||
|
|
||||||
|
const data = useContext(LocalContext);
|
||||||
|
console.log('data',data);
|
||||||
|
let pid = useSearchParams().get('pid');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addData()
|
addData()
|
||||||
}, [])
|
}, [data])
|
||||||
|
|
||||||
async function addData() {
|
function addData() {
|
||||||
const requestParam: Request<TaskSelectVO> = {
|
const requestParam: Request<TaskSelectVO> = {
|
||||||
pageSize: 1000,
|
pageSize: 1000,
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
data: {state: '9'}
|
data: {state: data.taskState}
|
||||||
}
|
}
|
||||||
const res = await selectTaskAPI(requestParam)
|
if (data.expectedStartTime.length>0){
|
||||||
|
const parse = JSON.parse(data.expectedStartTime);
|
||||||
|
requestParam.data.expectedStartTimeStart=parse[0].value;
|
||||||
|
requestParam.data.expectedStartTimeEnd=parse[1].value;
|
||||||
|
}
|
||||||
|
selectTaskAPI(requestParam).then(res=>{
|
||||||
setAllTaskList(res.data.content)
|
setAllTaskList(res.data.content)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理拖拽结束事件
|
// 处理拖拽结束事件
|
||||||
|
@ -51,6 +64,5 @@ export default function Layout({children}: { children: React.ReactNode }) {
|
||||||
{/* 不紧急不重要 */}
|
{/* 不紧急不重要 */}
|
||||||
<DroppableTable tableCode='0' taskList={allTaskList.filter(task => task.priority == '0')}/>
|
<DroppableTable tableCode='0' taskList={allTaskList.filter(task => task.priority == '0')}/>
|
||||||
</DragDropContext></div>
|
</DragDropContext></div>
|
||||||
)
|
);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,6 @@ export type TaskSelectVO = {
|
||||||
state?: string;
|
state?: string;
|
||||||
priority?: string;
|
priority?: string;
|
||||||
allOverdueTasks?:boolean;
|
allOverdueTasks?:boolean;
|
||||||
|
expectedStartTimeStart?:string;
|
||||||
|
expectedStartTimeEnd?:string;
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ export async function selectTaskAPI(requestParam: Request<TaskSelectVO>):
|
||||||
noStore();
|
noStore();
|
||||||
// 使用 Axios 发送 PUT 请求获取数据
|
// 使用 Axios 发送 PUT 请求获取数据
|
||||||
const response: AxiosResponse<ResponseVO<ResultPage<DataType>>> = await httpReq.post(
|
const response: AxiosResponse<ResponseVO<ResultPage<DataType>>> = await httpReq.post(
|
||||||
process.env.NEXT_PUBLIC_TODO_REQUEST_URL + '/task/select', requestParam);
|
process.env.NEXT_PUBLIC_TODO_REQUEST_URL + '/V2/task/select', requestParam);
|
||||||
// 从响应中提取数据并返回
|
// 从响应中提取数据并返回
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
|
@ -116,7 +116,7 @@ export const taskPriorityList: DictType[] = [
|
||||||
code: '1',
|
code: '1',
|
||||||
name: '不重要紧急',
|
name: '不重要紧急',
|
||||||
order: 2,
|
order: 2,
|
||||||
color: '#fafafa'
|
color: '#1677ff'
|
||||||
}, {
|
}, {
|
||||||
id: 0,
|
id: 0,
|
||||||
code: '0',
|
code: '0',
|
||||||
|
@ -216,5 +216,6 @@ export enum OPERATION_BUTTON_TYPE {
|
||||||
SHOW_FOUR,
|
SHOW_FOUR,
|
||||||
SHOW_CALENDAR,
|
SHOW_CALENDAR,
|
||||||
ADD,
|
ADD,
|
||||||
UPDATE_PRIORITY
|
UPDATE_PRIORITY,
|
||||||
|
INIT
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
import React, {Fragment, useState} from 'react';
|
||||||
|
import {Form, MenuProps, message, Popconfirm} from 'antd';
|
||||||
|
import { Dropdown, theme } from 'antd';
|
||||||
|
import {commonUpdate, deleteTask, OPERATION_BUTTON_TYPE} from "@/lib/task/project/data";
|
||||||
|
import {QuestionCircleOutlined} from "@ant-design/icons";
|
||||||
|
import Link from "next/link";
|
||||||
|
import {DetailModelForm} from "@/ui/task/project/DetailModelForm";
|
||||||
|
interface OperationButtonProps {
|
||||||
|
itemId: string,
|
||||||
|
priority?:string,
|
||||||
|
pid: string,
|
||||||
|
pPid: string,
|
||||||
|
operationId?: OPERATION_BUTTON_TYPE,
|
||||||
|
refreshDate?: () => void,
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
const RightOption: React.FC<OperationButtonProps> = (props) => {
|
||||||
|
const [operateState, setOperateState] = useState({
|
||||||
|
operationId:OPERATION_BUTTON_TYPE.INIT,openModal:false});
|
||||||
|
const items: MenuProps['items'] = [
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.DETAIL,
|
||||||
|
label: <a onClick={(e) => {
|
||||||
|
setOperateState({openModal: true, operationId: OPERATION_BUTTON_TYPE.DETAIL})
|
||||||
|
}}>任务详情</a>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.ADD_CHILD,
|
||||||
|
label: <a onClick={(e) => {
|
||||||
|
setOperateState({openModal: true, operationId: OPERATION_BUTTON_TYPE.ADD_CHILD})
|
||||||
|
}}>添加支线任务</a>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.UPDATE,
|
||||||
|
label: <a onClick={(e) => {
|
||||||
|
setOperateState({openModal: true, operationId: OPERATION_BUTTON_TYPE.UPDATE})
|
||||||
|
}}>修改任务</a>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.DELETE,
|
||||||
|
label: <Popconfirm
|
||||||
|
title="删除任务"
|
||||||
|
description="确认要删除任务?"
|
||||||
|
icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
|
||||||
|
okText="确认"
|
||||||
|
cancelText="取消"
|
||||||
|
onConfirm={() => {
|
||||||
|
deleteTask(props.itemId).then((response => {
|
||||||
|
console.log('response', response)
|
||||||
|
if (response.status.success) {
|
||||||
|
message.success("删除任务成功:" + response.data)
|
||||||
|
props.refreshDate?.()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
><a>删除任务</a></Popconfirm>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.COMPLETE,
|
||||||
|
label: <Popconfirm
|
||||||
|
title="完成任务"
|
||||||
|
description="确认要完成任务?"
|
||||||
|
okText="确认"
|
||||||
|
cancelText="取消"
|
||||||
|
onConfirm={() => {
|
||||||
|
commonUpdate({
|
||||||
|
updateColumnList:[{
|
||||||
|
name:'state',
|
||||||
|
code:'state',
|
||||||
|
value:'7'
|
||||||
|
}],
|
||||||
|
conditionColumnList:[{
|
||||||
|
name:'id',
|
||||||
|
code:'id',
|
||||||
|
operateType:'=',
|
||||||
|
value:props.itemId
|
||||||
|
}]
|
||||||
|
}).then((response => {
|
||||||
|
console.log('response', response)
|
||||||
|
if (response.status.success) {
|
||||||
|
message.success("完成任务成功")
|
||||||
|
props.refreshDate?.()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
><a>完成任务</a></Popconfirm>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.SHOW_TREE,
|
||||||
|
label: <Link href={"/task/project?pid=" + props.itemId}>树任务显示子任务</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.SHOW_FOUR,
|
||||||
|
label: <Link href={"/task/drag?pid=" + props.itemId}>四象限显示子任务</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: OPERATION_BUTTON_TYPE.SHOW_CALENDAR,
|
||||||
|
label: <Link href={"/task/calendar?pid=" + props.itemId}>日历显示子任务</Link>,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// 获取系统样式
|
||||||
|
const {
|
||||||
|
token: { colorBgLayout, colorTextTertiary },
|
||||||
|
} = theme.useToken();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOperateState({...operateState, openModal: false})
|
||||||
|
if (operateState.operationId !== OPERATION_BUTTON_TYPE.DETAIL) {
|
||||||
|
props.refreshDate?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onClick: MenuProps['onClick'] = ({key}) => {
|
||||||
|
console.log(key)
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Dropdown menu={{ items }} trigger={['contextMenu']}>
|
||||||
|
{props.children}
|
||||||
|
</Dropdown>
|
||||||
|
{operateState.openModal&&<DetailModelForm
|
||||||
|
haveButton={false}
|
||||||
|
itemId={operateState.operationId === OPERATION_BUTTON_TYPE.UPDATE||operateState.operationId === OPERATION_BUTTON_TYPE.DETAIL?props.itemId:undefined}
|
||||||
|
pPid={props.pPid}
|
||||||
|
pid={operateState.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD ?props.itemId:undefined}
|
||||||
|
operationId={operateState.operationId}
|
||||||
|
description={operateState.operationId === OPERATION_BUTTON_TYPE.DETAIL ? '任务详情' :
|
||||||
|
operateState.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD ? '添加支线任务' :
|
||||||
|
operateState.operationId === OPERATION_BUTTON_TYPE.UPDATE ? '修改任务' : '未知操作'}
|
||||||
|
open={operateState.openModal}
|
||||||
|
reloadData={handleCancel}/>}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RightOption;
|
|
@ -7,9 +7,8 @@ import {DataType} from "@/lib/definitions";
|
||||||
import './index.modules.css'
|
import './index.modules.css'
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {getTaskState, taskPriorityList} from "@/lib/task/project/data";
|
import {getTaskState, taskPriorityList} from "@/lib/task/project/data";
|
||||||
import {List as VirtualizedList, AutoSizer} from "react-virtualized";
|
|
||||||
import 'react-virtualized/styles.css';
|
import 'react-virtualized/styles.css';
|
||||||
import OperationButton from "@/ui/task/OperationButton";
|
import RightOption from "@/ui/task/RightOption";
|
||||||
|
|
||||||
interface DroppableTableProps {
|
interface DroppableTableProps {
|
||||||
tableCode: string,
|
tableCode: string,
|
||||||
|
@ -35,13 +34,14 @@ export const DroppableTable = React.memo((props: DroppableTableProps) => {
|
||||||
const getItemStyle = React.useCallback((isDragging: boolean, draggableStyle: any): CSSProperties => ({
|
const getItemStyle = React.useCallback((isDragging: boolean, draggableStyle: any): CSSProperties => ({
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
background: isDragging ? "lightgreen" : "white",
|
background: isDragging ? "lightgreen" : "white",
|
||||||
position: 'relative', // 确保 zIndex 生效
|
position: 'relative',
|
||||||
zIndex: isDragging ? 2147483647 : 'auto', // 使用 'auto' 代替 0 可能更好
|
zIndex: isDragging ? 2147483647 : 'auto',
|
||||||
...draggableStyle,
|
...draggableStyle,
|
||||||
borderBottom: '1px solid #f0f0f0',
|
borderBottom: '1px solid #f0f0f0',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
// verticalAlign: 'middle',
|
verticalAlign: 'middle',
|
||||||
// textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
borderColor:taskPriorityList.find((item) => item.code === props.tableCode)?.color
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
const getListStyle = React.useCallback((isDraggingOver: boolean) => ({
|
const getListStyle = React.useCallback((isDraggingOver: boolean) => ({
|
||||||
|
@ -50,55 +50,10 @@ export const DroppableTable = React.memo((props: DroppableTableProps) => {
|
||||||
width: '50vw'
|
width: '50vw'
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
const rowRenderer = React.useCallback(({index, key, style}: {
|
|
||||||
index: number;
|
|
||||||
key: string;
|
|
||||||
style: React.CSSProperties
|
|
||||||
}) => {
|
|
||||||
const record = props.taskList[index];
|
|
||||||
if (!record) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Draggable key={record.id} draggableId={record.id} index={index}>
|
|
||||||
{(provided, snapshot) => (
|
|
||||||
<div
|
|
||||||
ref={provided.innerRef}
|
|
||||||
{...provided.draggableProps}
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
style={getItemStyle(snapshot.isDragging, {...provided.draggableProps.style, ...style})}
|
|
||||||
className="virtualized-row displayFlexRow"
|
|
||||||
>
|
|
||||||
<div style={{width: '15%'}} className='displayFlexRow'>
|
|
||||||
<Tooltip placement="topLeft" title={record.name} className='displayFlexRow'>
|
|
||||||
<div className='displayFlexRow'>{record.name}</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div style={{width: '40%' , boxSizing: 'border-box', minWidth: 0}} className='displayFlexRow'>
|
|
||||||
<Tooltip placement="topLeft" title={record.description}>
|
|
||||||
<div className='displayFlexRow'>{record.description}</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
width: '10%',
|
|
||||||
}} className='displayFlexRow'>{getTaskState(record.state) ? getTaskState(record.state).name : ""}</div>
|
|
||||||
<div style={{width: '25%'}} className='displayFlexColumn'>
|
|
||||||
<div>起: {record.expectedStartTime ? dayjs(record.expectedStartTime).format("YYYY-MM-DD HH:mm") : ""}</div>
|
|
||||||
<div>止: {record.expectedStartTime ? dayjs(record.expectedStartTime).format("YYYY-MM-DD HH:mm") : ""}</div>
|
|
||||||
</div>
|
|
||||||
<div style={{width: '10%'}} className='displayFlexRow'>
|
|
||||||
<OperationButton itemId={record.id} priority={record.priority} pid={record.pid}
|
|
||||||
pPid={record.pPid}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
);
|
|
||||||
}, [props.taskList, getItemStyle]);
|
|
||||||
|
|
||||||
const headerStyle = React.useMemo(() => ({
|
const headerStyle = React.useMemo(() => ({
|
||||||
backgroundColor: taskPriorityList.find((item) => item.code === props.tableCode)?.color,
|
backgroundColor: taskPriorityList.find((item) => item.code === props.tableCode)?.color,
|
||||||
height: '55px',
|
height: '55px',
|
||||||
borderBottom: '1px solid #f0f0f0',
|
// borderBottom: '1px solid #f0f0f0',
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
}), [props.tableCode]);
|
}), [props.tableCode]);
|
||||||
|
|
||||||
|
@ -109,32 +64,54 @@ export const DroppableTable = React.memo((props: DroppableTableProps) => {
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.droppableProps}
|
{...provided.droppableProps}
|
||||||
style={getListStyle(snapshot.isDraggingOver)}
|
style={getListStyle(snapshot.isDraggingOver)}
|
||||||
className="droppable-table"
|
className="droppable-table scrollHidden"
|
||||||
>
|
>
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
{/* 表头 */}
|
{/* 表头 */}
|
||||||
<div style={headerStyle} className='displayFlexRow'>
|
<div style={headerStyle} className='displayFlexRow'>
|
||||||
<div style={{width: '15%'}} className='displayFlexRow'>{stateName}</div>
|
<div style={{width: '20%'}} className='displayFlexRow'>{stateName}</div>
|
||||||
<div style={{width: '40%'}} className='displayFlexRow'>任务描述</div>
|
<div style={{width: '45%'}} className='displayFlexRow'>任务描述</div>
|
||||||
<div style={{width: '15%'}} className='displayFlexRow'>任务状态</div>
|
<div style={{width: '15%'}} className='displayFlexRow'>任务状态</div>
|
||||||
<div style={{width: '20%'}} className='displayFlexRow'>期望时间</div>
|
<div style={{width: '20%'}} className='displayFlexRow'>期望时间</div>
|
||||||
<div style={{width: '10%'}} className='displayFlexRow'>操作</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/* 虚拟列表主体 */}
|
{/* 虚拟列表主体 */}
|
||||||
<div style={{height: 'calc(50vh - 76px)'}}>
|
<div style={{height: 'calc(50vh - 76px)',overflow: 'auto'}}>
|
||||||
<AutoSizer>
|
{props.taskList.map((record, index) => {
|
||||||
{({height, width}) => (
|
return <Draggable key={record.id} draggableId={record.id} index={index}>
|
||||||
<VirtualizedList
|
{(provided, snapshot) => (
|
||||||
className={"scrollHidden"}
|
<RightOption itemId={record.id} pid={record.pid} pPid={record.pPid} children={<div
|
||||||
height={height}
|
ref={provided.innerRef}
|
||||||
width={width}
|
{...provided.draggableProps}
|
||||||
rowCount={props.taskList.length}
|
{...provided.dragHandleProps}
|
||||||
rowHeight={80} // 与你的行高匹配
|
style={getItemStyle(snapshot.isDragging, {...provided.draggableProps.style})}
|
||||||
rowRenderer={rowRenderer}
|
className="virtualized-row displayFlexRow"
|
||||||
overscanRowCount={8} // 预渲染的行数
|
>
|
||||||
/>
|
<div style={{width: '20%'}} className='displayFlexRow'>
|
||||||
|
<Tooltip placement="topLeft" title={record.name}
|
||||||
|
className='displayFlexRow'>
|
||||||
|
<div className='displayFlexRow'>{record.name}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div style={{width: '45%', boxSizing: 'border-box', minWidth: 0}}
|
||||||
|
className='displayFlexRow'>
|
||||||
|
<Tooltip placement="topLeft" title={record.description}>
|
||||||
|
<div className='displayFlexRow'>{record.description}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
width: '10%',
|
||||||
|
}}
|
||||||
|
className='displayFlexRow'>{getTaskState(record.state) ? getTaskState(record.state).name : ""}</div>
|
||||||
|
<div style={{width: '25%'}} className='displayFlexColumn'>
|
||||||
|
<div>起: {record.expectedStartTime ? dayjs(record.expectedStartTime).format("YYYY-MM-DD HH:mm") : ""}</div>
|
||||||
|
<div>止: {record.expectedEndTime ? dayjs(record.expectedEndTime).format("YYYY-MM-DD HH:mm") : ""}</div>
|
||||||
|
</div>
|
||||||
|
</div>}>
|
||||||
|
|
||||||
|
</RightOption>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</Draggable>
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid #f0f0f0;
|
border: 1px solid #f0f0f0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtualized-row {
|
.virtualized-row {
|
||||||
|
|
|
@ -25,7 +25,7 @@ export type DetailModelFormProps={
|
||||||
// 祖宗任务id
|
// 祖宗任务id
|
||||||
pPid?:string,
|
pPid?:string,
|
||||||
// 操作id
|
// 操作id
|
||||||
operationId: number,
|
operationId: OPERATION_BUTTON_TYPE,
|
||||||
// 标题描述
|
// 标题描述
|
||||||
description:string,
|
description:string,
|
||||||
// 是否打开界面,用于非按钮操作
|
// 是否打开界面,用于非按钮操作
|
||||||
|
|
Loading…
Reference in New Issue