@@ -0,0 +1,22 @@ | |||
# 文件图标 | |||
```jsx | |||
import React from 'react'; | |||
import FileIcon from '@/components/FileIcon'; | |||
export default () => ( | |||
<> | |||
{/* 无法识别的的扩展名最终都会展示default */} | |||
<FileIcon extension="unknown_extenstion" /> | |||
{/* 等价扩展名: doc, docx */} | |||
<FileIcon extension="doc" /> | |||
{/* 等价扩展名: ppt, pptx */} | |||
<FileIcon extension="ppt" /> | |||
{/* 等价扩展名: xls, xlsx */} | |||
<FileIcon extension="xls" /> | |||
<FileIcon extension="zip" /> | |||
<FileIcon extension="folder" /> | |||
</> | |||
) | |||
``` |
@@ -0,0 +1,14 @@ | |||
```jsx | |||
import React from 'react'; | |||
import FileStatus from '@/components/FileStatus'; | |||
export default () => ( | |||
<> | |||
<FileStatus type="upload" loadingState="loading" progress={30} /> | |||
<FileStatus type="upload" loadingState="complete" result="success" /> | |||
<FileStatus type="upload" loadingState="complete" result="fail" /> | |||
<FileStatus type="download" loadingState="complete" result="success" /> | |||
<FileStatus type="download" loadingState="complete" result="fail" /> | |||
</> | |||
) | |||
``` |
@@ -0,0 +1,47 @@ | |||
import React from 'react'; | |||
import { Image, ImageProps } from 'antd'; | |||
import classNames from 'classnames'; | |||
import { memo } from 'react'; | |||
import styles from './fileIcon.less'; | |||
import { memoize } from 'lodash'; | |||
interface FileIconProps extends ImageProps { | |||
extension: string; | |||
} | |||
export default memo(function FileIcon(props: FileIconProps) { | |||
const { className, extension, ...restProps } = props; | |||
return ( | |||
<div className={classNames(styles.fileIcon, className)}> | |||
<Image | |||
preview={false} | |||
{...restProps} | |||
src={fileIconSrc(extension)} | |||
fallback={fileIconSrc('default')} | |||
/> | |||
</div> | |||
) | |||
}); | |||
const fileIconSrc = memoize((extension: string) => { | |||
const fixedExtension = matchFileImage(extension); | |||
return `/assets/file_icon/${fixedExtension}.svg`; | |||
}) | |||
export function matchFileImage(extension: string) { | |||
switch (extension) { | |||
case 'ppt': | |||
case 'pptx': | |||
return 'ppt'; | |||
case 'xls': | |||
case 'xlsx': | |||
return 'excel'; | |||
case 'doc': | |||
case 'docx': | |||
return 'word'; | |||
default: | |||
return extension; | |||
} | |||
} | |||
@@ -0,0 +1,4 @@ | |||
.fileIcon { | |||
display: inline-flex; | |||
align-items: center; | |||
} |
@@ -0,0 +1 @@ | |||
export { default } from './FileIcon'; |
@@ -0,0 +1,101 @@ | |||
.fileStatus { | |||
display: flex; | |||
align-items: center; | |||
flex-direction: row; | |||
background: #FFFFFF; | |||
box-shadow: 0px 1px 2px 0px rgba(102, 110, 115, 0.3); | |||
border-radius: 4px; | |||
height: 76px; | |||
padding: 12px; | |||
.left { | |||
width: 228px; | |||
flex: 0 0 228px; | |||
margin-right: 10px; | |||
} | |||
.mid { | |||
flex: 1; | |||
text-align: center; | |||
} | |||
.right { | |||
flex: 0 0 80px; | |||
margin-left: 10px; | |||
.button { | |||
color: @primary-color; | |||
font-size: 12px; | |||
} | |||
} | |||
} | |||
.left { | |||
height: 100%; | |||
.icon { width:36px; margin-right: 12px;} | |||
.content { | |||
display: inline-flex; | |||
width: calc(100% - 36px - 12px); | |||
height: 100%; | |||
vertical-align: top; | |||
flex-direction: column; | |||
justify-content: space-between; | |||
.fileName { | |||
flex: none; | |||
font-size: 14px; | |||
color: rgba(#000, 0.8); | |||
.textOverflow(); | |||
} | |||
.subContent { | |||
display: flex; | |||
flex-direction: row; | |||
font-size: 12px; | |||
color: rgba(#000, 0.6); | |||
.filePath { | |||
flex: 1; | |||
margin-right: 8px; | |||
.textOverflow(); | |||
} | |||
.modifyInfo { | |||
flex: none; | |||
} | |||
} | |||
} | |||
} | |||
.loadDesc { | |||
.font(); | |||
.font { | |||
font-size: 12px; | |||
color: rgba(#0f0f0f, 0.8); | |||
} | |||
:global { | |||
.ant-progress-text { | |||
.font(); | |||
width: 50px; | |||
} | |||
.ant-progress-show-info .ant-progress-outer { | |||
margin-right: -58px; | |||
padding-right: 58px; | |||
} | |||
} | |||
.stateIcon { | |||
vertical-align: sub; | |||
margin-right: 8px; | |||
font-size: 16px; | |||
&.success { color: #51DCB6; } | |||
&.error {color: #D6243A; } | |||
} | |||
} | |||
.time { | |||
display: inline-block; | |||
margin-left: 8px; | |||
font-size: 12px; | |||
color: rgba(#0f0f0f, 0.6); | |||
transform: scale(0.83); | |||
} | |||
.textOverflow { | |||
white-space: nowrap; | |||
text-overflow: ellipsis; | |||
overflow: hidden; | |||
} | |||
@@ -0,0 +1,91 @@ | |||
import classNames from 'classnames'; | |||
import dayjs from 'dayjs'; | |||
import React, { CSSProperties, useMemo } from 'react'; | |||
import { memo } from 'react'; | |||
import FileIcon from '../FileIcon'; | |||
import ATooltip from '../Tooltip'; | |||
import styles from './FileStatus.less'; | |||
import { CloseCircleFilled, CheckCircleFilled } from '@ant-design/icons'; | |||
import { Progress, Button } from 'antd'; | |||
interface FileStatusProps { | |||
className?: string; | |||
style?: CSSProperties; | |||
} | |||
export default function FileStatus(props: FileStatusProps & LoadDescProps) { | |||
const { className, style, ...restProps } = props; | |||
return ( | |||
<div className={classNames(styles.fileStatus, className)} style={style}> | |||
<div className={styles.left}> | |||
<FileIcon className={styles.icon} extension="folder" /> | |||
<div className={styles.content}> | |||
<ATooltip placement="top" title="123"> | |||
<div className={styles.fileName}>文件名称文件名称文件名称文件名称文件名称文件名称文件名称文件名称文件名称文件名称文件名称文件名称</div> | |||
</ATooltip> | |||
<div className={styles.subContent}> | |||
<ATooltip placement="bottom" title="456"> | |||
<div className={styles.filePath}>文件路径文件路径文件路径文件路径文件路径文件路径</div> | |||
</ATooltip> | |||
<div className={styles.modifyInfo}>XX创建/XX同步</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div className={styles.mid}> | |||
<LoadDesc {...restProps} /> | |||
{ | |||
restProps.loadingState === 'complete' | |||
? <Time time="2020-01-01 18:24:56" /> | |||
: null | |||
} | |||
</div> | |||
<div className={styles.right}> | |||
{/* 查看1: 已下载 文件打开文件夹 */} | |||
{/* 查看2: 未下载 且 已删除 文件跳转到web端 */} | |||
{/* 下载: 未下载 且 未删除 文件 */} | |||
{/* 重新下载: 下载失败时出现 */} | |||
{/* 重新上传: 上传失败时出现 */} | |||
{/* <Button type="link" className={styles.button} size="small">重新上传</Button> */} | |||
<Button type="link" className={styles.button} size="small">暂停</Button> | |||
<Button type="link" className={styles.button} size="small">取消</Button> | |||
</div> | |||
</div> | |||
) | |||
} | |||
const Time = memo((props: { time: string }) => ( | |||
<span className={styles.time}>{dayjs(props.time).format('HH:mm:ss')}</span> | |||
)); | |||
interface LoadDescProps { | |||
type: 'upload' | 'download'; | |||
loadingState: 'loading' | 'complete'; | |||
result?: 'success' | 'fail'; | |||
progress?: number; | |||
} | |||
function LoadDesc(props: LoadDescProps) { | |||
const { result, type, loadingState, progress } = props; | |||
const keywords = type === 'upload' ? '上传' : '下载'; | |||
const [resultText, icon] = useMemo(() => { | |||
if (result === 'success') { return ['成功', <CheckCircleFilled className={classNames(styles.stateIcon, styles.success)} />]; } | |||
if (result === 'fail') { return ['失败', <CloseCircleFilled className={classNames(styles.stateIcon, styles.error)} />]; } | |||
return ['', null]; | |||
}, [result]); | |||
return ( | |||
<span className={styles.loadDesc}> | |||
{ | |||
loadingState === 'loading' | |||
? <Progress percent={progress} format={() => `${keywords}中...`} /> | |||
: ( | |||
<> | |||
{icon} | |||
{keywords} | |||
{resultText}! | |||
</> | |||
) | |||
} | |||
</span> | |||
) | |||
} |
@@ -0,0 +1 @@ | |||
export { default } from './FileStatus'; |
@@ -0,0 +1,8 @@ | |||
import React from 'react'; | |||
import { Tooltip, TooltipProps } from 'antd'; | |||
export default function ATooltip(props: TooltipProps) { | |||
return ( | |||
<Tooltip color="#fff" overlayInnerStyle={{ color: "rgba(0, 0, 0, 0.53)" }} {...props} /> | |||
) | |||
} |