密選分享(第一版)

背景

以XXX男裝top店與其檔口,供貨商的交流痛點爲出發,解決選款,上架,交流等問題。css

重點功能

  1. 供應商編輯商品發送到分銷商
  2. 選款人員權限與協同管理
  3. 分銷商 GOOD PASS 選款
  4. 商品圖片下載管理
  5. 淘寶天貓上架

操做流程

供應鏈APP提報商品 --> 密選供應商編輯併發送到密選 --> 密選選款並直接淘寶天貓上架html

技術選型

框架

UI框架:react
UI組件庫:Ant Design
HTTP庫:Axios
APP狀態管理:Redux
代碼風格:ESLint Prettier
css預處理:Less
打包工具:webpack前端

第三方插件

七牛圖片上傳:qiniu-js
react添加多個className:classNames
複製功能:clipboard
文件下載保存:file-saver
文件壓縮:jszip
時間處理:moment
......node

業務插件

淘寶天貓上架:mx-quick-shelfreact

項目文件結構

項目目錄

├── /config/         # 配置相關:devServer配置,env多環境的配置,項目文件夾絕對路徑,antd定製主題
├── /dist/           # 輸出目錄
├── /public/         # html模板icon文件
├── /scripts/        # 構建配置
├── /src/            # 業務邏輯代碼
│ ├── /assets/       # 項目靜態資源文件
│ ├── /common/       # 路由表及頁面組件loader&菜單管理
│ ├── /components/   # UI組件及UI相關方法
│ │ ├── /Authorized/ # 權限組件&權限管理 
│ ├── /global/       # 全局狀態管理
│ ├── /layouts/      # 項目佈局組件
│ ├── /pages/        # 項目頁面組件
│ ├── /styles/       # 全局樣式
│ ├── /utils/        # 工具函數
│ ├── App.js         # 項目入口組件
│ ├── index.js       # 項目入口文件,掛載組件,初始化 
│ ├── reducers.js    # 合併combine reducers
│ └── store.js       # compose middlewares & create store
├── .gitignore       # git 配置
├── .babelrc         # babel-loader 配置
├── .eslintrc        # Eslint 配置
├── .prettierrc      # Prettierrc 配置
└── package.json     # 項目信息

頁面目錄

├── /pages/               # 項目頁面組件
│ ├── /Home/              # 首頁
│ │ ├── /components/      # 頁面私有組件
│ │ ├── /view/            # 視圖組件
│ │ │ ├── /index.js       # dom與控制
│ │ │ ├── /index.less     # 樣式
│ │ ├── /actions.js       # reduex action
│ │ ├── /actionTypes.js   # reduex action type
│ │ ├── /index.js         # 入口
│ │ ├── /reducer.js       # reduex reducer
│ │ └── /service.js       # http請求

版本控制與發佈流程

版本控制管理

clipboard.png

分支規範(git)

  • feature: 新功能製做區 測試問題處理區
  • develop: 測試在跑的最新代碼,迭代下一個版本
  • master: 正式在跑的最新代碼
  • fix: 快速解決正式問題
  • release: 針對某個版本處理

commit message 規範(git)

  • feat: 新功能
  • fix: 修改bug
  • docs: 文檔更新
  • style: 格式(css)
  • refactor: 重構 (密選選款卡片的變動)
  • test: 測試代碼
  • chore: 構建過程或輔助工具的變更

發佈上線流程

  1. 把須要上線的代碼合併develop。
  2. develop分支代碼build,發佈到測試服務器,交由測試,產品驗收。
  3. 解決bug後,再由測試,產品再驗收。
  4. develop合併master後再build,發佈到正式服務器,交由測試,產品使用。
  5. 正式緊急bug,fix直接修改測試好後合併master,小版本上線。不重要的下個版本再處理
  6. 寫版本日誌記錄,master提交代碼,添加git版本標籤,方便追溯。

版本日誌記錄

## [3.1.0] - 2019-09-19
### Added
- 淘寶上架

### Changed
- 供應商標題與計劃標題代碼整合

### Fixed
- 搜索調用接口屢次觸發

### Removed
- 刪除圖片放大功能

### Deprecated
- 不建議使用,將來會刪掉

### Security
- 安全相關的bug

部分功能展現與講解

頁面狀態保持

頁面說明

在頁面上選擇狀態,選擇子類,刷新頁面保持webpack

代碼說明

