Browse Source

完成项目列表页面的简易版本

main
郑州 3 years ago
parent
commit
44aefd5ecb
18 changed files with 17439 additions and 25 deletions
  1. +2
    -1
      babel.config.js
  2. +1
    -0
      package.json
  3. +66
    -0
      scripts/babel-plugin-jsx-classnames-advanced.js
  4. +10
    -13
      src/actions/app.js
  5. +3
    -0
      src/app.jsx
  6. +18
    -0
      src/components/Avator/index.jsx
  7. +10
    -0
      src/components/Avator/index.module.scss
  8. +44
    -0
      src/pages/project/components/welcome.jsx
  9. +7
    -0
      src/pages/project/components/welcome.module.scss
  10. +52
    -0
      src/pages/project/index.jsx
  11. +45
    -0
      src/pages/project/index.module.scss
  12. +16
    -0
      src/pages/project/service.js
  13. +10
    -0
      src/pages/recycle/index.jsx
  14. +7
    -4
      src/service/oss.js
  15. +21
    -0
      src/utils/hooks.js
  16. +30
    -5
      src/utils/request.js
  17. +34
    -2
      src/utils/tool.js
  18. +17063
    -0
      yarn.lock

+ 2
- 1
babel.config.js View File

@@ -6,5 +6,6 @@ module.exports = {
framework: 'react',
ts: false
}]
]
],
plugins: ['./scripts/babel-plugin-jsx-classnames-advanced'],
}

+ 1
- 0
package.json View File

