Vue2 基础八电商后台管理项目——中

代码下载

商品分类页

新商品分类组件 goods/Cate.vue,在router.js中导入子级路由组件 Cate.vue,并设置路由规则。

绘制商品分类基本结构

在Cate.vue组件中添加面包屑导航以及卡片视图中的添加分类按钮:

<template>
    <div>
        <!-- 面包屑导航 -->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>商品分类</el-breadcrumb-item>
        </el-breadcrumb>

        <!-- 卡片 -->
        <el-card>
            <!-- 添加 -->
            <el-button type="primary" @click="showAddCateDialog">添加分类</el-button>

            <!-- 分类列表 -->
            <tree-table class="tree-table" :data="catelist" :columns="columns" :selection-type = "false" :expand-type="false" :show-index="true" :index-text="'#'" border :show-row-hover="false">
                <template slot="isOk" slot-scope="scope">
                    <i class="el-icon-error" style="color: red;" v-if="scope.row.cat_deleted"></i>
                    <i class="el-icon-success" style="color: lightgreen;" v-else></i>
                </template>
                <template slot="order" slot-scope="scope">
                    <el-tag v-if="scope.row.cat_level === 0">一级</el-tag>
                    <el-tag type="success" v-else-if="scope.row.cat_level === 1">二级</el-tag>
                    <el-tag type="warning" v-else>三级</el-tag>
                </template>
                <template slot="opt" slot-scope="scope">
                    <el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button>
                    <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeCateById(scope.row.cat_id)">删除</el-button>
                </template>
            </tree-table>

            <!-- 分页 -->
            <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="queryInfo.pagenum"
            :page-sizes="[2, 3, 5, 10]"
            :page-size="queryInfo.pagesize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="total">
            </el-pagination>
        </el-card>

        <!-- 添加分类对话框 -->
        <el-dialog title="添加商品分类" :visible.sync="addCateDialogVisible" width="50%" @close="addCateDialogClosed">
            <el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRef" label-width="100px">
                <el-form-item label="分类名称:" prop="cat_name">
                    <el-input v-model="addCateForm.cat_name"></el-input>
                </el-form-item>
                <el-form-item label="父级分类:">
                    <el-cascader v-model="selectedKeys" :options="parentCateList" :props="cascaderProps" @change="parentCateChange" clearable></el-cascader>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="addCateDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="addCate">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

主要功能实现

<script>
export default {
    data() {
        return {
            catelist: [], 
            queryInfo: {
                type: 3,
                pagenum: 1,
                pagesize: 5
            },
            total: 0,
            columns: [
                {
                    label: '分类名称',
                    prop: 'cat_name'
                },
                {
                    label: '是否有效',
                    // 列类型,可选值有 'template'(自定义列模板)
                    type: 'template',
                    // 列类型为 'template'(自定义列模板) 时,对应的作用域插槽(它可以获取到 row, rowIndex, column, columnIndex)名称
                    template: 'isOk'
                },
                {
                    label: '排序',
                    type: 'template',
                    template: 'order'
                },
                {
                    label: '操作',
                    type: 'template',
                    template: 'opt'
                }
            ],
            addCateDialogVisible: false,
            addCateForm: {
                cat_pid: 0,
                cat_name: '',
                cat_level: 0
            },
            // 添加商品分类表单验证
            addCateFormRules: {
                cat_name: [
                    { required: true, message: '请输入分类名称', trigger: 'blur' }
                ]
            },
            parentCateList: [],
            cascaderProps: {
                expandTrigger: 'hover',
                checkStrictly: true,
                value: 'cat_id',
                label: 'cat_name',
                children: 'children'
            },
            selectedKeys: []
        }
    },
    created() {
        this.getCateList()
    },
    methods: {
        async getCateList() {
            const { data: res } = await this.$http.get('categories', { params: this.queryInfo })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.catelist = res.data.result
            this.total = res.data.total
            this.$msg.success('商品分类获取成功')
        },
        handleSizeChange(size) {
            console.log('size: ', size);
            this.queryInfo.pagesize = size
            const maxN = parseInt(this.total / this.queryInfo.pagesize + '') + (this.total % this.queryInfo.pagesize > 0 ? 1 : 0)
            this.queryInfo.pagenum = this.queryInfo.pagenum > maxN ? maxN : this.queryInfo.pagenum
            this.getCateList()
        },
        handleCurrentChange(page) {
            console.log('page: ', page);
            this.queryInfo.pagenum = page
            this.getCateList()
        },
        // 展示添加商品分类对话框
        async showAddCateDialog() {
            // 获取父级分类
            const { data: res } = await this.$http.get('categories', { params: { type: 2 } })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.error)
            this.parentCateList = res.data
            this.addCateDialogVisible = true
        },
        // 关闭添加商品分类对话框
        addCateDialogClosed() {
            this.$refs.addCateFormRef.resetFields()
            this.addCateForm.cat_pid = 0
            this.addCateForm.cat_level = 0
            this.addCateForm.cat_name = ''
            this.selectedKeys = []
        },
        // 添加商品分类
        async addCate() {
            // 验证表单
            this.$refs.addFormRef.validate(async valid => {
                if (!valid) return this.$msg.error('请填写正确的分类名称')

                const { data: res } = await this.$http.post('categories', this.addCateForm)
                if (res.meta.status !== 201) return this.$msg.error(res.meta.msg)
                this.$msg.success('添加商品分类成功')
                this.getCateList()
                // 关闭对话框,重置数据
                this.addCateDialogVisible = false
            })
        },
        parentCateChange(v) {
            console.log('change: ', v);
            // 处理父分类id和分类级别
            if (this.selectedKeys.length > 0) {
                this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]
                this.addCateForm.cat_level = this.selectedKeys.length
            } else {
                this.addCateForm.cat_pid = 0
                this.addCateForm.cat_level = 0
            }
        },
        async removeCateById(uid) {
            const confirm = await this.$confirm('此操作将永久删除该分类, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(e => e);

            // 如果用户确认删除,则返回值为字符串 confirm;如果用户取消了删除,则返回值为字符串 cancel
            console.log('confirm: ', confirm);
            if (confirm !== 'confirm') return

            const { data: res } = await this.$http.delete('categories/' + uid)
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success('删除商品分类成功')
            if (this.queryInfo.pagenum > 1 && this.catelist.length === 1) this.queryInfo.pagenum -= 1
            this.getCateList()
        }
    }
}
</script>