// 進入頁面 獲取分銷商分組
    componentDidMount() {
        this.getSendList({ redirect: true })  
    }
    
    // 從新渲染的時候調用,把路由中的值賦值到state
    static getDerivedStateFromProps(nextProps, prevState) {
        const { match } = nextProps
        const { queryType, dayCollectId } = match.params

        if (prevState.queryType !== Number(queryType)) {
            return {
                ...prevState,
                queryType: Number(queryType || 0)
            }
        }

        if (prevState.dayCollectId !== dayCollectId) {
            return {
                ...prevState,
                dayCollectId: dayCollectId || ''
            }
        }

        return null
    }

    // 有值改變的時候執行
    async componentDidUpdate(prevProps, prevState) {
        const { dayCollectId, queryType } = this.state
        
        // 從新獲取分銷商分組
        if (queryType !== prevState.queryType) {
            this.getSendList({
                redirect: true  // 是否重定向
            })
        }
        
        //  
        if (dayCollectId !== prevState.dayCollectId && dayCollectId) {
            this.setSendInfo({
                redirect: false
            })

            this.getSendItemInfo()
        }
    }
    
    // 獲取分銷商分組
    async getSendList(option) {
        const { pageNum, pageSize, queryType, vagueSearch } = this.state

        await getSendList({
            vagueSearch,
            pageNum,
            pageSize,
            queryType
        }).then(json => {
            const { success, data: sendList } = json

            if (success) {
                this.setState(
                    {
                        sendList
                    },
                    () => {
                        this.setSendInfo(option)
                    }
                )
            }
        })
    }
    
    // 設置分銷商詳情
    setSendInfo(option) {
        const { history } = this.props
        const { sendList, queryType, dayCollectId } = this.state

        option = Object.assign(
            {
                redirect: false,
                resume: true
            },
            option || {}
        )

        if (sendList.length < 1) {
            return
        }

        const existed = sendList.find(item => {
            return item.id === dayCollectId
        })
        const selectSendItem = option.resume && existed ? existed : sendList[0]

        this.setState({
            spuItem: selectSendItem,
            sendItemList: []
        })

        option.redirect && history.replace(`/goods/distributors_sent/${queryType}/${selectSendItem.id}`)
    }
    
    // 獲取分銷商詳情
    getSendItemInfo(){
        // ... 
    }

店鋪受權類目

頁面說明

clipboard.png

代碼展現

// CategoryModal.js 
<div style={styles.categoryChooseContent}>
  {categoryData.map((item, index) => (
    <div key={item.id} style={styles.categoryItem}>
        <CategoryItem
            loading={item.loading}
            categorys={item.categorys}
            onChoose={item => {
                handleCategorChoose(item, index)
            }}
        />
    </div>
))}

// CategoryItem.js
function CategoryItem({loading = false, categorys, onChoose }) {
        const [categorysList, setCategorysList] = useState([])
        const [selected, setSelected] = useState('')
        
        // 在load之後渲染類目。
        useEffect(() => {
            if (!loading) {
                setSelected('')
                setCategorysList(categorys)
            } else {
                setCategorysList([])
            }
        }, [loading])
    
        // 選中類目
        const handleChoose = item => {
            setSelected(item.cid)
            onChoose(item)
        }
        
        // 搜索 
        const handleChange = e => {
            const { value } = e.target
    
            delay(() => {
                const filterList = categorys.filter(item => item.name.indexOf(value) >= 0)
                setCategorysList(filterList)
            }, 800)
        }
    
        return (
            <Spin spinning={loading}>
                <div style={styles.header}>
                        <Search placeholder="名稱/拼音字母" onChange={handleChange} />
                    </div>
                    <div style={styles.body}>
                        {categorysList && categorysList.length > 0 && (
                            <ul>
                                {categorysList.map(item => (
                                    <li
                                        key={item.cid}
                                        className={item.cid === selected ? 'qs_category_selected' : 'qs_category'}
                                        style={styles.category}
                                        onClick={() => handleChoose(item)}>
                                        <span>{item.name}</span>
                                        {item.isParent ? <Icon type="right" style={styles.iconRight} /> : null}
                                    </li>
                                ))}
                            </ul>
                        )}
                    </div>
            </Spin>
        )
    }

踩過的坑

mx-quick-shelf 業務插件引入問題

mx-quick-shelf

➜ [mx-quick-shelf] npm linkios

secret-goods-pc

➜ [secret-goods-pc] npm link mx-quick-shelf 原則上這步就行可是須要:git

➜ [mx-quick-shelf] npm link ../../work/secret-goods-pc/node_modules/react
➜ [mx-quick-shelf] npm link ../../work/secret-goods-pc/node_modules/react-domweb

如今解決辦法webpack
   alias: {
     react: utils.resolve('./node_modules/react')
}

antd form 表單問題

clipboard.png

<Form.Item label={_attributes.name} {...itemLayout} {...extraProps}>
    {getFieldDecorator(`${_attributes.id}`, {
        rules: _rules,
        initialValue: _value,
        validateFirst: true
    })(
        formItemDom()
    )}
</Form.Item>

坑點

要實現 內部驗證與外部驗證,最好的方法是 form 嵌套form。剛開始的方法:npm

  • form 標籤不能嵌套,子組件外部不要包form標籤,尷尬
  • form傳遞到子組件,id使用 XXX.[x].xxx 方法:有時候有些數據須要整合之後的驗證

待優化提高點

  1. 添加前端日誌記錄,方便線上bug追蹤
  2. 部署自動化,防止項目上線錯誤的發生
相關文章
相關標籤/搜索