@@ -39,10 +39,26 @@ export function login({ username, password }) { | |||
return async (dispatch, getState) => { | |||
const res = await getData('authentication/login', { UserName: username, Password: password }, true); | |||
if (!isReqSuccess(res)) { | |||
dispatch(logout()); | |||
// dispatch(logout()); | |||
return res; | |||
} | |||
const userData = firstCharToLowerCase(res.data); | |||
const isSuperAdmin = userData.backgroundPermission === 3; | |||
if(isSuperAdmin) { | |||
return { | |||
code: 'err', | |||
msg: '该账号没有访问权限', | |||
} | |||
} | |||
const companyInfoRes = await getData('company/queryFrontDeskCompanyById', { id: userData.companyId }); | |||
if (!isReqSuccess(companyInfoRes)) { | |||
return companyInfoRes; | |||
} | |||
const { data: { company: { SoftwareVersion } = {} } = {} } = companyInfoRes; | |||
// 角色判断 | |||
// const { app: appStore } = getState(); | |||
@@ -56,8 +72,10 @@ export function login({ username, password }) { | |||
accountName: userData.cnName, | |||
customerId: userData.companyId, | |||
avatorUrl: userData.headImgUrl, | |||
isMini: SoftwareVersion === 'Mini', | |||
}; | |||
storage.set('accountId', payload.accountId); | |||
storage.set('isMini', payload.isMini) | |||
dispatch({ | |||
type: 'app/login', | |||
payload, | |||
@@ -0,0 +1,127 @@ | |||
.list { | |||
background-color: transparent; | |||
&:after { | |||
content: none; | |||
} | |||
} | |||
.listItem { | |||
padding: 0 20px * 2 0 8px * 2; | |||
&:after { | |||
left: 72px * 2; | |||
} | |||
:global { | |||
.at-list__item-thumb { | |||
width: 64px * 2; | |||
height: 64px * 2; | |||
margin-right: 0; | |||
padding: 5px * 2; | |||
box-sizing: border-box; | |||
} | |||
.at-icon-chevron-right { | |||
color: #7850FF; | |||
} | |||
.item-extra__image { | |||
margin-right: 0; | |||
} | |||
} | |||
} | |||
.listItem2 { | |||
$itemHeight: 64px * 2; | |||
position: relative; | |||
overflow: hidden; | |||
height: $itemHeight; | |||
.content { | |||
position: absolute; | |||
left: 0; | |||
right: 0; | |||
top: 0; | |||
height: 100%; | |||
display: flex; | |||
align-items: center; | |||
z-index: 2; | |||
padding: 0 20px * 2 0 8px * 2; | |||
transition: right 0.3s linear; | |||
background-color: rgba(#f6f6f6, 1); | |||
&.dragging { | |||
transition: none; | |||
} | |||
.img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
.itemThumb { | |||
width: $itemHeight; | |||
height: $itemHeight; | |||
padding: 10px; | |||
margin-right: 0; | |||
box-sizing: border-box; | |||
} | |||
.itemContent { | |||
flex: 1; | |||
overflow: hidden; | |||
margin-right: 40px; | |||
&_info { | |||
&_title { | |||
color: #32323c; | |||
font-size: 16Px; | |||
line-height: 1.5; | |||
white-space: nowrap; | |||
text-overflow: ellipsis; | |||
} | |||
&_note { | |||
color: rgba(0, 0, 0, 0.56);; | |||
font-size: 12Px; | |||
line-height: 1.5; | |||
} | |||
} | |||
} | |||
.itemExtra { | |||
position: relative; | |||
max-width: 235px; | |||
text-align: right; | |||
&Thumb { | |||
display: inline-block; | |||
width: 56px; | |||
height: 56px; | |||
vertical-align: middle; | |||
margin-right: 0; | |||
} | |||
&Icon { | |||
position: relative; | |||
right: -12px; | |||
} | |||
} | |||
} | |||
.actions { | |||
position: absolute; | |||
right: 0; | |||
z-index: 1; | |||
.deleteBtn { | |||
background-color: #D6243A; | |||
color: #fff; | |||
text-align: center; | |||
line-height: $itemHeight; | |||
} | |||
} | |||
&:after { | |||
content: ""; | |||
position: absolute; | |||
transform-origin: center; | |||
box-sizing: border-box; | |||
pointer-events: none; | |||
top: auto; | |||
left: 144rpx; | |||
right: 0; | |||
bottom: 0; | |||
z-index: 3; | |||
transform: scaleY(0.5); | |||
border-bottom: 1PX solid #d6e4ef; | |||
border-bottom-width: 1px; | |||
border-bottom-style: solid; | |||
border-bottom-color: rgb(214, 228, 239); | |||
} | |||
} |
@@ -0,0 +1,135 @@ | |||
import { getFileUrl } from '@root/service/oss'; | |||
import { ScrollView, View, MovableArea, MovableView, Image } from '@tarojs/components'; | |||
import React, { useCallback, useState } from 'react'; | |||
import { AtIcon, AtList, AtListItem } from 'taro-ui'; | |||
import styles from './fileExplorer.module.scss'; | |||
export default function FileExplorer(props) { | |||
const { className, node, nodeHash, gotoNode } = props; | |||
return ( | |||
<ScrollView scrollY className={className}> | |||
<AtList className={styles.list}> | |||
{ | |||
node && node.mapChildrenIds(nodeId => { | |||
const fileNode = nodeHash[nodeId]; | |||
if (!fileNode) return null; | |||
return ( | |||
fileNode.type === 'folder' | |||
? ( | |||
<ListItem | |||
key={fileNode.id} | |||
className={styles.listItem} | |||
title={fileNode.label} | |||
arrow="right" | |||
// note="xxx个项目" | |||
onClick={() => gotoNode(fileNode)} | |||
thumb={getFileUrl('oss://assets/file-icon/folder.png')} | |||
/> | |||
) | |||
: ( | |||
<ListItem | |||
key={fileNode.id} | |||
className={[styles.listItem, styles.fileItem]} | |||
title={fileNode.label} | |||
note="版本: xxx" | |||
thumb={getFileUrl('oss://assets/file-icon/default.svg')} | |||
extraThumb={getFileUrl('oss://assets/file-icon/download.png')} | |||
/> | |||
) | |||
) | |||
}) | |||
} | |||
</AtList> | |||
</ScrollView> | |||
) | |||
} | |||
const deleteBtnWidth = 64; | |||
function ListItem(props) { | |||
const { className, enableDelete = false, title, note, arrow, thumb, extraThumb, onClick } = props; | |||
const [initX, setInitX] = useState(); | |||
const [swipeWidth, setSwipeWidth] = useState(0); | |||
const onTouchStart = useCallback((e) => { | |||
setInitX(e.touches[0].pageX); | |||
}, [enableDelete]); | |||
const onTouchMove = useCallback((e) => { | |||
const currentX = e.touches[0].pageX; | |||
const diffx = currentX - initX; | |||
setSwipeWidth(prevValue => { | |||
return diffx >= 0 | |||
? prevValue ? Math.max(deleteBtnWidth - diffx, 0) : 0 // 右滑 | |||
: prevValue !== deleteBtnWidth ? Math.min(deleteBtnWidth, Math.abs(diffx)) : deleteBtnWidth; // 左滑 | |||
}); | |||
}, [initX, enableDelete]); | |||
const onTouchEnd = useCallback(() => { | |||
setSwipeWidth(prevWidth => prevWidth > deleteBtnWidth / 2 ? deleteBtnWidth : 0); | |||
setInitX(); | |||
}, [enableDelete]) | |||
return ( | |||
<View | |||
className={[styles.listItem2, className]} | |||
> | |||
<View | |||
className={[styles.content, initX ? styles.dragging : '']} | |||
onTouchStart={enableDelete ? onTouchStart : undefined} | |||
onTouchMove={enableDelete ? onTouchMove : undefined} | |||
onTouchEnd={enableDelete ? onTouchEnd : undefined} | |||
style={{ right: `${swipeWidth * 2}rpx` }} | |||
onClick={onClick} | |||
> | |||
{ | |||
thumb ? ( | |||
<View className={styles.itemThumb}> | |||
<Image className={styles.img} src={thumb} /> | |||
</View> | |||
) | |||
: null | |||
} | |||
<View className={styles.itemContent}> | |||
<View className={styles.itemContent_info}> | |||
<View className={styles.itemContent_info_title}>{title}</View> | |||
{ | |||
note | |||
? ( | |||
<View className={styles.itemContent_info_note}>{note}</View> | |||
) | |||
: null | |||
} | |||
</View> | |||
</View> | |||
<View className={styles.itemExtra}> | |||
{ | |||
extraThumb | |||
? ( | |||
<View className={styles.itemExtraThumb}> | |||
<Image className={styles.img} src={extraThumb} /> | |||
</View> | |||
) | |||
: null | |||
} | |||
{ | |||
arrow | |||
? ( | |||
<AtIcon className={styles.itemExtraIcon} value='chevron-right' /> | |||
) | |||
: null | |||
} | |||
</View> | |||
</View> | |||
<View className={styles.actions}> | |||
<View | |||
className={styles.deleteBtn} | |||
style={{ width: `${deleteBtnWidth * 2}rpx` }} | |||
>删除</View> | |||
</View> | |||
</View> | |||
) | |||
} |
@@ -1,10 +1,77 @@ | |||
import { Text, View } from '@tarojs/components'; | |||
import React from 'react'; | |||
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react'; | |||
import Page from '@root/components/Page'; | |||
import { AtSearchBar } from 'taro-ui'; | |||
import styles from './index.module.scss'; | |||
import FileExplorer from '@root/components/FileExplorer'; | |||
import { fetchProjectList, fetchProjectRemovedFiles } from './service'; | |||
export default function RecycleView() { | |||
const [tempKeywords, setTempKeywords] = useState(''); | |||
const [effectKeywords, setEffectKeywords] = useState(''); | |||
const [currentNode, setCurrentNode] = useState(null); | |||
const [nodeHash, setNodeHash] = useState({}); | |||
useLayoutEffect(() => { | |||
fetchProjectList() | |||
.then(([rootNode, nextNodeHash]) => { | |||
setCurrentNode(rootNode); | |||
setNodeHash(nextNodeHash); | |||
}) | |||
}, []); | |||
const gotoNode = useCallback((nextFileNode) => { | |||
if(nextFileNode.isRoot) { | |||
fetchProjectList() | |||
.then(([rootNode, nextNodeHash]) => { setCurrentNode(rootNode); setNodeHash(nextNodeHash); }) | |||
} else if(nextFileNode.isProject) { | |||
fetchProjectRemovedFiles(nextFileNode) | |||
.then(([nextNodeHash]) => { | |||
setCurrentNode(nextFileNode); | |||
setNodeHash(prevHash => ({ ...prevHash, ...nextNodeHash })); | |||
}) | |||
} else { | |||
setCurrentNode(nextFileNode); | |||
} | |||
}, []) | |||
return ( | |||
<View> | |||
<Text>回收站页</Text> | |||
</View> | |||
<Page | |||
className={styles.page} | |||
title={ | |||
currentNode && currentNode.id !== 'root' | |||
? currentNode.label | |||
: '回收站' | |||
} | |||
headerColor="#EBEBEB" | |||
leftText={ | |||
currentNode && currentNode.id !== 'root' | |||
? '返回'// nodeHash[currentNode.parentId].label || '项目列表' | |||
: undefined | |||
} | |||
onClickLeftIcon={() => { | |||
setCurrentNode(prevNode => nodeHash[prevNode.parentId]) | |||
}} | |||
> | |||
<AtSearchBar | |||
className={styles.search} | |||
value={tempKeywords} | |||
onChange={setTempKeywords} | |||
onActionClick={() => setEffectKeywords(tempKeywords)} | |||
onClear={() => {setEffectKeywords(''); setTempKeywords('');}} | |||
/> | |||
{ | |||
currentNode | |||
? ( | |||
<FileExplorer | |||
className={styles.list} | |||
node={currentNode} | |||
gotoNode={gotoNode} | |||
nodeHash={nodeHash} | |||
/> | |||
) | |||
: null | |||
} | |||
</Page> | |||
) | |||
} |
@@ -0,0 +1,25 @@ | |||
$searchBoxHeight: 82px; | |||
.page { | |||
background-color: #f6f6f6; | |||
} | |||
.search { | |||
height: $searchBoxHeight; | |||
background-color: #ebebeb; | |||
border-bottom: 1px solid rgba(0, 0, 0, 0.2); | |||
padding: 12px 16px * 2; | |||
:global(.at-search-bar__input-cnt) { | |||
border-radius: 8Px; | |||
background-color: rgba(0, 0, 0, 0.05); | |||
} | |||
:global(.at-search-bar__input) { | |||
color: rgba(0, 0, 0, 0.56); | |||
} | |||
:global(.at-search-bar__action) { | |||
background-color: transparent; | |||
color: #7850FF; | |||
} | |||
} | |||
.list { | |||
height: calc(100% - #{$searchBoxHeight}); | |||
} |
@@ -0,0 +1,98 @@ | |||
import getData from '@root/utils/request'; | |||
import * as storage from '@root/utils/storage'; | |||
import { FileNode, firstCharToLowerCase } from '@root/utils/tool'; | |||
import { hideLoading, showLoading } from '@tarojs/taro'; | |||
export async function fetchProjectList() { | |||
const accountId = storage.get('accountId'); | |||
showLoading(); | |||
const res = await getData('project/queryAllProjectListByUserId', { userId: accountId }); | |||
hideLoading(); | |||
const nodeHash = {}; | |||
const rootNode = new FileNode('root', '', null, 'folder'); | |||
rootNode.isRoot = true; | |||
nodeHash[rootNode.id] = rootNode; | |||
(res.data || []).forEach((upperProject) => { | |||
if(!upperProject.HasFile) { return; } | |||
// const project = firstCharToLowerCase(upperProject); | |||
const projectFolder = new FileNode(upperProject.Id, upperProject.ProjName, rootNode.id, 'folder'); | |||
projectFolder.isProject = true; | |||
nodeHash[projectFolder.id] = projectFolder; | |||
// if (upperProject.NodeFolder) { | |||
// upperProject.NodeFolder | |||
// .forEach((upperNodeFolder) => { | |||
// if(upperNodeFolder.FirstFolderId === upperNodeFolder.Id || | |||
// upperNodeFolder.NodeId === '0') { return; } | |||
// const folderNode = new FileNode(upperNodeFolder.Id, upperNodeFolder.FolderName, projectFolder.id, 'folder'); | |||
// nodeHash[folderNode.id] = folderNode; | |||
// projectFolder.append(folderNode.id); | |||
// }); | |||
// ; | |||
// } | |||
rootNode.append(projectFolder.id) | |||
}, []); | |||
return [rootNode, nodeHash]; | |||
} | |||
export async function fetchProjectRemovedFiles(projectNode) { | |||
showLoading(); | |||
const hash = {}; | |||
const isMini = storage.get('isMini'); | |||
const res = await getData('file/queryFilesFromRecycleBinByProjectId', { projectId: projectNode.id }); | |||
hideLoading(); | |||
projectNode.removeAllChildrenIds(); | |||
(res.data || []).forEach((topNode) => { | |||
if(!topNode.ProjArchives) { return ; } | |||
let parentNode = projectNode; | |||
if(!isMini) { | |||
const node = new FileNode(topNode.Id, topNode.FolderName, projectNode.id, 'folder'); | |||
hash[node.id] = node; | |||
projectNode.append(node.id); | |||
parentNode = node; | |||
} | |||
topNode.ProjArchives.forEach((upperFileItem) => { | |||
const fileItem = firstCharToLowerCase(upperFileItem); | |||
const lastFolder = generateFolder(hash, `${topNode.FolderName}${fileItem.relativePath ? `/${fileItem.relativePath}` : ''}`, parentNode); | |||
const file = new FileNode(fileItem.id, fileItem.archName, parentNode.id, 'file', fileItem); | |||
lastFolder.append(file.id); | |||
// parentNode.append(file); | |||
hash[file.id] = file; | |||
}); | |||
}); | |||
console.log('fetchProjectRemovedFiles:', res); | |||
return [hash]; | |||
} | |||
function resolveFileListToTree(upperFileList, hash) { | |||
const headListIdList = []; | |||
} | |||
function generateFolder(folderMap, filePath, topNode) { | |||
const paths = filePath.split("/"); | |||
let lastFolder = topNode; | |||
paths.forEach((_, idx) => { | |||
if(idx === 0) { // 第一位为节点文件夹的名称,已在前文创建,不在重复 | |||
return; | |||
} | |||
const fullRelativePath = `${paths.slice(0, idx + 1).join("/")}`; | |||
let folder; | |||
if (folderMap[fullRelativePath]) { | |||
folder = folderMap[fullRelativePath]; | |||
} else { | |||
const folderName = paths[idx]; | |||
const parentNodeId = idx === 1 ? topNode.id : paths.slice(0, idx).join('/'); | |||
folder = new FileNode(fullRelativePath, folderName, parentNodeId, 'folder'); | |||
folderMap[fullRelativePath] = folder; | |||
} | |||
// const parentRelativePath = folder.parentFolderPath; | |||
(folderMap[folder.parentId] || topNode) | |||
.append(folder.id); | |||
lastFolder = folder; | |||
}); | |||
return lastFolder; | |||
} |
@@ -6,6 +6,7 @@ const initState = { | |||
accountName: '', | |||
companyId: '', | |||
avatorUrl: '', | |||
isMini: false, | |||
} | |||
@@ -30,6 +31,7 @@ export default (state = initState, { type, payload }) => { | |||
accountName: '', | |||
companyId: '', | |||
avatorUrl: '', | |||
isMini: false, | |||
} | |||
default: | |||
return state; | |||
@@ -46,4 +46,35 @@ class RequestHandler { | |||
} | |||
} | |||
export const handleRequest = res => new RequestHandler(res); | |||
export const handleRequest = res => new RequestHandler(res); | |||
export class FileNode { | |||
// type: 'folder' | 'file' | |||
constructor(id, label, parentId, type, data = {}) { | |||
Object.assign(this, { id, label, parentId, type, data }); | |||
} | |||
append(childNodeId) { | |||
if(!this.childrenIds) { this.childrenIds = []; } | |||
this.childrenIds.push(childNodeId); | |||
} | |||
// sortChildren(f = sortFunc) { | |||
// if(!this.hasChildren) { return this; } | |||
// this.childrenIds.sort(f); | |||
// return this; | |||
// } | |||
mapChildrenIds(f) { | |||
if(!this.hasChildren) { return []; } | |||
return this.childrenIds.map(f); | |||
} | |||
// everyChildren(f) { | |||
// if(!this.hasChildren) { return false; } | |||
// return this.childrenIds.every(f); | |||
// } | |||
get hasChildren() { | |||
return this.childrenIds && !!this.childrenIds.length; | |||
} | |||
removeAllChildrenIds() { | |||
return this.childrenIds = []; | |||
} | |||
} |