uni app uview新增商品页(无限级分类选择和多图上传)

uni app uview新增商品页(无限级分类选择和多图上传)

给自己的牛腩商品库UNI APP加的一个新增功能,就是通用的新增页面,用的uview2(https://uviewui.com/components/intro.html),能选择无限级分类和多图上传,自已觉得这个新增页面在以后做uni app项目的时候很多地方会用到吧,先记下来了,以后用的时候直接复制粘贴代码就好了,其实那个弹出框选择分类的多少还是有点问题的,不过无伤大雅,就这样吧!!!


数据库设计:商品表,商品分类表,商品图片表, 商品属性表(这里没有用到)
接口:
上传图片(用牛腩代码生成器生成的HOMEController里有相关的代码,里面加了些代码,生成固定大小的格式,上传到七牛云,)
根据父ID取分类(0为第一级分类)
根据分类ID取上级一连串分类(用于弹出分类框上面那个tabs)

新增商品



上传图片接口代码
    /// <summary>
    /// 图片上传
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    public async Task<IActionResult> ImgUpload()
    {
        var imgFile = Request.Form.Files[0];
        if (imgFile != null && !string.IsNullOrEmpty(imgFile.FileName))
        {
            long size = 0;
            string tempname = "";
            var filename = ContentDispositionHeaderValue
                            .Parse(imgFile.ContentDisposition)
                            .FileName
                            .Trim();
            var fileExt = filename.Substring(filename.LastIndexOf('.'), filename.Length - filename.LastIndexOf('.')); //扩展名,如.jpg

            fileExt = fileExt.Replace("\"", "");

            #region 判断后缀
            if (!fileExt.ToLower().Contains("jpg") && !fileExt.ToLower().Contains("png") && !fileExt.ToLower().Contains("gif"))
            {
                return Json(new { code = 1, msg = "只允许上传jpg,png,gif格式的图片.", });
            }
            #endregion

            #region 判断大小
            long mb = imgFile.Length / 1024 / 1024; // MB
            if (mb > 20)
            {
                return Json(new { code = 1, msg = "只允许上传小于 20MB 的图片.", });
            }
            #endregion

            string nohouzui = "niunanproduct_" + System.Guid.NewGuid().ToString().Substring(0, 6);
            String newFileName = nohouzui + fileExt;


            var path = hostingEnv.WebRootPath; //网站静态文件目录  wwwroot

            //完整物理路径
            string wuli_path = path + $"{Path.DirectorySeparatorChar}upload{Path.DirectorySeparatorChar}";
            if (!System.IO.Directory.Exists(wuli_path))
            {
                System.IO.Directory.CreateDirectory(wuli_path);
            }
            filename = wuli_path + newFileName;
            size += imgFile.Length;
            using (FileStream fs = System.IO.File.Create(filename))
            {
                imgFile.CopyTo(fs);
                fs.Flush();
                fs.Close();
            }

            #region 生成900x900的图片,小于宽900的则不生成
            int img_w = 0;
            int img_h = 0;
            Tool.GetImgWH(wuli_path + newFileName, out img_w, out img_h);

            string filename_upload = ""; //最后返回的文件名
            string tpath = "";
            if (img_w < 900 && img_h<900)
            {
                filename_upload = newFileName;
                tpath = wuli_path + newFileName;
            }
            else
            {
                filename_upload = nohouzui + "_900x900" + fileExt;
                string opath = wuli_path + newFileName; //源路径
                tpath = wuli_path + filename_upload;  //目标路径
                Tool.CreateImage(opath, tpath, 900, 900);

            }
            #endregion
            #region 传到七牛云上
            string endfilepath = ""; //最后返回 的JSON里的Src
            if (webSetting.IsUploadTo7Niu == 1)
            {
                //using Qiniu.Util;
                //using Qiniu.Storage;
                string AccessKey = "WB-23aaaaaaaSZ45kvT_20zbdk2O";
                string SecretKey = "cJRT2FAbbbbbbbXTicRNJClNnu3x27nz3D";
                string Bucket = "niunan-net";
                Mac mac = new Mac(AccessKey, SecretKey);
                PutPolicy putPolicy = new PutPolicy();
                putPolicy.Scope = Bucket;
                putPolicy.SetExpires(3600);
                string jstr = putPolicy.ToJsonString();
                string token = Auth.CreateUploadToken(mac, jstr);
                Config config = new Config();
                config.Zone = Zone.ZoneCnSouth;
                config.UseHttps = false;
                config.UseCdnDomains = true;
                FormUploader fu = new FormUploader(config);
                Stream s = new System.IO.FileInfo(tpath).OpenRead();
                var result = await fu.UploadStream(s, filename_upload, token, null);
                s.Dispose();
                s.Close();
                log.Info($"上传图片到七牛云,文件名【{filename_upload}】,上传结果\r\n{result.Text}\r\n\r\n\r\n");
                endfilepath = "http://qiniu.niunan.net/" + filename_upload;

                //上传完成后要把本地的文件删除掉
                try
                {
                    System.IO.File.Delete(tpath);
                    log.Info($"删除本地文件成功:" + tpath);
                }
                catch (Exception ex)
                {
                    log.Error($"删除本地文件失败:" + ex.Message);
                }
            }
            else
            {
                endfilepath = "/upload/"+filename_upload;
            }



            #endregion

            return Json(new
            {
                code = 0,
                msg = "上传成功",
                data = new
                {
                    src = endfilepath,
                    title = filename_upload
                }
            });
        }
        return Json(new { code = 1, msg = "上传失败", });
    }
根据父ID取分类(0为第一级分类)

    public async Task<IActionResult> GetList(int pid = 0)
    {

        List<Category> list = await _carep.GetListAsync(pid);
        ArrayList arr = new ArrayList();
        foreach (Category c in list)
        {
            arr.Add(new
            {
                categoryID = c.CategoryID,
                categoryName = c.CategoryName,
                proCount = await _carep.GetProCountAsync(c.CategoryID), //商品数量
                xjCount = await _caRepository.CountAsync(a => a.ParentID == c.CategoryID),//下级数量
            });
        }
        return Json(arr);
    }
根据分类ID取上级一连串分类(用于弹出分类框上面那个tabs)

    /// <summary>
    /// 反正该分类的所有上级的JSON
    /// 如【{categoryID:1,categoryName:编程语言,parentid:0},{categoryID:2,categoryName:JAVA,parentID:1},】
    /// </summary>
    /// <param name="caid"></param>
    /// <returns></returns>
    public async Task<IActionResult> GetFullPath(int caid)
    {
        try
        {
            Category ca = _caRepository.FirstOrDefault(a => a.CategoryID == caid);
            if (ca == null)
            {
                throw new Exception("分类不存在!");
            }
            ArrayList arr = new ArrayList();
            if (!string.IsNullOrEmpty(ca.CategoryPath))
            {
                string[] ss = ca.CategoryPath.Split(',');
                foreach (string s in ss)
                {
                    if (string.IsNullOrEmpty(s)) { continue; }
                    Category ca_temp = _caRepository.FirstOrDefault(a => a.CategoryID == int.Parse(s));
                    if (ca_temp == null) { continue; }
                    arr.Add(new { categoryID = ca_temp.CategoryID, categoryName = ca_temp.CategoryName,parentID= ca_temp.ParentID });
                }
            }
            arr.Add(new { categoryID = ca.CategoryID, categoryName = ca.CategoryName,parentID = ca.ParentID });
            return Json(new { code = 0, msg = "success", data = arr });
        }
        catch (Exception ex)
        {
            return Json(new { code = 1, msg = "出错:" + ex.Message });
        }
    }
新增商品接口

    public async Task<IActionResult> Add(Product model, string strsx, string strimg)
    {
        try
        {
            if (string.IsNullOrEmpty(model.ProductName))
            {
                return Json(new { code = 1, msg = "名字不能为空。" });
            }
            if (string.IsNullOrEmpty(model.ThumbnailImage))
            {
                return Json(new { code = 1, msg = "首图不能为空。" });
            }
            if (model.CategoryID == 0)
            {
                return Json(new { code = 1, msg = "分类不能为空。" });
            }
            if (string.IsNullOrEmpty(model.BrandName))
            {
                model.BrandName = "无品牌";
                model.BrandID = 0;
            }
            if (string.IsNullOrEmpty(model.Unit))
            {
                model.Unit = "";
            }
            if (string.IsNullOrEmpty(model.Code))
            {
                //如果条码是空则给他一个22+guid 前11位
                model.Code ="22"+ Guid.NewGuid().ToString().Replace("-","").Substring(0,11);
            }

            if (websetting.IsUploadTo7Niu == 1 && !model.ThumbnailImage.StartsWith("http"))
            {
                //首图不为空,前台传过来的只是名字,还得加前缀
                model.ThumbnailImage = "http://qiniu.niunan.net/" + model.ThumbnailImage;
            }
            else
            {
                if (model.ProductID==0 && !model.ThumbnailImage.StartsWith("http"))
                {
                    //新增的时候传入的才只是文件名,修改时不管
                    model.ThumbnailImage = "/upload/" + model.ThumbnailImage;
                } 
            }
            model.LastUpdateTime = DateTime.Now;
            if (model.ProductID > 0)
            {
                #region 编辑
               
                model.Images = GetImg(strimg, model.ProductID);
                model.ShuXings = GetSX(strsx, model.ProductID);
                await _prorep.UpdateAsync(model);
                return Json(new { code = 0, msg = "商品编辑成功。" });
                #endregion

            }
            else
            {
                #region 新增
                Niunan.SPK.Models.Product pro = await _productRepository.FirstOrDefaultAsycn(a => a.Code == model.Code);
                if (pro != null)
                {
                    throw new Exception("条码重复,数据库中已有对应条码的商品【" + pro.ProductName + "】");
                }
                model.Images = GetImg(strimg, 0);
                model.ShuXings = GetSX(strsx, 0);
                if (await _prorep.HasSameName(model.ProductName))
                {
                    return Json(new { code = 1, msg = "已有同名商品,不能新增。" });
                }
                model.CreatedTime = DateTime.Now;

                if (string.IsNullOrEmpty(model.ThumbnailImage) && model.Images.Count > 0)
                {
                    model.ThumbnailImage = model.Images[0].RelativeUrl;
                }
                int proid = await _prorep.AddAsync(model);
                if (proid == 0)
                {

                    return Json(new { code = 1, msg = "新增商品失败" });
                }
                else
                {
                    if (websetting.IsUploadTo7Niu==1)
                    {
                        //只有图片传到七牛云的时候才删除大图
                        DeleteDaTu(proid);
                    }
                 
                    return Json(new { code = 0, msg = "新增商品成功,新增的商品ID:" + proid });
                }
                #endregion
            }
        }
        catch (Exception ex)
        {
            return Json(new { code = 1, msg = "出错:" + ex.Message });

        }
    }
    /// <summary>
    /// 删除服务器上的大图
    /// </summary>
    /// <param name="proid"></param>
    /// <exception cref="NotImplementedException"></exception>
    private async void DeleteDaTu(int proid)
    {
        if (websetting.IsUploadTo7Niu==1)
        {
            //传到七牛云上的时候才删除本地磁盘的
            string tou = hostingEnv.WebRootPath + "/upload/";
            List<Niunan.SPK.Models.ProductImage> list = _productimageRepository.GetAllList(a => a.ProductID == proid);
            foreach (var item in list)
            {
                string filename = item.Comments.Replace("_900x900", "");
                string fullfilename = tou + filename;
                if (!System.IO.File.Exists(fullfilename))
                {
                    log.Error(fullfilename + "不存在。");
                    continue;
                }
                try
                {
                    System.IO.File.Delete(fullfilename);
                }
                catch (Exception ex)
                {
                    log.Error(ex);
                    continue;
                }

            }
        } 
    }

    //根据字符串取产品图片集合  imgid:img^imgid2:img2...
    private List<ProductImage> GetImg(string strimg, int productid)
    {
        List<Niunan.SPK.Models.ProductImage> listimg = new List<ProductImage>();
        if (!string.IsNullOrEmpty(strimg))
        {
            string[] ss = strimg.Split('^');
            foreach (var s in ss)
            {
                if (!string.IsNullOrEmpty(s))
                {
                    string[] ss2 = s.Split(':');
                    string imgpath = ss2[1];
                    if (websetting.IsUploadTo7Niu == 1)
                    {
                        if (!imgpath.StartsWith("http")) {
                            imgpath = "http://qiniu.niunan.net/" + imgpath;
                        }
                        
                    }
                    else {
                        imgpath = "/upload/"+imgpath;
                    }
                    listimg.Add(new ProductImage()
                    {
                        ImageID = int.Parse(ss2[0]),
                        ProductID = productid,
                        Comments = ss2[1],
                        RelativeUrl = imgpath,
                        Title = ss2[2],
                    });
                }
            }
        }
        return listimg;
    }

    //根据字符串取产品属性集合   sxid:name:value^sxid:name2:value2...
    private List<ProductShuXing> GetSX(string strsx, int productid)
    {
        List<Niunan.SPK.Models.ProductShuXing> listsx = new List<ProductShuXing>();
        if (!string.IsNullOrEmpty(strsx))
        {
            string[] ss1 = strsx.Split('^');
            foreach (var s in ss1)
            {
                if (!string.IsNullOrEmpty(s))
                {
                    string[] ss2 = s.Split(':');
                    if (ss2.Length == 3 && !string.IsNullOrEmpty(ss2[1]))
                    {
                        listsx.Add(new ProductShuXing()
                        {
                            ShuXingID = int.Parse(ss2[0]),
                            ProductID = productid,
                            SXName = ss2[1],
                            SXValue = ss2[2]
                        });
                    }
                }
            }
        }
        return listsx;
    }
uni APP中新增商品的页面代码

<template>
    <view style="padding:10px;">
        <u--form labelPosition="left" ref="uForm">
            <u-form-item label="名称" borderBottom>
                <u--input v-model="product.productname" border="none"></u--input>
            </u-form-item>
            <u-form-item label="分类" borderBottom @click="showca = true;">
                {{cafullname}}
                <u-icon slot="right" name="arrow-right"></u-icon>
            </u-form-item>
            <u-form-item label="品牌" borderBottom>
                <u--input v-model="product.brandname" border="none"></u--input>
            </u-form-item>
            <u-form-item label="单位" borderBottom>
                <u--input v-model="product.unit" border="none"></u--input>
            </u-form-item>
            <u-form-item label="单价" borderBottom>
                <u--input v-model="product.unitprice" type="number" border="none"></u--input>
            </u-form-item>
            <u-form-item label="条码" borderBottom>
                <u--input v-model="product.code" border="none"></u--input>
            </u-form-item>
            <u-form-item label="图片" borderBottom>
                <u-upload :fileList="fileList1" @afterRead="afterRead" @delete="deletePic" name="1" multiple
                    :maxCount="10"></u-upload>
            </u-form-item>
            <u-form-item label="使用感受" borderBottom>
                <u--textarea v-model="product.usefeel" autoHeight border="none"></u--textarea>
            </u-form-item>
        </u--form>
        <u-button text='提交' @click="sub()" class="niunanbtn2"></u-button>

        <!--选择分类(无限级)-->
        <u-popup :show="showca">
            <view>
                <u-row>
                    <u-col span="10">
                        <u-tabs :list="tabs" keyName="categoryName" :current="currentIndex"
                            @change="changeTabs"></u-tabs>
                    </u-col>
                    <u-col span="2">
                        <u-button text="确定" class="niunanbtn" @click="selca_ok()"></u-button>
                    </u-col>
                </u-row>

                <u-list>
                    <u-list-item v-for="(item, index) in calist" :key="index">
                        <u-cell :isLink="item.xjCount==0?false:true" @click="toxiaji(item)">
                            <view slot="title" class="u-slot-title">
                                {{item.categoryName}}(商品数:{{item.proCount}},下级:{{item.xjCount}})


                            </view>
                        </u-cell>
                    </u-list-item>
                </u-list>
            </view>
        </u-popup>
        <!--选择分类(无限级)end -->
    </view>
</template>

<script>
 
    export default {
        components: {},
        data() {
            return {
                showca: false,
                product: {
                    productid: 0,
                    productname: '',
                    brandname: '',
                    categoryid: 0,
                    unit: '',
                    unitprice: 0,
                    usefeel: '',
                    code: '',
                    strsx: '',
                    strimg: '',
                    ThumbnailImage: '',
                },
                cafullname: '',
                tabs: [{
                    categoryID: 0,
                    categoryName: "请选择"
                }],
                currentIndex: 0,
                calist: [{
                    "categoryID": 1,
                    "categoryName": "电子产品",
                    "proCount": 19,
                    "xjCount": 10
                }, ],
                fileList1: [], //图片集合
            }
        },
        onLoad() {
            console.log(common.apiurl, 'apiurl');
            this.getcalist(0);
        },
        methods: {
            sexSelect(e) {
                this.model1.userInfo.sex = e.name
                this.$refs.uForm.validateField('userInfo.sex')
            },
            //选择商品分类
            selca(caid) {
                uni.showToast({
                    title: caid + ""
                })
            },
            //取商品分类
            getcalist(caid) {
                var that = this;
                var url =   "http://localhost:5000/category/getlist?pid=" + caid
                console.log(url)
                uni.request({
                    url: url,
                    method: 'GET',
                    success: (res) => {
                        that.calist = res.data
                    }
                })
            },
            //变更分类选项卡
            changeTabs(e) {
                console.log(e)
                this.getcalist(e.parentID);
            },
            //跳转到下级分类列表
            toxiaji(ca) {
                var that = this;
                console.log(JSON.stringify(ca))
                var url =   "http://localhost:5000/category/getfullpath?caid=" + ca.categoryID;
                console.log(url)
                uni.request({
                    url: url,
                    method: 'GET',
                    success: (res) => {
                        if (res.data.code == 0) {
                            that.tabs = res.data.data;
                            if (ca.xjCount != 0) {
                                //有下级,新加一个选项卡, 取下级列表
                                that.tabs.push({
                                    categoryID: 0,
                                    categoryName: '请选择',
                                    parentID: 0
                                });
                                console.log(JSON.stringify(that.tabs), '当前选项卡')
                                that.currentIndex = that.tabs.length - 1;
                                console.log(that.currentIndex, '当前选项卡索引')

                                that.getcalist(ca.categoryID)
                            } else {
                                console.log(that.tabs, that.currentIndex)
                                that.currentIndex = that.tabs.length - 1;
                            }

                        } else {
                            uni.showModal({
                                content: '' + res.msg,
                                showCancel: false
                            })
                        }
                    }
                })
            },
            //选择某一分类后点确定
            selca_ok() {
                this.showca = false;
                this.cafullname = '';
                var str = "";
                var len = this.tabs.length;
                for (var i = 0; i < len; i++) {
                    var oneca = this.tabs[i];
                    str += '/' + oneca.categoryName;
                    if (i == len - 1) {
                        //最后一项,赋值商品模型的分ID
                        this.product.categoryid = oneca.categoryID;
                    }
                }
                this.cafullname = str;
            },
            //提交
            sub() {
                var that = this;
                if (that.fileList1.length == 0) {
                    uni.showToast({
                        title: '请上传至少一张图片!',
                        icon: 'none'
                    })
                    return;
                }
                that.product.ThumbnailImage = that.fileList1[0].url; //默认第一张为首图
                //拼接图片集合 imgid:img^imgid2:img2... 新增时ID为0
                var imgs = '';
                for (var i = 0; i < that.fileList1.length; i++) {
                    var one = that.fileList1[i];
                    imgs += '0:' + one.url + '^';
                }
                that.product.strimg = imgs;

                // uni.showModal({
                //     content: JSON.stringify(this.product),
                //     showCancel: false
                // })
                uni.showLoading({
                    title: "加载中..."
                })
                var addurl = "http://localhost:5000/product/add";
                uni.request({
                    url: addurl,
                    method: 'POST',
                    header: {
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    data: that.product,
                    success: (res) => {
                        uni.hideLoading();
                        console.log("新增后返回:" + JSON.stringify(res));
                        if(res.data.code==1){
                            uni.showModal({
                                content:res.data.msg,
                                showCancel:false
                            })
                        }else        {
                            uni.showModal({
                                content:'新增成功',
                                showCancel:false,
                                success: () => {
                                    uni.switchTab({
                                        url:'/pages/index/index'
                                    })
                                }
                            })
                        }
                    } 
                })
            },
            // 删除图片
            deletePic(event) {
                this[`fileList${event.name}`].splice(event.index, 1)
            },
            // 新增图片
            async afterRead(event) {
                // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
                let lists = [].concat(event.file)
                let fileListLen = this[`fileList${event.name}`].length
                lists.map((item) => {
                    this[`fileList${event.name}`].push({
                        ...item,
                        status: 'uploading',
                        message: '上传中'
                    })
                })
                for (let i = 0; i < lists.length; i++) {
                    const result = await this.uploadFilePromise(lists[i].url)
                    console.log("上传一张图片,返回:" + JSON.stringify(result));
                    var imgurl = result.data.src;
                    console.log(imgurl, "imgurl");
                    let item = this[`fileList${event.name}`][fileListLen]
                    this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, {
                        status: 'success',
                        message: '',
                        url: imgurl
                    }))
                    fileListLen++
                }
            },
            uploadFilePromise(url) {
                return new Promise((resolve, reject) => {
                    let a = uni.uploadFile({
                        url:  'http://localhost:5000/home/ImgUpload', // 仅为示例,非真实的接口地址
                        filePath: url,
                        name: 'file',
                        formData: {
                            user: 'test'
                        },
                        success: (res) => {
                            var json = JSON.parse(res.data);
                            setTimeout(() => {
                                resolve(json)
                            }, 1000)
                        }
                    });
                })
            },

        }
    }
</script>

<style lang="scss">
    .niunanbtn {
        width: 50px !important;
        height: 30px !important;
        background-color: darkcyan !important;
        color: white !important;
    }

    .niunanbtn2 {
        background-color: $uni-color-primary !important;
        color: white !important;
    }
</style>
注:用本地 H5测试的话记得把接口那加上允许跨域的相关代码!!!