1、请求分类数据,请求分类数据并将数据保存在data中

2、使用插件展示数据,使用第三方插件 vue-table-with-tree-grid 展示分类数据:

  • 在vue 控制台中点击依赖->安装依赖->运行依赖->输入vue-table-with-tree-gird->点击安装
  • 打开main.js,导入vue-table-with-tree-grid import TreeTable from 'vue-table-with-tree-grid',并全局注册组件 Vue.component('tree-table', TreeTable)

3、自定义数据列,使用vue-table-with-tree-grid定义模板列并添加自定义列

4、完成分页功能

5、完成添加分类,添加级联菜单显示父级分类。先导入Cascader组件,并注册;然后添加使用级联菜单组件

参数管理页

只允许给三级分类内容设置参数,参数分为动态参数和静态参数属性。

添加 goods/Params.vue 子组件,并在router.js中引入该组件并设置路由规则。

绘制参数管理基本结构

完成Params.vue组件的基本布局,其中警告提示信息使用了el-alert,在element.js引入该组件并注册:

<template>
    <div>
        <!-- 面包屑导航 -->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>参数列表</el-breadcrumb-item>
        </el-breadcrumb>

        <!-- 卡片 -->
        <el-card>
            <el-alert title="注意:只允许为第三级分类设置相关参数!" type="warning" show-icon :closable="false"></el-alert>

            <!-- 商品分类 -->
            <div class="cat_opt">
                <span>请选择商品分类:</span>
                <el-cascader v-model="selectedCateKeys" :options="cateList" :props="cateProps"  @change="handleChange" clearable></el-cascader>
            </div>
            
            <el-tabs v-model="activeName" @tab-click="handleTabClick">
                <el-tab-pane label="动态参数" name="many">
                    <el-button type="primary" size="mini" :disabled="selectedCateKeys.length!==3" @click="addDialogVisible = true">添加参数</el-button>
                    <!-- 动态参数列表 -->
                    <el-table :data="manyTableData" style="width: 100%" border stripe>
                        <el-table-column type="expand">
                            <template slot-scope="scope">
                                <el-tag v-for="(v, i) in scope.row.attr_vals" :key="i" closable @close="handleClose(scope.row, i)">{{v}}</el-tag>
                                <el-input class="input-new-tag" v-if="scope.row.inputVisible" v-model="scope.row.inputValue" ref="saveTagInput" size="small" @keyup.enter.native="handleInputConfirm(scope.row)" @blur="handleInputConfirm(scope.row)">
                                </el-input>
                                <el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button>
                            </template>
                        </el-table-column>
                        <el-table-column type="index" label="#"></el-table-column>
                        <el-table-column  prop="attr_name" label="属性名称"></el-table-column>
                        <el-table-column label="操作">
                            <template slot-scope="scope">
                                <el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog(scope.row.attr_id)">编辑</el-button>
                                <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeParams(scope.row.attr_id)">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>
                <el-tab-pane label="静态属性" name="only">
                    <el-button type="primary" size="mini" :disabled="selectedCateKeys.length!==3" @click="addDialogVisible = true">添加属性</el-button>
                    <!-- 静态属性列表 -->
                    <el-table :data="onlyTableData" style="width: 100%" border stripe>
                        <el-table-column type="expand">
                            <template slot-scope="scope">
                                <el-tag v-for="(v, i) in scope.row.attr_vals" :key="i" closable @close="handleClose(scope.row, i)">{{v}}</el-tag>
                                <el-input class="input-new-tag" v-if="scope.row.inputVisible" v-model="scope.row.inputValue" ref="saveTagInput" size="small" @keyup.enter.native="handleInputConfirm(scope.row)" @blur="handleInputConfirm(scope.row)">
                                </el-input>
                                <el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button>
                            </template>
                        </el-table-column>
                        <el-table-column type="index" label="#"></el-table-column>
                        <el-table-column  prop="attr_name" label="属性名称"></el-table-column>
                        <el-table-column label="操作">
                            <template slot-scope="scope">
                                <el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog(scope.row.attr_id)">编辑</el-button>
                                <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeParams(scope.row.attr_id)">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>
            </el-tabs>
        </el-card>

        <!-- 添加参数对话框 -->
        <el-dialog :title="'添加' + titleText" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
            <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px">
                <el-form-item :label="titleText" prop="attr_name">
                    <el-input v-model="addForm.attr_name"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="addDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="addParams">确 定</el-button>
            </span>
        </el-dialog>

        <!-- 编辑参数对话框 -->
        <el-dialog :title="'编辑' + titleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed">
            <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px">
                <el-form-item :label="titleText" prop="attr_name">
                    <el-input v-model="editForm.attr_name"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="editParams">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

