電商后台管理系統的功能——商品管理模塊
商品管理概述
商品管理模塊用於維護電商平台的商品信息,包括商品的類型、參數、圖片、詳情等信息。 通過商品管理模塊可以實現商品的添加、修改、展示和刪除等功能。
1. 商品列表
在goods文件夾下創建List.vue組件
1.1 商品列表功能
- 實現商品列表布局效果
- 調用后台接口獲取商品列表數據
const { data: res } = await this.$http.get('goods', { params: this.queryInfo }) if (res.meta.status !== 200) { return this.$message.error('初始化商品列表失敗! ') } // 為商品列表賦值 this.goodsList = res.data.goods // 為總數量賦值 this.total = res.data.total
獲取到的數據中包含時間,但是所獲取的時間不是我們想要的格式:
因此我們需要實現時間過濾器功能:
在main.js入口文件中創建一個全局過濾器:所有的組件都可以使用
1.2 添加商品
點擊添加商品按鈕跳轉到添加商品的頁面
① 基本布局與分布條效果
需要使用step步驟條組件
- 添加商品基本布局
- 分布條組件用法
<el-steps :active="activeName-0" finish-status="success" align-center> <el-step title="基本信息"></el-step> <el-step title="商品參數"></el-step> <el-step title="商品屬性"></el-step> <el-step title="商品圖片"></el-step> <el-step title="商品內容"></el-step> <el-step title="完成"></el-step> </el-steps>
② 商品信息選項卡Tab布局效果
Tab 組件的基本使用
<el-tabs tab-position="left" v-model="activeName" :before-leave="beforeTabLeave"> <el-tab-pane label="基本信息" name="0"><!-- 基本信息面板 --></el-tab-pane> <el-tab-pane label="商品參數" name="1"><!-- 商品參數面板 --></el-tab-pane> <el-tab-pane label="商品屬性" name="2"><!-- 商品靜態屬性面板 --></el-tab-pane> <el-tab-pane label="商品圖片" name="3"><!-- 圖片上傳面板 --></el-tab-pane> <el-tab-pane label="商品內容" name="4"><!-- 商品描述面板 --></el-tab-pane> </el-tabs>
實現步驟條與tab欄標簽的數據聯動:對於步驟條來說,如果想要修改激活項的話,必須找到步驟條的active屬性,修改索引值;對於tab欄標簽,有一個v-model屬性,表示當前激活的面板,如果點擊的是第一個標簽欄,則將第一個面板的name屬性值賦值給v-model。因此,只需要將tab標簽欄的v-model屬性綁定的值與步驟條的active屬性綁定同樣的值,就可以實現數據聯動。
tab欄綁定的值是字符串類型的,但是步驟條中的active屬性綁定的是數值類型的,因此需要做一個轉換,將activeIndex轉成數值類型:
如果要將一個長得像數字一樣的字符串轉換為數值類型,只需要-0即可。
阻止標簽頁之間的切換:首先要監聽標簽頁的切換事件,在事件處理函數中判斷是否處於第一個頁簽,還要判斷選中的商品分類是否為三級分類。實際上就是判斷數組長度是否為3,如果不等於3,則阻止跳轉
③ 商品基本信息
我們將一個商品信息拆分成了五個panel面板,每個面板里面只維護着當前商品的部分數據,只有將所有面板的數據合並起來才是一個完整的商品數據,因此應該在五個panel面板的外面嵌套一層form表單。
- 商品基本信息表單布局
- 表單數據綁定
- 表單驗證
addFormRules: { goods_name: [{ required: true, message: '請填寫商品名稱', trigger: 'blur' }], goods_price: [{ required: true, message: '請填寫商品價格', trigger: 'blur' }], goods_weight: [{ required: true, message: '請填寫商品重量', trigger: 'blur' }], goods_number: [{ required: true, message: '請填寫商品數量', trigger: 'blur' }], goods_cat: [{ required: true, message: '請選擇商品分類', trigger: 'blur' }] }
使標簽與文本框不在同一行:
④ 商品分類信息
- 商品分類布局
- 商品分類數據加載
<el-cascader expand-trigger="hover" :options="cateList" :props="cascaderConfig" v-model="addForm.goods_cat" @change="handleCascaderChange"></el-cascader>
const { data: res } = await this.$http.get('categories') if (res.meta.status !== 200) { return this.$message.error('初始化商品分類失敗! ') } this.cateList = res.data
⑤ 商品動態參數
- 獲取商品動態參數數據
- 商品動態參數布局
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: 'many' } }) if (res.meta.status !== 200) return this.$message.error('獲取動態參數列表失敗! ') // 把動態參數中的每一項數據中的 attr_vals,都從字符串分割為數組 res.data.forEach(item => { item.attr_vals = item.attr_vals.length === 0 ? [] : item.attr_vals.split(' ') }) this.manyData = res.data
使用到復選框組件:
⑥ 商品靜態屬性
- 獲取商品靜態屬性數據
- 商品靜態屬性布局
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: 'only' } }) if (res.meta.status !== 200) { return this.$message.error('獲取動態參數列表失敗! ') } this.onlyData = res.data
⑦ 商品圖片上傳
圖片上傳upload組件基本使用
<el-upload action="http://47.96.21.88:8888/api/private/v1/upload" :headers="uploadHeaders" :on-preview="handlePreview" :on-remove="handleRemove" :on-success="handleSuccess" list-type="picture"> <el-button size="small" type="primary">點擊上傳</el-button> </el-upload>
注意,action不能直接寫action="upload",這樣的話表示圖片要上傳的是http://localhost:8080/#/goods/add/upload
我們后台API接口不是8080,http://47.96.21.88:8888/api/private/v1/才是后台服務器的請求路徑
點擊上傳圖片按鈕,可以成功預覽圖片,但是實際上並沒有上傳成功,這是因為除了登錄接口之外的所有接口都是有權限的,在調用這些接口的時候提供一個token值,我們在main.js入口文件中添加了一個axios攔截器,為每一個請求掛載了一個Authorization字段,字段的值就是token。但是在調用upload組件上傳圖片的時候並沒有用到axios攔截器,這個組件內部自己封裝了一套Ajax,是沒有攜帶Authorization字段的
解決方法如下:
圖片預覽
實現圖片的預覽效果:圖片預覽實際上就是彈出一個對話框,只不過沒有取消和確定按鈕而已。
// 預覽圖片時候,觸發的方法 handlePreview(result) { this.previewImgSrc = result.response.data.url this.previewVisible = true }
圖片刪除
// 當移除圖片,會觸發這個方法 handleRemove(result) { // 根據 result.response.data.temp_path 從 addForm.pics 數組中,找到要刪除那個對象的索引值 const index = this.addForm.pics.findIndex(item => item.pic === result.response.data.tmp_path) // 根據索引刪除對應的圖片信息對象 this.addForm.pics.splice(index, 1) }
完成圖片上傳
// 圖片上傳成功 handleSuccess(result) { if (result.meta.status === 200) { // 把上傳成功后,圖片的臨時路徑,保存到 addForm.pics 數組中,作為對象來保存 this.addForm.pics.push({ pic: result.data.tmp_path }) } }
⑧ 商品詳情
富文本編輯器基本使用
// 安裝vue-quill-editor npm install vue-quill-editor -S
或者使用vue ui圖形化界面安裝:
import VueQuillEditor from 'vue-quill-editor‘
Vue.use(VueQuillEditor)
<quill-editor v-model="addForm.goods_introduce"></quill-editor>
⑨ 完成商品添加
- 處理商品相關數據格式
- 調用接口完成商品添加
// 先處理好商品相關的數據格式,然后再提交 const newForm = _.cloneDeep(this.addForm) newForm.goods_cat = newForm.goods_cat.join(',') // 到此位置,商品相關數據已經准備好,可以提交了 const { data: res } = await this.$http.post('goods', newForm) if (res.meta.status !== 201) return this.$message.error(res.meta.msg) this.$message.success('添加商品成功! ') // 跳轉到商品列表頁 this.$router.push('/goods/list')
實現商品內容的添加,在添加之前要對整個表單進行數據的校驗,檢驗表單的數據是否合法
上面的一行代碼執行后會報錯,這是因為在基本信息面板中的級聯選擇器必須雙向綁定要一個數組上,但是在提交商品信息時,執行add()函數后,就將goods_cat轉換為了字符串,這樣就會報錯
如何解決呢?可以在拼接操作goods_cat之前將addForm對象做一下深拷貝(將這個對象原封不動的拷貝一份出來,兩個對象之間互不相干)
可以使用第三方模塊lodash的cloneDeep(obj)方法:官網:https://www.lodashjs.com/
有兩個bug還未解決:
① 當不選擇商品價格、商品重量和商品數量時,后台服務器會報錯,顯示必須輸入這些值,但實際上這些屬性是有一個默認值的0,為什么會顯示為空數據呢?
② 添加商品名稱不能一致,但是刪除了該商品之后,再添加一個與刪除商品名稱一致的商品卻不行,這是為什么呢?后台數據庫中沒有刪數據嗎?知道原因了,sp_goods_pics這個表中已經刪除該商品,但是在sp_goods表中卻仍然保留着該商品的id值,是后台數據接口的問題。
1.3 提交商品列表功能到碼雲
- 使用git checkout -b goods_list創建一個新分支並切換到該分支上
- 使用git branch查看當前所處的分支,所有代碼的修改也一起被切換到了goods_list子分支中
- 使用git status命令檢查當前分支的代碼狀態
- 使用git add .命令統一增加到暫存區
- 使用git commit -m "完成商品列表功能的開發"命令把goods_list分支提交到本地倉庫中
本地goods_list的代碼就是最新的了
使用git push -u origin goods_list命令把本地的goods_list分支推送到雲端中
master是主分支,但是它的代碼還是舊的,應該立即把所有的代碼合並到主分支上
- 使用git checkout master命令切換到主分支
- 使用git merge goods_list命令從主分支上把goods_list分支上的代碼合並過來
- 使用git push命令將本地的master分支的代碼推送到雲端,這樣master分支上的代碼也是最新的了
2. 分類管理
在components目錄下創建goods文件夾,然后創建cate.vue組件
2.1 商品分類概述
商品分類用於在購物時,快速找到所要購買的商品,可以通過電商平台主頁直觀的看到。
2.2 商品分類列表
基本布局與數據獲取
- 實現基本布局
- 實現分類列表數據加載
const { data: res } = await this.$http.get('categories', { params: this.queryInfo }) if (res.meta.status !== 200) { return this.$message.error('獲取商品分類失敗! ') } this.cateList = res.data.result this.total = res.data.total
2.3 樹形表格
將商品分類的數據渲染為樹形表格,但elementUI中並沒有提供這樣的組件,我們需要依賴第三方的插件來實現樹形的table表格:
① 第三方樹形表格的基本使用
npm i vue-table-with-tree-grid -S
或者使用vue ui圖形化界面來安裝依賴包:
- 基本使用
我們使用手動注冊的方式:在main.js入口文件中導入,ZkTable是要注冊的插件,ZkTable.name是注冊的名稱
import Vue from 'vue' import ZkTable from 'vue-table-with-tree-grid' Vue.use(ZkTable)
② 實現分類樹形列表
- 實現樹形列表布局並進行數據填充
- 自定義表格列
<tree-table :data="cateList" :columns="columns" border :selection-type="false" :expand-type="false" show-index index-text="#" class="tree-table"> <!-- 操作的模板列 --> <!-- 排序的模板列 --> <!-- 是否有效的模板列 --> <template slot="isok" slot-scope="scope"> <i class="el-icon-success" style="color:#20B2AA;" v-if="scope.row.cat_deleted === false"></i> <i class="el-icon-error" style="color:#F92672;" v-else></i> </template> </tree-table>
data屬性是樹形表格的數據源,column是每列的信息,是個數組
隱藏掉樹形表格前面的復選框和展開行的效果
需要在前面加一個索引列:
自定義索引列的標題:
設置表格列與列之間的邊框:
實現'是否有效'這一列:需要用到作用域插槽
先將這一列用type指定為template,再使用template屬性來指定具體的作用域插槽。在表格的中間用一個template來定義這個插槽,然后使用scope屬性取個名字來與column中的template名稱對應,並且通過slot-scope屬性來接收這一行的數據
2.4 分頁功能
- 實現分頁組件效果
- 分頁組件數據處理
<!-- 分頁區域 --> <el-pagination @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-size="queryInfo.pagesize" layout="total, prev, pager, next, jumper" :total="total"> </el-pagination>
2.5 添加分類
① 實現分類樹形列表
- 實現添加分類對話框布局
- 控制對話框顯示和隱藏
實現添加分類按鈕的功能:
父級分類是沒有驗證規則的,如果為空,則是將當前的分類名稱添加為一級分類,如果選擇了一個(大家電),則是二級分類(大家電的子分類),如果選擇了兩個,則是三級分類。只需要獲取前兩級的分類,即請求的type設為2,不需要pagenum和pagesize,一次性的將前兩級分類獲取過來
<el-dialog title="添加分類" :visible.sync="addDialogVisible" width="50%" @close="resetForm"> <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px"> <el-form-item label="分類名稱: " prop="cat_name"> <el-input v-model="addForm.cat_name"></el-input> </el-form-item> <el-form-item label="父級分類: "> <!-- 分類菜單 --> </el-form-item> </el-form> </el-dialog>
② 實現分類級聯菜單效果
- 實現級聯菜單效果
- 級聯菜單數據加載與填充
級聯選擇器Cascader::options指的是級聯選擇器的數據源,props屬性是級聯選擇器的配置選項,你選中的值、展示的值、以及通過哪個屬性來實現父子嵌套都要通過props預先進行配置
<el-cascader expand-trigger="hover" :options="parentCateList" :props="cascaderConfig" v-model="selectedCateList" @change="handleChange" change-on-select clearable> </el-cascader>
// 先獲取所有父級分類的數據列表 const { data: res } = await this.$http.get('categories', { params: { type: 2 } }) if (res.meta.status !== 200) return this.$message.error('獲取父級分類失敗! ') // 把父級分類數據,掛載到data中 this.parentCateList = res.data
③ 控制父級分類的選擇
父級分類選擇時,獲取對應的分類 id。
handleChange() { if (this.selectedCateList.length === 0) { // 證明沒有選中任何父級分類 this.addForm.cat_pid = 0 this.addForm.cat_level = 0 } else { // 選中父級分類 this.addForm.cat_pid = this.selectedCateList[this.selectedCateList.length - 1] // 設置分類等級 this.addForm.cat_level = this.selectedCateList.length } }
④ 完成分類添加
將分類名稱、分類等級和父分類 id 提交到后台,完成分類添加。
const { data: res } = await this.$http.post('categories', this.addForm) if (res.meta.status !== 201) { return this.$message.error('添加分類失敗! ') } this.$message.success('添加分類成功! ')
2.6 提交分類管理功能到碼雲
- 使用git checkout -b goods_cate創建一個新分支並切換到該分支上
- 使用git branch查看當前所處的分支,所有代碼的修改也一起被切換到了goods_cate子分支中
- 使用git status命令檢查當前分支的代碼狀態
- 使用git add .命令統一增加到暫存區
- 使用git commit -m "完成分類管理功能的開發"命令把goods_cate分支提交到本地倉庫中
本地goods_cate的代碼就是最新的了
使用git push -u origin goods_cate命令把本地的goods_cate分支推送到雲端中
master是主分支,但是它的代碼還是舊的,應該立即把所有的代碼合並到主分支上
- 使用git checkout master命令切換到主分支
- 使用git merge goods_cate命令從主分支上把goods_cate分支上的代碼合並過來
- 使用git push命令將本地的master分支的代碼推送到雲端,這樣master分支上的代碼也是最新的了
3. 參數管理
在goods文件夾下創建Params.vue組件
3.1 參數管理概述
商品參數用於顯示商品的固定的特征信息,可以通過電商平台商品詳情頁面直觀的看到。
動態參數:
靜態屬性:
3.2 商品分類選擇
① 選擇商品分類
- 頁面基本布局
- 加載商品分類數據
- 實現商品分類的級聯選擇效果
// 獲取所有商品的分類列表 async getAllCateList() { const { data: res } = await this.$http.get('categories') if (res.meta.status !== 200) { return this.$message.error('獲取商品分類列表失敗! ') } this.cateList = res.data }
② 控制級聯菜單分類選擇
- 只允許選擇三級分類
- 通過計算屬性的方式獲取分類 ID
級聯選擇器默認只能選擇選擇框的最后一項,但是我們只允許選擇三級分類,如果只有二級分類的選項,其實是可以選中二級分類的。那如何解決呢?
我們只需要監聽選擇器的change事件,在change事件中只需要判斷選中項的長度,如果長度不等於3,則說明選擇的不是三級分類,只需要將v-model綁定的數組(當前選中的值)清空即可,對應的選擇器中的選擇項也會被清空。
cascaderChanged() { if (this.selectedCateList.length !== 3) { // 沒有選中三級分類,把分類重置為空 this.selectedCateList = [] this.manyTableData = [] this.onlyTableData = [] } else { // 選中了三級分類后,獲取該分類對應的參數列表數據 this.getParamsList() } }
cateId() { if (this.selectedCateList.length === 3) { return this.selectedCateList[this.selectedCateList.length - 1] } else { return null } }
如果在級聯框中選中了一項,則啟用tab標簽欄,否則,tab標簽欄處於禁用狀態
如何實現呢?定義一個計算屬性,根據級聯選擇數組的長度返回一個bool值,從而控制按鈕的啟用和禁用
3.3 實現參數列表
① 根據選擇的商品分類加載對應的參數數據
- 參數列表布局
- 根據分類 id 加載參數列表數據
// 獲取所有商品的分類列表 const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: this.activeName } })
級聯選擇框發生變化時需要獲取參數值,tab標簽欄發生點擊切換時,也需要獲取參數值,所以需要提取出來單獨封裝為一個函數
添加參數和添加屬性共用一個彈出對話框:也通過計算屬性綁定
② 處理標簽數據格式
將字符串形式的數據分隔為數組。
res.data.forEach(item => { // 把字符串的可選項,分割為數組,重新賦值給 attr_vals item.attr_vals = item.attr_vals.length > 0 ? item.attr_vals.split(‘, ') : [] })
實現展開列里面的數據渲染:
在getParamsData()函數中賦值之前添加下面這一段代碼
出現一個bug:當我們新增一個參數后,展開列中應該沒有內容,但是卻有一個空的標簽:
這是因為,如果attr_vals為空,則用split按空格分割之后得到的數組是包含一項的,就是空字符串,所以就渲染出了一個空白的el-tag
如何解決呢?過濾掉空字符串
③ 控制添加標簽文本框的顯示
$nextTick 的執行時機:DOM 更新完畢之后
<el-button size="small" v-else @click="showTagInput(scope.row)">+ New Tag</el-button>
showTagInput(row) { row.tagInputVisible = true // 當我們修改了 data 中 tagInputVisible 的值以后,如果要操作文本框,必須等頁面重新渲染完畢之后才可以, // 所以,必須把操作文本框的代碼放到 $nextTick 中,當作回調去執行( $nextTick 的執行時機,是在DOM 更新完畢之后) this.$nextTick(() => { this.$refs.saveTagInput.$refs.input.focus() }) }
實現添加按鈕與輸入文本框之間的切換顯示:使用v-if和v-else實現
出現一個bug:展開兩項,然后點擊其中一項的輸入new tag按鈕,但是兩項都變成了輸入文本框,並且輸入其中一個文本框,另一個會有聯動的效果
這是因為所有的input綁定的都是同一個if判斷條件,數據雙向綁定的也是同一個值。每渲染一個展開行,都公用同一個value值和bool值
如何解決呢?只需要給每一行的數據單獨提供個value值和bool值
把inputVisible設置為true之后,頁面上的元素還沒有被重新渲染,也就是說頁面上展示的還是button按鈕,並不是文本輸入框。所以要等到渲染完成之后,才去執行。
優化:當文本框中輸入的內容后,失去焦點之后,再次點擊按鈕顯示文本框,之前輸入文本框中的內容並沒有被清空。但是我們想要清除不合法的輸入內容,比如說一串空格
出現一個bug:當我們選中一個三級分類之后,下面會顯示相應的參數,然后當我們再切換選擇為一個二級分類,但是不允許我們選擇,所以級聯選擇框會自動被清空,但是下面的參數卻還在。這樣如果我們添加新tag時,會因為沒有商品的id值而報錯
解決方法:
④ 實現標簽動態添加的文本框控制邏輯
- 控制標簽輸入域的顯示和隱藏
- 對輸入的內容進行數據綁定
res.data.forEach(item => { // 把字符串的可選項,分割為數組,重新賦值給 attr_vals item.attr_vals = item.attr_vals.length > 0 ? item.attr_vals.split(‘, ') : [] // 為每個數據行,添加自己的 tagInputVisible ,從而控制自己展開行中的輸入框的顯示與隱藏 item.tagInputVisible = false // 把文本框中輸入的值,雙向綁定到 item.tagInputValue 上 item.tagInputValue = '' })
⑤ 實現標簽的添加和刪除操作
添加標簽和刪除標簽使用的是同一個接口,參數是一樣的。
const { data: res } = await this.$http.put( `categories/${this.cateId}/attributes/${row.attr_id}`, { attr_name: row.attr_name, attr_sel: row.attr_sel, attr_vals: row.attr_vals.join(' ') } ) if (res.meta.status !== 200) { return this.$message.error('更新參數項失敗! ') } this.$message.success('更新參數項成功! ')
3.4 實現動態參數與靜態屬性添加
- 動態參數與靜態屬性表單重用
- 添加動態參數與靜態屬性使用的是同一個接口,參數是一樣的
const { data: res } = await this.$http.post(`categories/${this.cateId}/attributes`, { // 參數的名稱 attr_name: this.addForm.attr_name, // 參數類型 many only attr_sel: this.activeName }) if (res.meta.status !== 201) return this.$message.error('添加參數失敗! ') this.$message.success('添加參數成功! ')
3.5 提交分類參數功能到碼雲
- 使用git checkout -b goods_params創建一個新分支並切換到該分支上
- 使用git branch查看當前所處的分支,所有代碼的修改也一起被切換到了goods_params子分支中
- 使用git status命令檢查當前分支的代碼狀態
- 使用git add .命令統一增加到暫存區
- 使用git commit -m "完成分類參數功能的開發"命令把goods_params分支提交到本地倉庫中
本地goods_params的代碼就是最新的了
使用git push -u origin goods_params命令把本地的goods_params分支推送到雲端中
master是主分支,但是它的代碼還是舊的,應該立即把所有的代碼合並到主分支上
- 使用git checkout master命令切換到主分支
- 使用git merge goods_params命令從主分支上把goods_params分支上的代碼合並過來
- 使用git push命令將本地的master分支的代碼推送到雲端,這樣master分支上的代碼也是最新的了
代碼地址:https://github.com/Emliy-zcy/Backstage-Management-System-Based-on-vue