Jelajahi Sumber

feat: 接口对接完成

xyh 2 tahun lalu
induk
melakukan
b9c415ebff

+ 1 - 1
project.config.json

@@ -4,7 +4,7 @@
   "description": "首钢收发货小程序",
   "appid": "wxbbc727cd608c4336",
   "setting": {
-    "urlCheck": true,
+    "urlCheck": false,
     "es6": false,
     "enhance": false,
     "compileHotReLoad": false,

+ 45 - 1
src/apis/deliver.js

@@ -1,4 +1,4 @@
-import {post} from './request';
+import {get, post, put} from './request';
 
 const BASE_URL = '/dispatch';
 
@@ -21,3 +21,47 @@ const BASE_URL = '/dispatch';
 export function addDeliver(data) {
   return post({url: `${BASE_URL}/addDispatch`, data});
 }
+
+/** 
+ * 确认收货
+ *
+ *  {
+    "customer": "string",
+    "truckNo": "string",
+    "anomaly": "string",
+    "note": "string",
+    "imgs": "string"
+  }
+ *  */
+export function confirmDeliver(data) {
+  return put({url: `${BASE_URL}/updDispatch`, data});
+}
+
+/**
+ * 获取列表
+ *
+ * arrivalTime arrivalTimes 发货时间
+ * finalTime finalTimes 送达时间
+ * customer 客户号
+ * truckNo 卡车号
+ * anomaly 1异常订单
+ * page
+ * limit
+ *  */
+export function getList(data) {
+  return get({url: `${BASE_URL}/getDispatchList`, data});
+}
+
+/**
+ *
+ * 获取详情
+ *
+ * track 卡车号
+ * customer 客户号
+ */
+export function getInfo(track, customer) {
+  return get({
+    url: `${BASE_URL}/getDispatchList`,
+    data: {track, customer, page: '1', limit: '1'},
+  });
+}

+ 1 - 0
src/apis/index.js

@@ -1,3 +1,4 @@
 export * from './user';
 export * from './client';
 export * from './deliver';
+export * from './file';

+ 3 - 3
src/apis/request.js

@@ -11,7 +11,7 @@ function request({url, data, method, skipError}) {
       url: `${NETWORK_URL}${url}`,
       data,
       method,
-      timeout: 5000,
+      timeout: 10000,
       header: {
         'Content-Type': 'application/json',
         'Cache-Control': 'no-cache',
@@ -73,12 +73,12 @@ export function uploadFile(url, img, skipError = false) {
       url: NETWORK_URL + url,
       filePath: img,
       name: 'file',
-      timeout: 5000,
+      timeout: 10000,
       header: {
         'Cache-Control': 'no-cache',
       },
       success(data) {
-        const result = data.data;
+        const result = JSON.parse(data.data);
         if (result.code === '500' && !skipError) {
           showToast({
             title: result.msg,

TEMPAT SAMPAH
src/assets/empty.png


+ 30 - 0
src/components/empty/index.jsx

@@ -0,0 +1,30 @@
+import empty from '@assets/empty.png';
+import {View, Image, Text} from '@tarojs/components';
+import classNames from 'classnames';
+
+const Empty = function ({className, msg, imgClassName, children}) {
+  return (
+    <View
+      className={classNames(
+        'w-full h-full flex flex-col items-center justify-center',
+        className,
+      )}
+    >
+      <Image
+        src={empty}
+        mode='widthFix'
+        className={classNames(
+          'w-[calc(100vw-80px)] mx-auto -mt-10',
+          'lg:w-64',
+          imgClassName,
+        )}
+      />
+      <Text className='block text-center text-sm text-dark-3'>
+        {msg ?? '暂无数据'}
+      </Text>
+      {children}
+    </View>
+  );
+};
+
+export default Empty;

+ 2 - 0
src/components/index.js

@@ -1 +1,3 @@
 export {default as TextGroup} from './text-group';
+export {default as Refresh} from './refresh';
+export {default as Empty} from './empty';

+ 44 - 0
src/components/refresh/hooks.js

@@ -0,0 +1,44 @@
+import {useEffect, useRef, useState} from 'react';
+
+export function useScrollControl() {
+  const topRef = useRef(0);
+  function onScroll(e) {
+    topRef.current = e.detail.scrollTop;
+  }
+
+  return [topRef, onScroll];
+}
+
+export function useRefreshState(ref, isRefreshing) {
+  const [refreshState, setRefreshState] = useState(false);
+
+  useEffect(
+    function () {
+      if (isRefreshing) {
+        ref.current = 0;
+      }
+      setRefreshState(isRefreshing);
+    },
+    [isRefreshing, ref],
+  );
+
+  return refreshState;
+}
+
+export function useListEvent(options) {
+  const {isLoading, isRefreshing, onRefresh, onLoading, noMore, empty} =
+    options;
+
+  // 在有刷新或者下一页是不触发任何事件
+  function onListRefresh() {
+    if (isLoading || isRefreshing) return;
+    onRefresh();
+  }
+
+  function onListLoading() {
+    if (isLoading || isRefreshing || noMore || empty) return;
+    onLoading?.();
+  }
+
+  return {onListRefresh, onListLoading};
+}

+ 73 - 0
src/components/refresh/index.jsx

@@ -0,0 +1,73 @@
+import {ScrollView, Text} from '@tarojs/components';
+import {useListEvent, useRefreshState, useScrollControl} from './hooks';
+import {Empty} from '@components';
+import classNames from 'classnames';
+
+const Refresh = function ({
+  isRefreshing,
+  onRefresh,
+  onLoading,
+  isLoading,
+  children,
+  style,
+  className,
+  background,
+  refreshStyle,
+  noMore,
+  empty,
+  emptyMsg,
+  emptyClassName,
+  emptyChildren,
+}) {
+  const [topRef, onScroll] = useScrollControl();
+  const refreshState = useRefreshState(topRef, isRefreshing);
+  const {onListRefresh, onListLoading} = useListEvent({
+    isLoading,
+    isRefreshing: refreshState,
+    onRefresh,
+    onLoading,
+    noMore,
+  });
+
+  return (
+    <ScrollView
+      scrollY
+      style={style}
+      className={className}
+      refresherEnabled
+      refresherTriggered={refreshState}
+      onRefresherRefresh={onListRefresh}
+      onScrollToLower={onListLoading}
+      refresherBackground={background ?? '#fff'}
+      scrollTop={topRef.current}
+      onScroll={onScroll}
+      refresherDefaultStyle={refreshStyle}
+    >
+      {empty ? (
+        <Empty msg={emptyMsg} className={emptyClassName}>
+          {emptyChildren}
+        </Empty>
+      ) : (
+        children
+      )}
+
+      <Text
+        className={classNames('hidden text-sm text-center py-4 text-dark-3', {
+          '!block': isLoading,
+        })}
+      >
+        正在加载中
+      </Text>
+
+      <Text
+        className={classNames('hidden text-sm text-center py-4 text-dark-3', {
+          '!block': noMore && !isRefreshing && !isLoading && !empty,
+        })}
+      >
+        已经到底了~~
+      </Text>
+    </ScrollView>
+  );
+};
+
+export default Refresh;

+ 1 - 0
src/hooks/index.js

@@ -1,2 +1,3 @@
 export * from './use-scan-order';
 export * from './use-navigate';
+export * from './use-infinite-fetch';

+ 81 - 0
src/hooks/use-infinite-fetch/index.js

@@ -0,0 +1,81 @@
+import {useInfiniteQuery, useQueryClient} from '@tanstack/react-query';
+import {useReady} from '@tarojs/taro';
+import {useState, useRef, useLayoutEffect} from 'react';
+
+export function useInfiniteFetch({key, fn, limit: propsLimit, params}) {
+  const [enabled, setEnabled] = useState(false);
+  const page = useRef(1);
+  const limit = propsLimit ?? '10';
+
+  useLayoutEffect(
+    function () {
+      page.current = 1;
+    },
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+    key,
+  );
+
+  const {
+    data,
+    fetchNextPage,
+    isFetching,
+    isFetchingNextPage,
+    hasNextPage,
+    refetch,
+  } = useInfiniteQuery(
+    key,
+    async function () {
+      const result = await fn({page: page.current, limit, ...params});
+
+      result.code === '200' && result.data.list.length > 0 && page.current++;
+
+      return result;
+    },
+    {
+      enabled,
+      cacheTime: 0,
+      select(data) {
+        const list = data.pages
+          .map(function (val) {
+            return val.data.list;
+          })
+          .filter(val => val.length > 0);
+
+        return {pageParams: data.pageParams, pages: list.flat(2)};
+      },
+      getNextPageParam(last) {
+        if (!last) return false;
+
+        return last.data.hasNextPage;
+      },
+    },
+  );
+
+  useReady(function () {
+    setTimeout(() => setEnabled(true), 50);
+  });
+
+  const isEmpty =
+    page.current === 1 && !data?.pages.length && !isFetching && !hasNextPage;
+
+  const queryClient = useQueryClient();
+  function refresh() {
+    if (isFetching) return;
+    page.current = 1;
+    refetch({refetchPage: (_, index) => index === 0}).then(function () {
+      queryClient.setQueriesData(key, function (data) {
+        const firstData = data?.pages[0];
+
+        return {
+          pageParams: data.pageParams,
+          pages: [firstData],
+        };
+      });
+    });
+  }
+
+  return [
+    {data, isFetching, isFetchingNextPage, hasNextPage, isEmpty},
+    {fetchNextPage, refresh},
+  ];
+}

+ 1 - 1
src/hooks/use-navigate/index.js

@@ -61,7 +61,7 @@ export function useNavigate() {
 
     function navigateWithLogin(name, params) {
       if (!token) {
-        return showToast({title: '请登录', mask: true, icon: 'error'});
+        return showToast({title: '请登录', icon: 'error'});
       }
       push(name, params);
     }

+ 1 - 1
src/pages/deliver/index.jsx

@@ -52,7 +52,7 @@ export default function DeliverGoods() {
                   className='border-0 border-b border-dashed border-gray-100 last:border-none'
                 >
                   <Item title='品号' content={no} />
-                  <Item title='数量' content={Number(num)} />
+                  <Item title='数量' content={num} />
                 </View>
               );
             })}

+ 46 - 6
src/pages/list/filter/field/index.jsx

@@ -2,24 +2,46 @@ import {Input, Picker, Text, View} from '@tarojs/components';
 import {useMemo} from 'react';
 import {Switch} from '@antmjs/vantui';
 
-export default function Field({title, value, disabled, date, anomaly}) {
+export default function Field({
+  title,
+  value,
+  date,
+  anomaly,
+  startTime,
+  endTime,
+  onChange,
+  onStartChange,
+  onEndChange,
+}) {
   const Info = useMemo(
     function () {
       if (date) {
         return (
           <View className='flex justify-between'>
-            <Picker mode='date' className='w-[48%]'>
+            <Picker
+              mode='date'
+              className='w-[48%]'
+              value={startTime}
+              onChange={e => onStartChange(e.detail.value)}
+            >
               <Input
                 className='border-0 border-b border-solid border-gray-200 mt-1 py-1'
                 placeholder={`请选择开始时间`}
                 disabled
+                value={startTime}
               />
             </Picker>
-            <Picker mode='date' className='w-[48%]'>
+            <Picker
+              mode='date'
+              className='w-[48%]'
+              value={endTime}
+              onChange={e => onEndChange(e.detail.value)}
+            >
               <Input
                 className='border-0 border-b border-solid border-gray-200 mt-1 py-1'
                 placeholder={`请选择结束时间`}
                 disabled
+                value={endTime}
               />
             </Picker>
           </View>
@@ -27,7 +49,15 @@ export default function Field({title, value, disabled, date, anomaly}) {
       }
 
       if (anomaly) {
-        return <Switch size='24px' className='mt-1' activeColor='#58C6EA' />;
+        return (
+          <Switch
+            size='24px'
+            className='mt-1'
+            activeColor='#58C6EA'
+            checked={value}
+            onChange={e => onChange(e.detail)}
+          />
+        );
       }
 
       return (
@@ -35,11 +65,21 @@ export default function Field({title, value, disabled, date, anomaly}) {
           className='border-0 border-b border-solid border-gray-200 mt-1 py-1'
           placeholder={`请输入${title}`}
           value={value}
-          disabled={disabled}
+          onChange={e => onChange(e.detail.value)}
         />
       );
     },
-    [anomaly, date, disabled, title, value],
+    [
+      anomaly,
+      date,
+      endTime,
+      onChange,
+      onEndChange,
+      onStartChange,
+      startTime,
+      title,
+      value,
+    ],
   );
 
   return (

+ 66 - 8
src/pages/list/filter/index.jsx

@@ -2,7 +2,33 @@ import {Button, Popup} from '@antmjs/vantui';
 import Field from './field';
 import {View} from '@tarojs/components';
 
-export default function Filter({visible, onClose}) {
+export default function Filter({
+  visible,
+  onClose,
+  track,
+  custom,
+  anomaly,
+  startTime,
+  endTime,
+  startTime2,
+  endTime2,
+  onChange,
+  onReset,
+  onConfirm,
+}) {
+  function reset() {
+    onReset();
+    setTimeout(function () {
+      onConfirm();
+      onClose();
+    }, 0);
+  }
+
+  function confirm() {
+    onConfirm();
+    onClose();
+  }
+
   return (
     <Popup
       show={visible}
@@ -12,17 +38,49 @@ export default function Filter({visible, onClose}) {
       round
       className='pt-8 px-4 !pb-6'
     >
-      <Field title='卡车号' />
-      <Field title='客户号' />
-      <Field title='发货时间' disabled date />
-      <Field title='收货时间' disabled date />
-      <Field title='异常订单' disabled anomaly />
+      <Field title='卡车号' value={track} onChange={onChange('track')} />
+      <Field title='客户号' value={custom} onChange={onChange('custom')} />
+      <Field
+        title='发货时间'
+        disabled
+        date
+        startTime={startTime}
+        endTime={endTime}
+        onStartChange={onChange('startTime')}
+        onEndChange={onChange('endTime')}
+      />
+      <Field
+        title='收货时间'
+        disabled
+        date
+        startTime={startTime2}
+        endTime={endTime2}
+        onStartChange={onChange('startTime2')}
+        onEndChange={onChange('endTime2')}
+      />
+      <Field
+        title='异常订单'
+        disabled
+        anomaly
+        value={anomaly}
+        onChange={onChange('anomaly')}
+      />
 
       <View className='flex justify-around mt-4'>
-        <Button round className='border border-solid border-gray-200 w-28'>
+        <Button
+          round
+          className='border border-solid border-gray-200 w-28'
+          onClick={reset}
+        >
           重置
         </Button>
-        <Button type='primary' round className='w-28' color='#58C6EA'>
+        <Button
+          type='primary'
+          round
+          className='w-28'
+          color='#58C6EA'
+          onClick={confirm}
+        >
           查询
         </Button>
       </View>

+ 29 - 11
src/pages/list/hooks.js

@@ -1,15 +1,33 @@
-import {previewMedia} from '@tarojs/taro';
+import {useState} from 'react';
 
-export function usePreview() {
-  return function (files) {
-    const sources = files.map(function (url) {
-      return {url};
-    });
+export function useField() {
+  const [fields, setFields] = useState({
+    track: '',
+    custom: '',
+    anomaly: false,
+    startTime: '',
+    endTime: '',
+    startTime2: '',
+    endTime2: '',
+  });
 
-    return function () {
-      previewMedia({
-        sources,
-      });
+  function onChange(key) {
+    return function (val) {
+      setFields(prev => ({...prev, [key]: val}));
     };
-  };
+  }
+
+  function reset() {
+    setFields({
+      track: '',
+      custom: '',
+      anomaly: false,
+      startTime: '',
+      endTime: '',
+      startTime2: '',
+      endTime2: '',
+    });
+  }
+
+  return [fields, {onChange, reset}];
 }

+ 48 - 77
src/pages/list/index.jsx

@@ -1,89 +1,53 @@
-import {TextGroup} from '@components';
-import {Image, Text, View} from '@tarojs/components';
+import {Image, View} from '@tarojs/components';
 import classNames from 'classnames';
-import {Button} from '@antmjs/vantui';
-import {usePreview} from './hooks';
 import filterIcon from '@assets/filter.svg';
 import Filter from './filter';
 import {useBoolean} from 'ahooks';
+import Item from './item';
+import {Refresh} from '@components';
+import {useField} from './hooks';
+import {useInfiniteFetch} from '@hooks';
+import {getList} from '@apis';
 
 export default function List() {
-  const onClick = usePreview();
   const [visible, {setFalse: onClose, setTrue: onShow}] = useBoolean();
+  const [fields, {onChange, reset}] = useField();
+  const [
+    {data, isFetching, isFetchingNextPage, hasNextPage, isEmpty},
+    {fetchNextPage, refresh},
+  ] = useInfiniteFetch({
+    key: [getList.name],
+    fn: getList,
+    limit: 5,
+    params: {
+      arrivalTime: fields.startTime,
+      arrivalTimes: fields.endTime,
+      finalTime: fields.startTime2,
+      finalTimes: fields.endTime2,
+      customer: fields.custom,
+      truckNo: fields.track,
+      anomaly: fields.anomaly ? '1' : '',
+    },
+  });
 
   return (
     <>
-      <View className='h-screen overflow-auto bg-gray-200 py-4'>
-        <View className='bg-white mx-4 rounded-lg px-3 py-4'>
-          <View
-            className={classNames(
-              'flex items-center overflow-hidden border-0 border-b border-gray-200 border-solid',
-              'pb-3',
-            )}
-          >
-            <Text className='flex-1 text-over text-[#333] mr-2 text-sm'>
-              TSDFSDFTSDF
-            </Text>
-            <View className='text-center w-16 h-7 leading-7 text-sm text-white bg-primary rounded-md'>
-              已送达
-            </View>
-          </View>
-
-          <View className='mt-4 overflow-hidden border-0 border-b border-dashed border-gray-200 pb-4'>
-            <View className='flex overflow-hidden text-sm text-gray-500'>
-              <Text className='flex-1 block text-over mr-2'>品号</Text>
-              <Text className='w-[7em]'>数量</Text>
-            </View>
-            <View className='flex overflow-hidden text-sm mt-2'>
-              <Text className='flex-1 block text-over mr-2'>12312</Text>
-              <Text className='w-[7em]'>9871876</Text>
-            </View>
-            <View className='flex overflow-hidden text-sm mt-2'>
-              <Text className='flex-1 block text-over mr-2'>12312</Text>
-              <Text className='w-[7em]'>9871876</Text>
-            </View>
-            <View className='flex overflow-hidden text-sm mt-2'>
-              <Text className='flex-1 block text-over mr-2'>12312</Text>
-              <Text className='w-[7em]'>9871876</Text>
-            </View>
-          </View>
-
-          <View className='mt-4'>
-            <TextGroup title='卡车号' content='123' className='!py-0' />
-            <TextGroup
-              title='客户编号'
-              content='123555'
-              className='!py-0 mt-2'
-            />
-            <TextGroup
-              title='发货时间'
-              content='2020-12-22'
-              className='!py-0 mt-2'
-            />
-            <TextGroup
-              title='送达时间'
-              content='2020-12-23'
-              className='!py-0 mt-2'
-            />
-          </View>
-
-          <View className='flex justify-end mt-2'>
-            <Button
-              round
-              className='!m-0 border border-solid border-gray-200'
-              onClick={onClick([
-                'https://picsum.photos/id/1/800/600',
-                'https://picsum.photos/id/2/800/600',
-                'https://picsum.photos/id/3/800/600',
-                'https://picsum.photos/id/4/800/600',
-                'https://picsum.photos/id/5/800/600',
-              ])}
-            >
-              查看图片
-            </Button>
-          </View>
-        </View>
-      </View>
+      <Refresh
+        className='h-screen bg-[#eee]'
+        background='#eee'
+        isRefreshing={isFetching && !isFetchingNextPage}
+        onRefresh={refresh}
+        isLoading={isFetchingNextPage}
+        onLoading={fetchNextPage}
+        noMore={!hasNextPage}
+        empty={isEmpty}
+      >
+        <View className='h-3' />
+        {(data?.pages ?? []).map(function (val) {
+          return <Item key={val.id} {...val} />;
+        })}
+        <View className='h-3' />
+      </Refresh>
 
       <View
         onClick={onShow}
@@ -95,7 +59,14 @@ export default function List() {
         <Image src={filterIcon} mode='widthFix' className='w-5' />
       </View>
 
-      <Filter visible={visible} onClose={onClose} />
+      <Filter
+        visible={visible}
+        onClose={onClose}
+        {...fields}
+        onChange={onChange}
+        onReset={reset}
+        onConfirm={refresh}
+      />
     </>
   );
 }

+ 15 - 0
src/pages/list/item/hooks.js

@@ -0,0 +1,15 @@
+import {previewMedia} from '@tarojs/taro';
+
+export function usePreview() {
+  return function (files) {
+    const sources = files.map(function (url) {
+      return {url};
+    });
+
+    return function () {
+      previewMedia({
+        sources,
+      });
+    };
+  };
+}

+ 104 - 0
src/pages/list/item/index.jsx

@@ -0,0 +1,104 @@
+import {usePreview} from './hooks';
+import {Button} from '@antmjs/vantui';
+import {View, Text} from '@tarojs/components';
+import classNames from 'classnames';
+import {TextGroup} from '@components';
+import dayjs from 'dayjs';
+
+export default function Item({
+  anomaly,
+  arrivalTime,
+  customer,
+  dataList,
+  finalTime,
+  scrq,
+  states,
+  truckNo,
+  note,
+  imgs,
+}) {
+  const onClick = usePreview();
+
+  return (
+    <View className='bg-white mx-4 rounded-lg px-3 py-4 mt-3 first:mt-0'>
+      <View
+        className={classNames(
+          'flex items-center overflow-hidden border-0 border-b border-gray-200 border-solid',
+          'pb-3',
+        )}
+      >
+        <Text className='flex-1 text-over text-[#333] mr-2 text-sm'>
+          {truckNo}-{customer}
+        </Text>
+        <View
+          className={classNames(
+            'text-center w-20 h-7 leading-7 text-sm text-white rounded-md',
+            {
+              'bg-primary': states === '1' && anomaly === '0',
+              'bg-red-500': states === '1' && anomaly === '1',
+              'bg-gray-400': states === '0',
+            },
+          )}
+        >
+          {states === '0'
+            ? '正在送货'
+            : anomaly === '1'
+            ? '订单异常'
+            : '已送达'}
+        </View>
+      </View>
+
+      <View className='mt-4 overflow-hidden border-0 border-b border-dashed border-gray-200 pb-4'>
+        <View className='flex overflow-hidden text-sm text-gray-500'>
+          <Text className='flex-1 block text-over mr-2'>品号</Text>
+          <Text className='w-[7em]'>数量</Text>
+        </View>
+        {dataList.map(function ({id, partNumber, qty}) {
+          return (
+            <View className='flex overflow-hidden text-sm mt-2' key={id}>
+              <Text className='flex-1 block text-over mr-2'>{partNumber}</Text>
+              <Text className='w-[7em]'>{qty}</Text>
+            </View>
+          );
+        })}
+      </View>
+
+      <View className='mt-4'>
+        <TextGroup title='卡车号' content={truckNo} className='!py-0' />
+        <TextGroup title='客户编号' content={customer} className='!py-0 mt-2' />
+        <TextGroup
+          title='发货时间'
+          content={dayjs(scrq).format('YYYY-MM-DD')}
+          className='!py-0 mt-2'
+        />
+        <TextGroup
+          title='预计送达'
+          content={arrivalTime}
+          className='!py-0 mt-2'
+        />
+        {finalTime ? (
+          <TextGroup
+            title='送达时间'
+            content={dayjs(finalTime).format('YYYY-MM-DD')}
+            className='!py-0 mt-2'
+          />
+        ) : null}
+        {anomaly === '1' && note.length > 0 ? (
+          <TextGroup title='异常信息' content={note} className='!py-0 mt-2' />
+        ) : null}
+      </View>
+
+      {imgs ? (
+        <View className='flex justify-end mt-2'>
+          <Button
+            round
+            className='!m-0 border border-solid border-gray-200'
+            onClick={onClick(imgs ? imgs.split(',') : [])}
+          >
+            查看图片
+          </Button>
+        </View>
+      ) : null}
+    </View>
+  );
+}

+ 3 - 1
src/pages/receive/exception/index.jsx

@@ -2,7 +2,7 @@ import {View, Text, Textarea} from '@tarojs/components';
 import {Switch} from '@antmjs/vantui';
 import classNames from 'classnames';
 
-export default function Exception({checked, onChange}) {
+export default function Exception({checked, onChange, note, onNoteChange}) {
   return (
     <>
       <View className='flex items-center'>
@@ -16,6 +16,8 @@ export default function Exception({checked, onChange}) {
       </View>
 
       <Textarea
+        value={note}
+        onInput={e => onNoteChange(e.detail.value)}
         className={classNames(
           'border border-solid border-gray-200 rounded-md mt-3 w-[calc(100%-32px)] p-4 text-sm',
           {hidden: !checked},

+ 128 - 8
src/pages/receive/hooks.js

@@ -1,20 +1,40 @@
-import {useState} from 'react';
-import {showModal, chooseMedia} from '@tarojs/taro';
+import {useState, useEffect} from 'react';
+import {
+  showModal,
+  chooseMedia,
+  showLoading,
+  hideLoading,
+  showToast,
+} from '@tarojs/taro';
+import {useMutation, useQuery} from '@tanstack/react-query';
+import {confirmDeliver, getInfo, uploadImg} from '@apis';
+import {useBoolean} from 'ahooks';
+import {useNavigate} from '@hooks';
 
 export function useUpload() {
   const [files, setFiles] = useState([]);
 
+  const {mutate} = useMutation({
+    mutationFn: uploadImg,
+    onMutate() {
+      showLoading({title: '正在提交图片', mask: true});
+    },
+    onSettled() {
+      hideLoading({noConflict: true});
+    },
+    onSuccess(data) {
+      data.code === '200' && setFiles(prev => [...prev, data.data.data]);
+    },
+  });
+
   function onAdd() {
     chooseMedia({
-      count: 9,
+      count: 1,
       mediaType: ['image'],
       sourceType: ['album', 'camera'],
       success(res) {
         const {tempFiles} = res;
-        setFiles(function (prev) {
-          const imgs = tempFiles.map(val => val.tempFilePath);
-          return [...prev, ...imgs];
-        });
+        mutate(tempFiles[0].tempFilePath);
       },
     });
   }
@@ -35,5 +55,105 @@ export function useUpload() {
     };
   }
 
-  return [files, {onAdd, onRemove}];
+  return [files, {onAdd, onRemove, setFiles}];
+}
+
+export function useAnomaly() {
+  const [anomaly, {set: setAnomaly}] = useBoolean();
+  const [note, setNote] = useState('');
+
+  useEffect(
+    function () {
+      !anomaly && setNote('');
+    },
+    [anomaly],
+  );
+
+  return [
+    {anomaly, note},
+    {setAnomaly, setNote},
+  ];
+}
+
+export function useSubmit({customerNo, truckNo, anomaly, note, imgs}) {
+  const {pop} = useNavigate();
+  const {mutate, isLoading} = useMutation({
+    mutationFn: confirmDeliver,
+    onSuccess({code}) {
+      if (code === '200') {
+        pop();
+        showToast({title: '收货成功', icon: 'success', mask: true});
+      }
+    },
+  });
+
+  function onSubmit() {
+    if (!customerNo || !truckNo) {
+      return showToast({title: '请先扫码', icon: 'error'});
+    }
+    if (anomaly && !note) {
+      return showToast({title: '请输入异常内容', icon: 'error'});
+    }
+    if (!imgs.length) {
+      return showToast({title: '请上传图片', icon: 'error'});
+    }
+
+    const imgUrls = imgs.join(',');
+
+    showModal({
+      title: '确认收货',
+      content: `你确认要对${truckNo}${customerNo}进行确认收货吗?`,
+      success() {
+        mutate({
+          customer: customerNo,
+          truckNo,
+          anomaly: anomaly ? '1' : '0',
+          note,
+          imgs: imgUrls,
+        });
+      },
+    });
+  }
+
+  return [isLoading, onSubmit];
+}
+
+export function useInfo(customerNo, truckNo, {setFiles, setAnomaly, setNote}) {
+  const no = customerNo + truckNo;
+
+  const {data} = useQuery({
+    queryKey: [getInfo.name, no],
+    enabled: Boolean(no),
+    async queryFn() {
+      showLoading({title: '正在获取发货单信息', mask: true});
+      const data = await getInfo(truckNo, customerNo);
+      hideLoading({noConflict: true});
+
+      if (data.code === '200') {
+        if (data.data.list.length) {
+          return data.data.list[0];
+        }
+
+        return null;
+      }
+
+      return null;
+    },
+  });
+
+  useEffect(
+    function () {
+      if (!data) {
+        setFiles([]);
+        setNote('');
+        setAnomaly(false);
+        return;
+      }
+
+      setFiles(data.imgs ? data.imgs.split(',') : []);
+      setAnomaly(data.anomaly === '1');
+      setNote(data?.note ?? '');
+    },
+    [data, setAnomaly, setFiles, setNote],
+  );
 }

+ 29 - 8
src/pages/receive/index.jsx

@@ -3,15 +3,23 @@ import icon from '@assets/goods/complate.svg';
 import {TextGroup} from '@components';
 import {useScanOrder} from '@hooks';
 import {Button} from '@antmjs/vantui';
-import {useUpload} from './hooks';
+import {useAnomaly, useInfo, useSubmit, useUpload} from './hooks';
 import Upload from './upload';
 import Exception from './exception';
-import {useBoolean} from 'ahooks';
+import {BTN_LOADING_SIZE} from '@utils';
 
 export default function Receive() {
   const [{goodsList, customerNo, truckNo}, onScan] = useScanOrder();
-  const [files, {onRemove, onAdd}] = useUpload();
-  const [checked, {set: onChange}] = useBoolean();
+  const [files, {onRemove, onAdd, setFiles}] = useUpload();
+  const [{anomaly, note}, {setAnomaly, setNote}] = useAnomaly();
+  const [isLoading, onSubmit] = useSubmit({
+    customerNo,
+    truckNo,
+    anomaly,
+    note,
+    imgs: files,
+  });
+  useInfo(customerNo, truckNo, {setFiles, setAnomaly, setNote});
 
   return (
     <View className='h-screen overflow-auto bg-gray-100 flex flex-col'>
@@ -40,7 +48,7 @@ export default function Receive() {
                   className='border-0 border-b border-dashed border-gray-100 last:border-none'
                 >
                   <TextGroup title='品号' content={no} />
-                  <TextGroup title='数量' content={Number(num)} />
+                  <TextGroup title='数量' content={num} />
                 </View>
               );
             })}
@@ -53,14 +61,27 @@ export default function Receive() {
       </View>
 
       <View className='mt-3 bg-white px-4 py-2 mb-10'>
-        <Exception checked={checked} onChange={onChange} />
+        <Exception
+          checked={anomaly}
+          onChange={setAnomaly}
+          note={note}
+          onNoteChange={setNote}
+        />
       </View>
 
       <View className='flex mt-auto justify-around pb-12'>
-        <Button className='w-36' onClick={onScan} round>
+        <Button disabled={isLoading} className='w-36' onClick={onScan} round>
           扫码
         </Button>
-        <Button type='primary' className='w-36' color='#58C6EA' round>
+        <Button
+          type='primary'
+          className='w-36'
+          color='#58C6EA'
+          round
+          onClick={onSubmit}
+          loading={isLoading}
+          loadingSize={BTN_LOADING_SIZE}
+        >
           确认收货
         </Button>
       </View>

+ 1 - 1
src/utils/constants.js

@@ -1,5 +1,5 @@
 /** 请求地址 */
-export const NETWORK_URL = 'https://3f0974b6.r8.vip.cpolar.cn';
+export const NETWORK_URL = 'https://2d8cec95.r8.vip.cpolar.cn';
 /** token存储 */
 export const USER_TOKEN_STORAGE = 'userToken';
 /** 按钮loading大小 */

+ 2 - 2
src/utils/resolving/index.js

@@ -20,14 +20,14 @@ export function resolving(code) {
 
   const [first, last] = code.split(' ');
   const truckNo = first.slice(0, 7);
-  const customerNo = first.slice(7);
+  const customerNo = first.slice(7, 15);
 
   // 排除掉一开始的两位数
   const goodsStr = last.slice(2);
   const goodsList = goodsStr.match(/(.{22})/g).map(function (el) {
     // 前15位是品号 后7位是数量
     const no = el.slice(0, 15),
-      num = el.slice(15);
+      num = Number(el.slice(15));
 
     return {no, num};
   });