主要功能实现

<script>
export default {
    data() {
        return {
            cateList: [],
            selectedCateKeys: [],
            cateProps: {
                expandTrigger: 'hover',
                value: 'cat_id',
                label: 'cat_name',
                children: 'children'
            },
            // 被激活的标签页名
            activeName: 'many',
            // 动态参数数据
            manyTableData: [],
            // 静态属性数据
            onlyTableData: [],
            // 是否展示添加参数对话框
            addDialogVisible: false,
            // 添加参数对话框数据
            addForm: {
                attr_name: ''
            },
            // 添加参数对话框验证规则
            addFormRules: {
                attr_name: [
                    { required: true, message: '请输入参数名称', trigger: 'blur' }
                ]
            },
            // 是否展示编辑参数对话框
            editDialogVisible: false,
            // 编辑参数对话框数据
            editForm: {
                attr_name: ''
            },
            // 编辑参数对话框验证规则
            editFormRules: {
                attr_name: [
                    { required: true, message: '请输入参数名称', trigger: 'blur' }
                ]
            }
        }
    },
    created() {
        this.getCateList()
    },
    methods: {
        // 获取分类数据
        async getCateList() {
            const { data: res } = await this.$http.get('categories', { params: { type: 3 } })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success('获取商品分类成功')
            this.cateList = res.data
        },
        // 获取参数列表数据
        async getParamsData() {
            const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: this.activeName } })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success('获取参数列表成功')
            // 将 attr_vals 转换为数组
            res.data.forEach(item => {
                item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : []
                item.inputVisible = false
                item.inputValue = ''
            });
            if (this.activeName === 'many') this.manyTableData = res.data
            this.onlyTableData = res.data
        },
        // 分类改变
        handleChange() {
            if (this.selectedCateKeys.length !== 3) {
                this.selectedCateKeys = []
                this.manyTableData = []
                this.onlyTableData = []
                return
            }
            console.log('change: ', this.selectedCateKeys);

            this.getParamsData()
        },
        // 点击标签页
        handleTabClick() {
            if (this.selectedCateKeys.length === 3) this.getParamsData()
        },
        // 关闭添加参数对话框
        addDialogClosed() {
            this.$refs.addFormRef.resetFields()
        },
        // 添加商品参数
        addParams() {
            // 验证表单
            this.$refs.addFormRef.validate(async valid => {
                if (!valid) return this.$msg.error('请填写正确的参数名称')

                const { data: res } = await this.$http.post(`categories/${this.cateId}/attributes`, { 
                    attr_name: this.addForm.attr_name,
                    attr_sel: this.activeName
                 })
                if (res.meta.status !== 201) return this.$msg.error(res.meta.msg)
                this.$msg.success(`添加${this.titleText}成功`)
                this.addDialogVisible = false
                this.getParamsData()
            })
        },
        // 展示编辑参数对话框
        async showEditDialog(attrId) {
            const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes/${attrId}`, { params: { attr_sel: this.activeName } })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success(`获取${this.titleText}成功`)
            this.editForm = res.data
            this.editDialogVisible = true
        },
        // 关闭编辑参数对话框
        editDialogClosed() {
            this.$refs.editFormRef.resetFields()
        },
        // 编辑商品参数
        editParams() {
            // 验证表单
            this.$refs.editFormRef.validate(async valid => {
                if (!valid) return this.$msg.error('请填写正确的参数名称')

                const { data: res } = await this.$http.put(`categories/${this.cateId}/attributes/${this.editForm.attr_id}`, { 
                    attr_name: this.editForm.attr_name,
                    attr_sel: this.activeName
                 })
                if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
                this.$msg.success(`添加${this.titleText}成功`)
                this.editDialogVisible = false
                this.getParamsData()
            })
        },
        // 删除商品参数
        async removeParams(attrId) {
            const confirm = await this.$confirm(`此操作将永久删除该${this.titleText}, 是否继续?`, '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(e => e);

            // 如果用户确认删除,则返回值为字符串 confirm;如果用户取消了删除,则返回值为字符串 cancel
            console.log('confirm: ', confirm);
            if (confirm !== 'confirm') return

            const { data: res } = await this.$http.delete(`categories/${this.cateId}/attributes/${attrId}`)
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success(`删除${this.titleText}成功`)
            this.getParamsData()
        },
        // 保存参数可选项
        async saveAttrVals(row, attrVals, isAdd) {
            const { data: res } = await this.$http.put(`categories/${this.cateId}/attributes/${row.attr_id}`, { 
                attr_name: row.attr_name,
                attr_sel: this.activeName,
                attr_vals: attrVals
            })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success(`${isAdd ? '添加' : '删除'}${this.titleText}可选项成功`)
            this.getParamsData()
        },
        // 删除参数可选项
        handleClose(row, i) {
            // 删除元素
            const attrVals = [...row.attr_vals.slice(0, i), ...row.attr_vals.slice(i + 1)].join(' ')
            console.log('attrVals: ', attrVals, '\ni: ', i);
            this.saveAttrVals(row, attrVals, false)
        },
        // 展示添加参数可选项输入框
        showInput(row) {
            row.inputVisible = true
            // 让文本框自动获得焦点
            // $nextTick 方法的作用,就是当页面上元素被重新渲染之后,才会指定回调函数中的代码
            this.$nextTick(_ => {
                this.$refs.saveTagInput.$refs.input.focus();
            });
        },
        // 
        handleInputConfirm(row) {
            if (row.inputValue.trim().length === 0) {
                row.inputVisible = false
                row.inputValue = ''
                return
            }

            // 添加元素
            const attrVals = row.attr_vals.concat(row.inputValue.trim()).join(' ')
            console.log('attrVals: ', attrVals);
            this.saveAttrVals(row, attrVals, true)
        }
    },
    // 计算属性
    computed: {
        // 选中的分类id
        cateId() {
            if (this.selectedCateKeys.length === 3) return this.selectedCateKeys[2]
            return null
        },
        // 动态计算标题的文本
        titleText() {
            if (this.activeName === 'many') {
                return '动态参数'
            }
            return '静态属性'
        }
    }
}
</script>

1、完成级联选择框,完成商品分类级联选择框

2、展示参数,展示动态参数数据以及静态属性数据

3、添加参数,完成添加参数或属性

4、编辑参数,完成编辑参数或属性

5、删除参数,删除参数或属性

6、动态参数和静态属性管理:

  • 展示动态参数可选项,动态参数可选项展示及操作在获取动态参数的方法中进行处理。
  • 添加/删除动态参数可选项
  • 展示静态属性可选项,静态属性可选项展示及操作在获取动态参数的方法中进行处理。
  • 添加/删除静态属性可选项

注意:当用户在级联选择框中选中了非三级分类时,需要清空表格中数据

商品列表页

添加 goods/List.vue 子组件,并在router.js中引入该组件并设置路由规则

绘制商品列表页基本结构

略,详情参考如下代码:

<template>
    <div>
        <!-- 面包屑导航 -->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>商品列表</el-breadcrumb-item>
        </el-breadcrumb>

        <!-- 卡片 -->
        <el-card>
            <!-- 搜索与添加 -->
            <el-row :gutter="20">
                <el-col :span="8">
                    <el-input placeholder="请输入内容" v-model.trim="queryInfo.query" clearable @clear="getGoodsList">
                        <el-button slot="append" icon="el-icon-search" @click="queryGoodsList" :disabled="queryInfo.query ? false : true"></el-button>
                    </el-input>
                </el-col>
                <el-col :span="4">
                    <el-button type="primary" @click="goAddPage">添加商品</el-button>
                </el-col>
            </el-row>

            <!-- 用户列表 -->
            <el-table :data="goodsList" style="width: 100%" border stripe>
                <el-table-column type="index" label="#"></el-table-column>
                <el-table-column  prop="goods_name" label="商品名称"></el-table-column>
                <el-table-column prop="goods_price" label="商品价格(元)" width="95"></el-table-column>
                <el-table-column prop="goods_weight" label="商品重量" width="70"></el-table-column>
                <el-table-column prop="add_time" label="创建时间" width="140">
                    <template slot-scope="scope">
                        {{scope.row.add_time | dateFormatter}}
                    </template>
                </el-table-column>
                <el-table-column label="操作" width="130">
                    <template slot-scope="scope">
                        <el-button type="primary" icon="el-icon-edit" size="mini"></el-button>
                        <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeById(scope.row.goods_id)"></el-button>
                    </template>
                </el-table-column>
            </el-table>

            <!-- 分页 -->
            <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="queryInfo.pagenum"
            :page-sizes="[2, 3, 5, 10]" 
            :page-size="queryInfo.pagesize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="total" background>
            </el-pagination>
            <pre>{{goodsList}}</pre>
        </el-card>
    </div>
</template>

主要功能实现

<script>
export default {
    data() {
        return {
            // 获取商品列表接口参数
            queryInfo: {
                query: '', // 搜索内容
                pagenum: 1, // 页面
                pagesize: 10 // 每页显示条数
            },
            // 商品列表数据
            goodsList: [],
            // 商品列表总条数
            total: 0
        }
    },
    created() {
        this.getGoodsList()
    },
    methods: {
        // 获取商品列表数据
        async getGoodsList() {
            const { data: res } = await this.$http.get('goods', { params: this.queryInfo })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.goodsList = res.data.goods
            this.total = res.data.total
            this.$msg.success('获取商品列表成功')
        },
        // 查询商品
        queryGoodsList() {
            this.queryInfo.pagenum = 1
            this.getGoodsList()
        },
        // pagesize 改变
        handleSizeChange(size) {
            console.log('size: ', size);
            this.queryInfo.pagesize = size
            const maxN = parseInt(this.total / this.queryInfo.pagesize + '') + (this.total % this.queryInfo.pagesize > 0 ? 1 : 0)
            this.queryInfo.pagenum = this.queryInfo.pagenum > maxN ? maxN : this.queryInfo.pagenum
            this.getGoodsList()
        },
        // 页码值 改变
        handleCurrentChange(num) {
            console.log('num: ', num);
            this.queryInfo.pagenum = num
            this.getGoodsList()
        },
        // 删除商品
        async removeById(gid) {
            const confirm = await this.$confirm('此操作将永久删除该商品, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(e => e);

            // 如果用户确认删除,则返回值为字符串 confirm;如果用户取消了删除,则返回值为字符串 cancel
            console.log('confirm: ', confirm);
            if (confirm !== 'confirm') return
            
            const { data: res } = this.$http.delete('goods/' + gid)
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success('删除商品成功')
            this.getGoodsList()
        },
        // 添加商品
        goAddPage() {
            console.log('goAddPage');
            this.$router.push('/home/addGoods')
        }
    }
}
</script>

1、数据展示,添加数据表格展示数据以及分页功能的实现,搜索功能的实现。在main.js中添加过滤器:

Vue.filter('dateFormatter', (ov) => {
  const date = new Date(ov)
  const y = date.getFullYear()
  const m = (date.getMonth() + 1 + '').padStart(2, '0')
  const d = (date.getDate() + '').padStart(2, '0')
  const hh = (date.getHours() + '').padStart(2, '0')
  const mm = (date.getHours() + '').padStart(2, '0')
  const ss = (date.getHours() + '').padStart(2, '0')
  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})

2、实现删除商品

3、添加商品,添加编程式导航,在List.vue中添加编程式导航,并创建添加商品路由组件及规则

添加商品页

添加 goods/Add.vue 子组件,并在router.js中引入该组件并设置路由规则。

绘制添加商品页基本结构

  • 布局过程中需要使用Steps组件,在element.js中引入并注册该组件,并在global.css中给组件设置全局样式
  • 其他略……
<template>
    <div>
        <!-- 面包屑导航 -->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>添加商品</el-breadcrumb-item>
        </el-breadcrumb>

        <!-- 卡片 -->
        <el-card>
            <!-- 警告 -->
            <el-alert
                title="添加商品信息"
                type="info"
                center
                show-icon 
                :closable="false">
            </el-alert>
        
            <!-- 步骤条 -->
            <el-steps :space="200" :active="activeIndex * 1" align-center finish-status="success">
                <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>

            <!-- 标签栏 -->
            <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="70px" label-position="top">
                <el-tabs v-model="activeIndex" tab-position="left" :before-leave="beforeTabLeave" @tab-click="tabClicked">
                    <el-tab-pane label="基本信息" name="0">
                        <el-form-item label="商品名称" prop="goods_name">
                            <el-input v-model="addForm.goods_name"></el-input>
                        </el-form-item>
                        <el-form-item label="商品价格" prop="goods_price">
                            <el-input v-model="addForm.goods_price" type="number"></el-input>
                        </el-form-item>
                        <el-form-item label="商品重量" prop="goods_weight">
                            <el-input v-model="addForm.goods_weight" type="number"></el-input>
                        </el-form-item>
                        <el-form-item label="商品数量" prop="goods_number">
                            <el-input v-model="addForm.goods_number" type="number"></el-input>
                        </el-form-item>
                        <el-form-item label="商品分类" prop="goods_cat">
                            <el-cascader v-model="addForm.goods_cat" :options="cateList" :props="cateProps"  @change="handleChange" clearable></el-cascader>
                        </el-form-item>
                    </el-tab-pane>
                    <el-tab-pane label="商品参数" name="1">
                        <el-form-item v-for="(v, i) in manyTableData" :key="i" :label="v.attr_name">
                            <el-checkbox-group v-model="v.attr_vals">
                                <el-checkbox v-for="(item, index) in v.attr_vals" :key="index" :label="item" border></el-checkbox>
                            </el-checkbox-group>
                        </el-form-item>
                        <pre>{{manyTableData}}</pre>
                    </el-tab-pane>
                    <el-tab-pane label="商品属性" name="2">
                        <el-form-item v-for="(v, i) in onlyTableData" :key="i" :label="v.attr_name">
                            <el-input v-model="v.attr_vals"></el-input>
                        </el-form-item>
                        <pre>{{onlyTableData}}</pre>
                    </el-tab-pane>
                    <el-tab-pane label="商品图片" name="3">
                        <el-upload 
                        action="http://127.0.0.1:8888/api/private/v1/upload"
                        :on-preview="handlePreview"
                        :on-remove="handleRemove" 
                        :headers="headerObj" 
                        :on-success="handleSuccess"
                        list-type="picture">
                            <el-button size="small" type="primary">点击上传</el-button>
                        </el-upload>
                    </el-tab-pane>
                    <el-tab-pane label="商品内容" name="4">
                        <quill-editor v-model="addForm.goods_introduce" @blur="onEditorBlur"></quill-editor>
                        <el-button class="btnAdd" type="primary" @click="add">添加商品</el-button>
                    </el-tab-pane>
                </el-tabs>
            </el-form>

            <!-- 预览对话框 -->
            <el-dialog
            title="图片预览"
            :visible.sync="previewVisible"
            width="50%">
                <img class="previewImg" :src="previewPath" alt="">
            </el-dialog>
        </el-card>
    </div>
</template>

主要功能实现

<script>
import _ from 'lodash';

export default {
    data() {
        return {
            activeIndex: '0',
            addForm: {
                goods_name: '',
                goods_price: '',
                goods_weight: '',
                goods_number: '',
                goods_cat: [],
                // 商品图片
                pics: [],
                // 描述
                goods_introduce: '',
                // 参数
                attrs: []
            },
            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: 'change' }]
            },
            // 商品分类
            cateList: [],
            cateProps: {
                expandTrigger: 'hover',
                value: 'cat_id',
                label: 'cat_name',
                children: 'children'
            },
            // 动态参数列表
            manyTableData: [],
            // 静态属性列表
            onlyTableData: [],
            // 请求头
            headerObj: { Authorization: window.sessionStorage.getItem('token') },
            // 是否展示预览图
            previewVisible: false,
            // 预览图片路径
            previewPath: ''
        }
    },
    created() {
        this.getCateList()
    },
    methods: {
        // 获取分类数据
        async getCateList() {
            const { data: res } = await this.$http.get('categories', { params: { type: 3 } })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success('获取商品分类成功')
            this.cateList = res.data
        },
        // 选择分类
        handleChange(v) {
            if (this.addForm.goods_cat.length !== 3) {
                this.addForm.goods_cat = []
                return
            }
            console.log('handleChange value: ', v);
        },
        // 标签页钩子函数
        beforeTabLeave(ai, oai) {
            console.log('ai: ', ai, ', oai: ', oai);
            if (oai === '0' && this.addForm.goods_cat.length !== 3) {
                this.$msg.error('请选择商品分类')
                return false
            }
        },
        // 点击标签栏
        async tabClicked() {
            if (this.activeIndex === '1') {
                const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: 'many' } })
                if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
                this.$msg.success('获取动态参数成功')
                res.data.forEach(item => {
                    item.attr_vals = item.attr_vals.length > 0 ? item.attr_vals.split(' ') : []
                });
                this.manyTableData = res.data
            } else if (this.activeIndex === '2') {
                const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`, { params: { sel: 'only' } })
                if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
                this.$msg.success('获取动态参数成功')
                this.onlyTableData = res.data
            }
        },
        // 点击预览图片
        handlePreview(file) {
            console.log('handlePreview: ', file);
            this.previewPath = file.response.data.url
            this.previewVisible = true
        },
        // 点击删除图片
        handleRemove(file, fileList) {
            // console.log('handleRemove:\nfile: ', file, '\nfileList: ', fileList);
            const index = this.addForm.pics.findIndex(v => v.pic === file.response.data.tmp_path)
            if (index !== -1) this.addForm.pics.splice(index, 1)
            console.log('addForm: ', this.addForm);
        },
        // 上传文件成功
        handleSuccess(response, file, fileList) {
            // console.log('handleSuccess:\nresponse: ', response, '\nfile: ', file, '\nfileList: ', fileList);
            this.addForm.pics.push({ pic: response.data.tmp_path })
            console.log('addForm: ', this.addForm);
        },
        // 富文本编辑器失去焦点
        onEditorBlur() {
            console.log('content: ', this.addForm.goods_introduce);
        },
        // 添加商品
        add() {
            this.$refs.addFormRef.validate(async valid => {
                if (!valid) return this.$msg.error('请填写必要的表单项')

                // lodash 深拷贝
                const addForm = _.cloneDeep(this.addForm)

                // 处理分类数据
                addForm.goods_cat = addForm.goods_cat.join(',')

                // 处理动态参数
                this.manyTableData.forEach(item => {
                    const newInfo = {
                        attr_id: item.attr_id,
                        attr_value: item.attr_vals.join(' ')
                    }
                    addForm.attrs.push(newInfo)
                });

                // 处理静态属性
                this.onlyTableData.forEach(item => {
                    const newInfo = {
                        attr_id: item.attr_id,
                        attr_value: item.attr_vals
                    }
                    addForm.attrs.push(newInfo)
                });
                console.log('addForm: ', addForm);
                
                // 发起请求
                const { data: res } = await this.$http.post('goods', addForm)
                if (res.meta.status !== 201) this.$msg.error(res.meta.msg)
                this.$msg.success('添加商品成功')
                this.$router.push('/home/goods')
            })
        }
    },
    computed: {
        cateId() {
            if (this.addForm.goods_cat.length === 3) return this.addForm.goods_cat[2]
            return null
        }
    }
}
</script>