@@ -55,6 +55,7 @@
},
"devDependencies": {
"@babel/core": "^7.8.0",
"@babel/helper-module-imports": "^7.14.5",
"@tarojs/mini-runner": "3.2.10",
"@tarojs/webpack-runner": "3.2.10",
"@types/react": "^17.0.2",


+ 66
- 0
scripts/babel-plugin-jsx-classnames-advanced.js View File

@@ -0,0 +1,66 @@
const { addDefault } = require('@babel/helper-module-imports');

const defaultOpts = {
nameHint: '_babel_plugin_jsx_classnames_advanced',
attributeNames: [
'className',
'dropdownClassName',
'wrapperClassName',
'wrapClassName',
'overlayClassName',
],
ignoreMemberExpression: true,
ignoreIdentifier: true,
};

function f({ types: t }) {
function replaceNode(path, callee) {
path.node.value = t.JSXExpressionContainer(
t.callExpression(
callee,
[path.node.value.expression],
),
);
}

const visitor = {
JSXAttribute(path, state) {
const {
attributeNames, nameHint, ignoreIdentifier, ignoreMemberExpression,
} = { ...defaultOpts, ...state.opts };

if (
!attributeNames.some(i => t.isJSXIdentifier(path.node.name, { name: i })) ||
!t.isJSXExpressionContainer(path.node.value) ||
t.isStringLiteral(path.node.value.expression) ||
(t.isMemberExpression(path.node.value.expression) && ignoreMemberExpression) ||
(t.isIdentifier(path.node.value.expression) && ignoreIdentifier)
) return;

if (nameHint === false) {
replaceNode(path, addDefault(path, 'classnames', { nameHint: defaultOpts.nameHint }));
return;
}

if (!state.isImported) {
state.importedIndentifier = addDefault(path, 'classnames', { nameHint });

replaceNode(path, state.importedIndentifier);

state.isImported = 1;
} else {
replaceNode(path, t.identifier(state.importedIndentifier.name));
}
},
};

return {
visitor,
};
}




module.exports = f;
module.exports.defaultOpts = defaultOpts;

+ 10
- 13
src/actions/app.js View File

@@ -3,7 +3,6 @@ import getData from '@root/utils/request';
import Taro from '@tarojs/taro';
import { isReqSuccess } from '@root/utils/tool';
import { firstCharToLowerCase } from '../utils/tool';
import { getFileUrl } from '../service/oss';

async function saveSafeArea(dispatch) {
const res = await Taro.getSystemInfo();
@@ -17,11 +16,8 @@ async function saveSafeArea(dispatch) {
}
}

const hint = (msg) => Taro.showToast({ title: msg, icon: 'none', duration: 2000 })

export function logout() {
return async (dispatch) => {
storage.remove('token');
dispatch({
type: 'app/logout',
});
@@ -37,7 +33,7 @@ export function initApp() {

export function login({ username, password }) {
return async (dispatch, getState) => {
const res = await getData('authentication/login', { UserName: username, Password: password });
const res = await getData('authentication/login', { UserName: username, Password: password }, true);
if (!isReqSuccess(res)) {
dispatch(logout());
return res;
@@ -51,16 +47,17 @@ export function login({ username, password }) {
// dispatch(logout());
// return false;
// }
console.log(getFileUrl(userData.headImgUrl));
const payload = {
accountId: userData.id,
accountName: userData.cnName,
customerId: userData.companyId,
avatorUrl: userData.headImgUrl,
};
storage.set('accountId', payload.accountId);
dispatch({
type: 'app/login',
payload: {
accountId: userData.id,
accountName: userData.cnName,
customerId: userData.companyId,
avatorUrl: userData.headImgUrl,
}
})
payload,
});

return res;
}


+ 3
- 0
src/app.jsx View File

@@ -4,6 +4,9 @@ import './custom-taroui-variables.scss';
import configStore from './store';
import { initApp } from '@root/actions/app';
import { Provider } from 'react-redux';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn')

const store = configStore();



+ 18
- 0
src/components/Avator/index.jsx View File

@@ -0,0 +1,18 @@
import { Image, View } from '@tarojs/components';
import { useSelector } from 'react-redux';
import React, { useMemo } from 'react';
import styles from './index.module.scss';
import { getFileUrl } from '@root/service/oss';

export default function Avator(props) {
const { src: propsSrc, className, ...restProps } = props;
const accountAvator = useSelector(state => state.app.avatorUrl)
const src = useMemo(() => {
return getFileUrl(propsSrc || accountAvator);
}, [propsSrc, accountAvator])
return (
<View className={[styles.avator, className]}>
<Image src={src} />
</View>
)
}

+ 10
- 0
src/components/Avator/index.module.scss View File

@@ -0,0 +1,10 @@
.avator {
display: inline-block;
border-radius: 50%;
overflow: hidden;
> image {
vertical-align: top;
width: 100%;
height: 100%;
}
}

+ 44
- 0
src/pages/project/components/welcome.jsx View File

@@ -0,0 +1,44 @@
import { useSelector } from 'react-redux';
import React, {useState} from 'react';
import styles from './welcome.module.scss';
import dayjs from 'dayjs';
import { useInterval } from '@root/utils/hooks';
import { Text, View } from '@tarojs/components';


const getText = () => {
const now = dayjs();
const hour = now.get('hour');
const dateText = now.format('M月D日 ddd');
const welcome = hour > 5 && hour < 12
? '上午好! '
: hour === 12
? '中午好!'
: hour > 12 && hour < 18
? '下午好!'
: hour > 18 && hour < 22
? '晚上好!'
: '夜深了,注意休息。';


return [dateText, welcome]
}

export default function Welcome(props) {
const { className } = props;
const accountName = useSelector(state => state.app.accountName);
const [dateText, setDateText] = useState('');
const [welcomeText, setWelcomeText] = useState('');
useInterval(() => {
const [nextDateText, nextWelcomeText] = getText();
setDateText(nextDateText);
setWelcomeText(nextWelcomeText);
}, 1000 * 60, { immediate: true});
return (
<View className={[styles.welcome, className]}>
<Text className={styles.text}>{dateText}</Text>
<Text className={styles.text}>{`${welcomeText} ${accountName}`}</Text>
</View>
)
}

+ 7
- 0
src/pages/project/components/welcome.module.scss View File

@@ -0,0 +1,7 @@
.welcome {
.text {
display: block;
color: rgba(50, 50, 60, 100);
font-size: 40px;
}
}

+ 52
- 0
src/pages/project/index.jsx View File

@@ -0,0 +1,52 @@
import { Image, Text, View, ScrollView } from '@tarojs/components';
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
import Taro from '@tarojs/taro';
import styles from './index.module.scss';
import Avator from '@root/components/Avator';
import Welcome from './components/welcome';
import { fetchProjectList } from './service';

export default function ProjectListView() {
const [projectList, setProjectList] = useState([]);

useLayoutEffect(() => {
Taro.setNavigationBarColor({
backgroundColor: '#f6f6f6', // 窗口的背景色为白色
frontColor: '#ffffff',
});
}, []);

const fetchList = useCallback(async () => {
const dataList = await fetchProjectList();
setProjectList(dataList);
}, []);
useEffect(() => {
fetchList();
}, [fetchList])

return (
<ScrollView className={styles.project} scrollY>
<View className={styles.wrapPadding}>
<Avator className={styles.avator} />
</View>
<Welcome className={styles.wrapPadding} />
<View className={[styles.listTitle, styles.wrapPadding]}>
<Text>我的项目</Text>
</View>
{
projectList.map(project => (
<View className={styles.projectItem} key={project.id}>
<Image className={styles.img} src={project.showImgUrl} />
<View className={styles.label}>
<Text>{project.projName}</Text>
</View>
</View>
))
}
</ScrollView>
)
}




+ 45
- 0
src/pages/project/index.module.scss View File

@@ -0,0 +1,45 @@
.project {
$commonPadding: 24px * 2;
height: 100vh;
background-color: #f6f6f6;
.wrapPadding {
padding: 0 $commonPadding;
}
.avator {
width: 84px;
height: 84px;
}

.list {
height: calc(100% - 274px * 2);
&Title {
margin-top: 40px * 2;
color: rgba(50, 50, 60, 100);
font-size: 20px * 2;
}
}

&Item {
display: flex;
padding: 8px * 2 $commonPadding;
.img {
flex: none;
width: 108px * 2;
height: 80px * 2;
border-radius: 8px;
box-shadow: 0px 7px 16px 0px rgba(0, 0, 0, 0.2);
}
.label {
flex: 1;
margin-left: 23px * 2;
// white-space: nowrap;
// text-overflow: ellipsis;
// overflow: hidden;
color: rgba(50, 50, 60, 100);
font-size: 18px * 2;
display: flex;
align-items: center;

}
}
}

+ 16
- 0
src/pages/project/service.js View File

@@ -0,0 +1,16 @@
import { getFileUrl } from "@root/service/oss";
import getData from "@root/utils/request";
import * as storage from '@root/utils/storage';
import { firstCharToLowerCase } from "@root/utils/tool";


export async function fetchProjectList() {
const accountId = storage.get('accountId');
const res = await getData('project/queryProjectListByUserId', { userId: accountId });
const dataList = (res.data || []).map(upperD => {
const d = firstCharToLowerCase(upperD);
d.showImgUrl = getFileUrl(d.showImgUrl);
return d;
});
return dataList;
}

+ 10
- 0
src/pages/recycle/index.jsx View File

@@ -0,0 +1,10 @@
import { Text, View } from '@tarojs/components';
import React from 'react';

export default function RecycleView() {
return (
<View>
<Text>回收站页</Text>
</View>
)
}

+ 7
- 4
src/service/oss.js View File

@@ -28,12 +28,15 @@ export const wrapOssProtocol = path => `oss://${path}`;
* @param {*} saveAs
*/
export const getFileUrl = memoize(function getFileUrlInner(serverOSSPath = '', saveAs) {
if (serverOSSPath.indexOf('oss://') !== 0) {
return serverOSSPath;
if (serverOSSPath.indexOf('oss://') === 0) {
const objectKey = serverOSSPath.substr(6);
return `https://yiyun-client-files.oss-cn-hangzhou.aliyuncs.com/${objectKey}`;
}
if(serverOSSPath.indexOf('static/') === 0) { // 处理 static/img/faceImg/faceXX.png
return `https://yiyun-client-files.oss-cn-hangzhou.aliyuncs.com/faceImg/${serverOSSPath.substr(19)}`;
}
const objectKey = serverOSSPath.substr(6);
// const filename = saveAs || objectKey.split('/').pop();
return `https://yiyun-client-files.oss-cn-hangzhou.aliyuncs.com/${objectKey}`;
return serverOSSPath;
})
// export async function saveStr(saveAs, str) {
// return await getData('oss', 'saveStr', { saveAs, context: str });


+ 21
- 0
src/utils/hooks.js View File

@@ -0,0 +1,21 @@
import { useEffect, useRef } from 'react';

export function useInterval(fn,delay,options) {
const immediate = options?.immediate;

const fnRef = useRef();
fnRef.current = fn;

useEffect(() => {
if (delay === undefined || delay === null) return;
if (immediate) {
fnRef.current?.();
}
const timer = setInterval(() => {
fnRef.current?.();
}, delay);
return () => {
clearInterval(timer);
};
}, [delay]);
}

+ 30
- 5
src/utils/request.js View File

@@ -1,24 +1,49 @@
import Taro from '@tarojs/taro';
import { parseRequest } from './request.config';
import { firstCharToLowerCase } from './tool';
import { firstCharToLowerCase, handleRequest, hint } from './tool';
import * as storage from './storage';

const ip = 'http://139.198.180.242:9003';

const header = { 'Content-Type': 'application/json' };

const getData = async (path, params = {}) => {
const datas = JSON.stringify({ ...params });
let gsessionId = storage.get('gssesionid');

const getData = async (path, params = {}, silent = false) => {
// const datas = JSON.stringify({ ...params });
if(gsessionId) {
header['Cookie'] = gsessionId;
}
const [method, fullpath] = parseRequest(path);
const httpResponse = await Taro.request({
url: `${ip}/${fullpath}`,
data: datas,
data: params,
header,
method,
});
const ifhaveCookie = httpResponse.cookies[0] && httpResponse.cookies[0].match(/gsessionid\=([\d\w]*)/);
if(ifhaveCookie) {
gsessionId = ifhaveCookie[0];
}
if (httpResponse.statusCode !== 200) {
debugger;
}
return firstCharToLowerCase(httpResponse.data);
const outputData = firstCharToLowerCase(httpResponse.data);
outputData.httpCode = httpResponse.statusCode;
handleRequest(outputData)
.httpError(() => {
if(!silent) {
hint('网络错误,请稍微再试');
}
})
.error(() => {
if(!silent) {
hint(outputData.msg);
}
});
return outputData;
}



export default getData;

+ 34
- 2
src/utils/tool.js View File

@@ -1,6 +1,5 @@


export const isReqSuccess = res => res.code === 0 || res.Code === 0;

export function firstCharToLowerCase(obj) {
return Object.entries(obj).reduce((o, [key, value]) => {
@@ -9,9 +8,42 @@ export function firstCharToLowerCase(obj) {
},{});
}

export const hint = msg => Taro.showToast({ title: msg, icon: 'none', duration: 2000 });


export function firstCharToUpperCase(obj) {
return Object.entries(obj).reduce((o, [key, value]) => {
o[`${key[0].toLocaleUpperCase()}${key.slice(1)}`] = value;
return o;
},{});
}
}

export const isReqSuccess = res => res.code === 0 || res.Code === 0;
class RequestHandler {
constructor(response) {
this.response = response;
}
success(f) {
const res = this.response;
if (res.httpCode !== 200) { return; }
if (!isReqSuccess(res)) { return; }
f(res);
return this;
}
error(f) { // 业务上报错
const res = this.response;
if (res.httpCode !== 200) { return; }
if (isReqSuccess(res)) { return; }
f(res);
return this;
}
httpError(f) { // 请求报错
const res = this.response;
if(res.httpCode !== 200) {
f(res);
}
return this;
}
}

export const handleRequest = res => new RequestHandler(res);

+ 17063
- 0
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save