1、添加tab栏切换验证,也就是说不输入某些内容,无法切换到别的tab栏

2、展示信息,展示商品参数信息、商品属性信息,在商品参数信息展示中使用的el-checkbox,el-checkbox-group组件,打开element.js引入组件并注册组件

3、完成图片上传,使用upload组件完成图片上传,在element.js中引入upload组件,并注册。因为upload组件进行图片上传的时候并不是使用axios发送请求,所以需要手动为上传图片的请求添加token,即为upload组件添加headers属性

4、使用富文本插件,想要使用富文本插件vue-quill-editor,就必须先从依赖安装该插件,引入并注册vue-quill-editor

5、添加商品,完成添加商品的操作,在添加商品之前,为了避免goods_cat数组转换字符串之后导致级联选择器报错需要打开vue控制条,点击依赖,安装lodash,把addForm进行深拷贝

订单列表页

创建订单列表路由组件并添加路由规则。

绘制订单列表页基本结构

略,详情参考如下代码:

<template>
    <div>
        <!-- 面包屑导航 -->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>订单管理</el-breadcrumb-item>
            <el-breadcrumb-item>订单列表</el-breadcrumb-item>
        </el-breadcrumb>

        <!-- 卡片 -->
        <el-card>
            <!-- 搜索与添加 -->
            <el-row :gutter="20">
                <el-col :span="8">
                    <el-input placeholder="请输入内容" v-model.trim="queryInfo.query" clearable @clear="getOrderList">
                        <el-button slot="append" icon="el-icon-search" @click="queryOrderList" :disabled="queryInfo.query ? false : true"></el-button>
                    </el-input>
                </el-col>
            </el-row>

            <!-- 用户列表 -->
            <el-table :data="orderList" style="width: 100%" border stripe>
                <el-table-column type="index" label="#"></el-table-column>
                <el-table-column  prop="order_number" label="订单编号"></el-table-column>
                <el-table-column prop="order_price" label="订单价格"></el-table-column>
                <el-table-column prop="order_pay" label="是否付款">
                    <template slot-scope="scope">
                        <el-tag type="danger" v-if="scope.row.pay_status === '0'">未付款</el-tag>
                        <el-tag type="success" v-else>已付款</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="is_send" label="是否发货"></el-table-column>
                <el-table-column prop="create_time" label="下单时间">
                    <template slot-scope="scope">
                        {{scope.row.create_time | dateFormatter}}
                    </template>
                </el-table-column>
                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button type="primary" icon="el-icon-edit" size="mini" @click="addressVisible = true"></el-button>
                        <el-button type="success" icon="el-icon-location" size="mini" @click="showProgressBox(scope.row.order_number)"></el-button>
                    </template>
                </el-table-column>
            </el-table>

            <!-- 分页 -->
            <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="queryInfo.pagenum"
            :page-sizes="[2, 3, 5, 10]" 
            :page-size="queryInfo.pagesize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="total" background>
            </el-pagination>
            <pre>{{orderList}}</pre>
        </el-card>

        <!-- 修改地址对话框 -->
        <el-dialog title="添加商品分类" :visible.sync="addressVisible" width="50%" @close="addressDialogClosed">
            <el-form :model="addressForm" :rules="addressFormRules" ref="addressFormRef" label-width="120px">
                <el-form-item label="省市区/县:" prop="address1">
                    <el-cascader v-model="addressForm.address1" :options="cityData" clearable></el-cascader>
                </el-form-item>
                <el-form-item label="详细地址:" prop="address2">
                    <el-input v-model="addressForm.address2"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="addressVisible = false">取 消</el-button>
                <el-button type="primary" @click="addressConfirm">确 定</el-button>
            </span>
        </el-dialog>

        <!-- 物流进度对话框 -->
        <el-dialog title="物流进度" :visible.sync="progressVisible" width="50%">
            <el-timeline>
                <el-timeline-item
                v-for="(activity, index) in progressInfo"
                :key="index"
                :color="index === 0 ? '#00ff00' : ''"
                :timestamp="activity.time">
                {{activity.context}}
                </el-timeline-item>
            </el-timeline>
        </el-dialog>
    </div>
</template>

主要功能实现

<script>
import cityData from './citydata.js'

export default {
    data() {
        return {
            queryInfo: {
                query: '',
                pagenum: 1,
                pagesize: 10
            },
            total: 0,
            // 订单列表
            orderList: [],
            // 省市区/县数据
            cityData,
            // 级联选择器属性
            cascaderProps: {
                expandTrigger: 'hover'
            },
            // 修改地址对话框
            addressVisible: false,
            addressForm: {
                address1: [],
                address2: ''
            },
            addressFormRules: {
                address1: [{ required: true, message: '请选择省市区县', trigger: 'change' }],
                address2: [{ required: true, message: '请填写详细地址', trigger: 'blur' }]
            },
            // 物流进度对话框
            progressVisible: false,
            // 物流进度数据
            progressInfo: []
        }
    },
    created() {
        this.getOrderList()
    },
    methods: {
        // 获取订单列表
        async getOrderList() {
            const { data: res } = await this.$http.get('orders', { params: this.queryInfo })
            if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            this.$msg.success('获取订单列表成功')
            this.orderList = res.data.goods
            this.total = res.data.total
        },
        // 查询订单列表
        queryOrderList() {
            this.queryInfo.pagenum = 1
            this.getOrderList()
        },
        // pagesize 改变
        handleSizeChange(size) {
            console.log('size: ', size);
            this.queryInfo.pagesize = size
            const maxN = parseInt(this.total / this.queryInfo.pagesize + '') + (this.total % this.queryInfo.pagesize > 0 ? 1 : 0)
            this.queryInfo.pagenum = this.queryInfo.pagenum > maxN ? maxN : this.queryInfo.pagenum
            this.getOrderList()
        },
        // 页码值 改变
        handleCurrentChange(num) {
            console.log('num: ', num);
            this.queryInfo.pagenum = num
            this.getOrderList()
        },
        // 关闭修改地址对话框
        addressDialogClosed() {
            // console.log('addressForm: ', this.addressForm);
            this.$refs.addressFormRef.resetFields()
        },
        // 修改地址
        addressConfirm() {
            // console.log('addressForm: ', this.addressForm);
            this.addressVisible = false
        },
        // 展示物流进度对话框
        async showProgressBox(orderNumber) {
            // const { data: res } = await this.$http.get('kuaidi/' + orderNumber)
            // if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
            // this.$msg.success('获取物流进度成功')
            // this.progressInfo = res.data
            
            const data = `[
                {
                "time": "2018-05-10 09:39:00",
                "ftime": "2018-05-10 09:39:00",
                "context": "已签收,感谢使用顺丰,期待再次为您服务",
                "location": ""
                },
                {
                "time": "2018-05-10 08:23:00",
                "ftime": "2018-05-10 08:23:00",
                "context": "[北京市]北京海淀育新小区营业点派件员 顺丰速运 95338正在为您派件",
                "location": ""
                },
                {
                "time": "2018-05-10 07:32:00",
                "ftime": "2018-05-10 07:32:00",
                "context": "快件到达 [北京海淀育新小区营业点]",
                "location": ""
                },
                {
                "time": "2018-05-10 02:03:00",
                "ftime": "2018-05-10 02:03:00",
                "context": "快件在[北京顺义集散中心]已装车,准备发往 [北京海淀育新小区营业点]",
                "location": ""
                },
                {
                "time": "2018-05-09 23:05:00",
                "ftime": "2018-05-09 23:05:00",
                "context": "快件到达 [北京顺义集散中心]",
                "location": ""
                },
                {
                "time": "2018-05-09 21:21:00",
                "ftime": "2018-05-09 21:21:00",
                "context": "快件在[北京宝胜营业点]已装车,准备发往 [北京顺义集散中心]",
                "location": ""
                },
                {
                "time": "2018-05-09 13:07:00",
                "ftime": "2018-05-09 13:07:00",
                "context": "顺丰速运 已收取快件",
                "location": ""
                },
                {
                "time": "2018-05-09 12:25:03",
                "ftime": "2018-05-09 12:25:03",
                "context": "卖家发货",
                "location": ""
                },
                {
                "time": "2018-05-09 12:22:24",
                "ftime": "2018-05-09 12:22:24",
                "context": "您的订单将由HLA(北京海淀区清河中街店)门店安排发货。",
                "location": ""
                },
                {
                "time": "2018-05-08 21:36:04",
                "ftime": "2018-05-08 21:36:04",
                "context": "商品已经下单",
                "location": ""
                }
            ]`
            this.progressInfo = JSON.parse(data)
            this.progressVisible = true
        }
    }
}
</script>

1、实现数据展示及分页

2、制作省市区/县联动,制作 citydata.js 数据文件放到到components/order文件夹中,然后导入citydata.js文件

3、制作物流进度对话框,因为使用的是element-ui中提供的Timeline组件,所以需要导入并注册组件

数据统计页

创建数据统计路由组件并添加路由规则。

导入ECharts并使用,具体实现如下:

<template>
    <div>
        <!-- 面包屑导航 -->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{ path: '/home/welcome' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>数据统计</el-breadcrumb-item>
            <el-breadcrumb-item>数据报表</el-breadcrumb-item>
        </el-breadcrumb>

        <!-- 卡片 -->
        <el-card>
            <div id="main" style="width: 750px; height:400px;"></div>
        </el-card>
    </div>
</template>

<script>
// 只会导出(export default mutations)这个默认的对象返回
// import echarts from 'echarts'

// 将 若干export导出的内容组合成一个对象返回
import * as echarts from 'echarts'

import _ from 'lodash'

export default {
    data() {
        return {
            options: {
                title: {
                    text: '用户来源'
                },
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                        type: 'cross',
                        label: {
                            backgroundColor: '#E9EEF3'
                        }
                    }
                },
                grid: {
                    left: '3%',
                    right: '4%',
                    bottom: '3%',
                    containLabel: true
                },
                xAxis: [
                    {
                        boundaryGap: false
                    }
                ],
                yAxis: [
                    {
                        type: 'value'
                    }
                ]
            }
        }
    },
    async mounted() {
        const { data: res } = await this.$http.get('reports/type/1')
        if (res.meta.status !== 200) return this.$msg.error(res.meta.msg)
        this.$msg.success('获取折线图数据成功')

        // 初始化
        var myChart = echarts.init(document.getElementById('main'))

        // 设置配置项
        myChart.setOption(_.merge(this.options, res.data))
    }
}
</script>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容