• 欢迎访问开心洋葱网站,在线教程,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站,欢迎加入开心洋葱 QQ群
  • 为方便开心洋葱网用户,开心洋葱官网已经开启复制功能!
  • 欢迎访问开心洋葱网站,手机也能访问哦~欢迎加入开心洋葱多维思维学习平台 QQ群
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏开心洋葱吧~~~~~~~~~~~~~!
  • 由于近期流量激增,小站的ECS没能经的起亲们的访问,本站依然没有盈利,如果各位看如果觉着文字不错,还请看官给小站打个赏~~~~~~~~~~~~~!

元旦三天假期,实现一个电商退单管理系统【二】

C# 开心洋葱 2519次浏览 0个评论

一、仓库扫码监听客户端实现

(一)功能分析

快递小哥骑着小三轮,运送退货快递到仓库,库管打开客户端,选择快递公司后,递给快递一把扫码枪,小哥滴滴滴,滴滴滴,一顿操作猛如虎,打完收功。仓管将数据提交服务器,打印回单,整个客户端流程结束。

 元旦三天假期,实现一个电商退单管理系统【二】

 

仓库的客户端需要监听扫码枪输入,计划使用C#编写一个托盘程序,负责订单的接收,以及提交服务器端、打印回单等任务,同时还能查询历史订单信息等。

 主要功能如下:

用户登录:调用服务端接口,验证用户身份。

选择快递公司:调用服务端接口,查询所有快递公司列表,为了直观,直接显示快递公司的Logo,供用户选择。

扫码监听:监听输入,语音提醒,并显示日志,对于不符合快递单号长度、重复单号等错误予以提醒。

提交服务器:将本地缓存的扫码单,传到服务器端,再根据服务端返回结果,更新本地缓存状态(同步成功、订单号重复、其他错误等)。

打印今日回单:打印该快递当前日期的回单。

打印当前页面回单:打印本次扫码订单。

查看历史订单:查看历史订单,显示是否已同步成功。

元旦三天假期,实现一个电商退单管理系统【二】

(二)代码实现

1. 基础类编写

好久没有写c#了,现在居然是MVVM了,找了一个MVVMLight,然后又找了一个materialDesign的皮肤(网上文档太少,项目中用的不是太多),把界面搭建起来。

因为要与服务端通讯,从网上找了一个Http传输的类库,做了点修改,加了一个async的请求方法。其他业务只要调用HttpGet、HttpPost、HttpGetAsync就可以了。基本上都是用的get方法,只有数据同步接口,因为要post json上去,才用了post方法。

元旦三天假期,实现一个电商退单管理系统【二】

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace ordermanage.Common
{
    public class HttpRequestHelper
    {
        public static string HttpPost(string Url, string postDataStr)
        {
            //获取提交的字节
            byte[] bs = Encoding.UTF8.GetBytes(postDataStr);
            //设置提交的相关参数
            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(Url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            req.ContentLength = bs.Length;
            //提交请求数据
            Stream reqStream = req.GetRequestStream();
            reqStream.Write(bs, 0, bs.Length);
            reqStream.Close();
            //接收返回的页面,必须的,不能省略
            WebResponse wr = req.GetResponse();
            System.IO.Stream respStream = wr.GetResponseStream();
            System.IO.StreamReader reader = new System.IO.StreamReader(respStream, System.Text.Encoding.GetEncoding("utf-8"));
            string t = reader.ReadToEnd();
            return t;
        }

        public static string HttpGet(string Url, string postDataStr)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr);
            request.Method = "GET";
            request.ContentType = "application/json";
            string retString;
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream myResponseStream = response.GetResponseStream();
            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));
            retString = myStreamReader.ReadToEnd();
            myStreamReader.Close();
            myResponseStream.Close();
            return retString;
        }

        public static async Task<string> HttpGetAsync(string Url, string postDataStr)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr);
            request.Method = "GET";
            request.ContentType = "application/json";
            string retString;
            HttpWebResponse response = (HttpWebResponse)await getServerResponseSync(request);
            Stream myResponseStream = response.GetResponseStream();
            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));
            retString = myStreamReader.ReadToEnd();
            myStreamReader.Close();
            myResponseStream.Close();
            return retString;
        }

        public static async Task<WebResponse> getServerResponseSync(HttpWebRequest request) {
            return await request.GetResponseAsync();
        }

        /// <summary> 
        /// 创建GET方式的HTTP请求 
        /// </summary> 
        //public static HttpWebResponse CreateGetHttpResponse(string url, int timeout, string userAgent, CookieCollection cookies)
        public static HttpWebResponse CreateGetHttpResponse(string url)
        {
            HttpWebRequest request = null;
            if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
            {
                //对服务端证书进行有效性校验(非第三方权威机构颁发的证书,如自己生成的,不进行验证,这里返回true)
                ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
                request = WebRequest.Create(url) as HttpWebRequest;
                request.ProtocolVersion = HttpVersion.Version10;    //http版本,默认是1.1,这里设置为1.0
            }
            else
            {
                request = WebRequest.Create(url) as HttpWebRequest;
            }
            request.Method = "GET";

            //设置代理UserAgent和超时
            //request.UserAgent = userAgent;
            //request.Timeout = timeout;
            //if (cookies != null)
            //{
            //    request.CookieContainer = new CookieContainer();
            //    request.CookieContainer.Add(cookies);
            //}
            return request.GetResponse() as HttpWebResponse;
        }

        /// <summary> 
        /// 创建POST方式的HTTP请求 
        /// </summary> 
        //public static HttpWebResponse CreatePostHttpResponse(string url, IDictionary<string, string> parameters, int timeout, string userAgent, CookieCollection cookies)
        public static HttpWebResponse CreatePostHttpResponse(string url, IDictionary<string, string> parameters)
        {
            HttpWebRequest request = null;
            //如果是发送HTTPS请求 
            if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
            {
                //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
                request = WebRequest.Create(url) as HttpWebRequest;
                //request.ProtocolVersion = HttpVersion.Version10;
            }
            else
            {
                request = WebRequest.Create(url) as HttpWebRequest;
            }
            request.Method = "POST";
            request.ContentType = "application/json";

            //设置代理UserAgent和超时
            //request.UserAgent = userAgent;
            //request.Timeout = timeout;

            //if (cookies != null)
            //{
            //    request.CookieContainer = new CookieContainer();
            //    request.CookieContainer.Add(cookies);
            //}
            //发送POST数据 
            if (!(parameters == null || parameters.Count == 0))
            {
                StringBuilder buffer = new StringBuilder();
                int i = 0;
                foreach (string key in parameters.Keys)
                {
                    if (i > 0)
                    {
                        buffer.AppendFormat("&{0}={1}", key, parameters[key]);
                    }
                    else
                    {
                        buffer.AppendFormat("{0}={1}", key, parameters[key]);
                        i++;
                    }
                }
                byte[] data = Encoding.ASCII.GetBytes(buffer.ToString());
                using (Stream stream = request.GetRequestStream())
                {
                    stream.Write(data, 0, data.Length);
                }
            }
            string[] values = request.Headers.GetValues("Content-Type");
            return request.GetResponse() as HttpWebResponse;
        }

        /// <summary>
        /// 获取请求的数据
        /// </summary>
        public static string GetResponseString(HttpWebResponse webresponse)
        {
            using (Stream s = webresponse.GetResponseStream())
            {
                StreamReader reader = new StreamReader(s, Encoding.UTF8);
                return reader.ReadToEnd();

            }
        }

        /// <summary>
        /// 验证证书
        /// </summary>
        private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        {
            if (errors == SslPolicyErrors.None)
                return true;
            return false;
        }
    }
}

View Code

再写一个通用返回体,c#里的泛型真是太好用了,居然可以ResponseMsg<List<Backorder>>这样直接与json之间直接转换。code应该设置个枚举值。这里偷懒了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/// <summary>
/// 通用返回体,用于服务器交互
/// </summary>
namespace ordermanage.Common
{
    public class ResponseMsg<T>
    {
        public int code { get; set; }
        public string msg { get; set; }
        public T data { get; set; }
        public int mark1;
        public string mark2;
    }
}

再封装几个方法用与网络请求,并返回ResponseMsg类型。服务端接口是与之对应的返回体。code,msg,data三个字段。mark1,mark2备用,如部分接口需要传回总记录数或总页数等。

getSign是签名认证方法,对服务器端接口进行保护,大概就是流水和参数拼接,再字典排序,再加密。这里就不放出来了
private static async Task<string> getServerResponseAsync(string method, string otherParams) {
            string ts = getTimeStamp();
            string transid = getTrnasID(ts);
            string sign = getSign(transid);
            string strParams = "method=" + method  + "&transid="+transid+"&ts="+ts + "&sign="+sign;
            if (!string.IsNullOrEmpty(otherParams)) {
                strParams += "&" + otherParams;
            }
            try
            {
                string result = await HttpRequestHelper.HttpGetAsync(ServerUrl, strParams);
                return result;
            }
            catch (Exception e) {
                return "{\"code\":500,\"msg\":\"" + e.Message + "\",\"data\":null}";
            }
            
        }

        private static string getServerResponseSync(string method, string otherParams)
        {
            string ts = getTimeStamp();
            string transid = getTrnasID(ts);
            string sign = getSign(transid);
            string strParams = "method=" + method + "&transid=" + transid + "&ts=" + ts + "&sign=" + sign;
            if (!string.IsNullOrEmpty(otherParams))
            {
                strParams += "&" + otherParams;
            }
            try
            {
                string result = HttpRequestHelper.HttpGet(ServerUrl, strParams);
                return result;
            }
            catch (Exception e)
            {
                return "{\"code\":500,\"msg\":\"" + e.Message + "\",\"data\":null}";
            }

        }
        private static string getServerResponseWithPostMethod(string method, string otherParams) {
            string ts = getTimeStamp();
            string transid = getTrnasID(ts);
            string sign = getSign(transid);
            string strParams = "method=" + method + "&transid=" + transid + "&ts=" + ts + "&sign=" + sign;
            if (!string.IsNullOrEmpty(otherParams))
            {
                strParams += "&" + otherParams;
            }
            try
            {
                string result = HttpRequestHelper.HttpPost(ServerUrl, strParams);
                return result;
            }
            catch (Exception e)
            {
                return "{\"code\":500,\"msg\":\"" + e.Message + "\",\"data\":null}";
            }
        }

本地数据缓存使用的是SQLite,SqLiteBaseRepository类用来获取Connection,如果数据库文件不存在,则从安装目录拷贝一份空库。

 

namespace ordermanage.DB
{
    public class SqLiteBaseRepository
    {
        public static string DbFile
        {
            get { return Environment.CurrentDirectory + "\\orderdb.sqlite"; }
        }

        public static SQLiteConnection DbConnection()
        {
            //如果数据库文件不存在,则从源位置复制一份
            string dbFolder = @"C:\退单管理";
            string dbFileName = "orderdb.sqlite";
            if (!Directory.Exists(dbFolder)) {
                Directory.CreateDirectory(dbFolder);
            }
            if (!File.Exists(dbFolder + "\\" + dbFileName)) {
                File.Copy(DbFile, dbFolder + "\\orderdb.sqlite",true);
            }
            return new SQLiteConnection("Data Source=" + dbFolder + "\\" + dbFileName + ";Pooling=true;FailIfMissing=false;Version=3;UTF8Encoding=True;Journal Mode=Off;");
        }
    }
}

 

业务比较简,没有分层,在业务逻辑层写了SQL语句。就一张表,所有业务全部在BackorderBLL里了。

元旦三天假期,实现一个电商退单管理系统【二】

using Dapper;
using ordermanage.Common;
using ordermanage.Model;
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;

namespace ordermanage.DB
{
    public class BackorderBLL
    {
        private SQLiteConnection conn = null;
        public BackorderBLL() {
            conn = SqLiteBaseRepository.DbConnection();
        }

        /// <summary>
        /// 根据快递公司获取所有退单列表
        /// </summary>
        /// <param name="express_id"></param>
        /// <returns></returns>
        public ResponseMsg<List<Backorder>> getBackorderList(int express_id) {
            conn.Open();
            List<Backorder> list = conn.Query<Backorder>(@"SELECT * FROM tb_backorder WHERE express_id=@express_id ORDER BY backorder_date DESC", new { express_id}).ToList();
            conn.Close();
            return new ResponseMsg<List<Backorder>>() {code=100,msg="",data=list };
        }

        /// <summary>
        /// 获取待同步清单(指定快递公司)
        /// </summary>
        /// <param name="express_id"></param>
        /// <returns></returns>
        public ResponseMsg<List<Backorder>> getWaitforSyncBackorderList(int express_id) {
            conn.Open();
            List<Backorder> list = conn.Query<Backorder>(@"SELECT * FROM tb_backorder WHERE express_id=@express_id AND sync_flag=0 ORDER BY backorder_date ASC", new { express_id }).ToList();
            conn.Close();
            return new ResponseMsg<List<Backorder>>() { code = 100, msg = "", data = list };
        }

        /// <summary>
        /// 获取待同步服务器订单(所有快递公司)
        /// </summary>
        /// <returns></returns>
        public ResponseMsg<List<Backorder>> getWaitforSyncBackorderList()
        {
            conn.Open();
            List<Backorder> list = conn.Query<Backorder>(@"SELECT * FROM tb_backorder WHERE sync_flag=0 ORDER BY backorder_date ASC").ToList();
            conn.Close();
            return new ResponseMsg<List<Backorder>>() { code = 100, msg = "", data = list };
        }

        /// <summary>
        /// 新增一行退单
        /// </summary>
        /// <param name="order"></param>
        /// <returns></returns>
        public ResponseMsg<Backorder> addNewBackorder(Backorder order) {
            conn.Open();
            //如果订单长度不符合要求的话?
            if (order.backorder_code.Length < 12)
            {
                conn.Close();
                return new ResponseMsg<Backorder> { code = 202, msg = "快递单号长度不符合要求", data = null };
            }
            else
            {
                //如果订单号存在的话?
                
                Backorder backorder = conn.Query<Backorder>(@"SELECT * FROM tb_backorder WHERE backorder_code=@backorder_code", new { order.backorder_code }).FirstOrDefault();
                if (backorder != null)
                {
                    conn.Close();
                    return new ResponseMsg<Backorder> { code = 203, msg = "快递单号已存在", data = null };
                }
                else
                {
                    string sql = "INSERT INTO tb_backorder(backorder_code,backorder_date,userid,express_id,remark,seq_no) VALUES (@backorder_code,@backorder_date,@userid,@express_id,@remark,@seq_no)";
                    int result = conn.Execute(sql, new { order.backorder_code, order.backorder_date, order.userid, order.express_id, order.remark,order.seq_no });
                    //单机模式,可以立即获取当前记录
                    order = conn.Query<Backorder>("SELECT * FROM tb_backorder ORDER BY backorder_id DESC").FirstOrDefault();
                    //同时更新今日退单数量
                    int count = conn.Query<int>("SELECT * FROM tb_backorder WHERE express_id=? AND backorder_date between datetime('now','start of day','+0 seconds') and  datetime('now','start of day','+1 days','-1 seconds')", new { order.express_id }).Count();
                    //再同时更新待同步服务器数量
                    int count2 = conn.Query<int>("SELECT * FROM tb_backorder WHERE express_id=? AND sync_flag=0", new { order.express_id }).Count();
                    conn.Close();
                    return new ResponseMsg<Backorder> { code = 100, msg = "", data = order, mark1 = count,mark2=count2.ToString() };
                }
            }
        }

        //更新一个订单的同步状态和备注
        public ResponseMsg<bool> updateBackorderSysncStatus(Backorder backorder) {
            string sql = "UPDATE tb_backorder SET sync_flag=@sync_flag,remark=@remark WHERE backorder_code=@backorder_code";
            int result = conn.Execute(sql, new { backorder.sync_flag,backorder.remark,backorder.backorder_code});
            conn.Close();
            return new ResponseMsg<bool>() { code = 100, msg = "", data = result > 0 };
        }

        /// <summary>
        /// 当日退单数量及待同步服务的订单数
        /// </summary>
        /// <param name="express_id"></param>
        /// <returns></returns>
        public ResponseMsg<int> getDayBackorderCount(int express_id) {
            conn.Open();
            int count = conn.Query<int>("SELECT * FROM tb_backorder WHERE express_id=? AND backorder_date between datetime('now','start of day','+0 seconds') and  datetime('now','start of day','+1 days','-1 seconds')", new { express_id }).Count();
            int count2 = conn.Query<int>("SELECT * FROM tb_backorder WHERE express_id=? AND sync_flag=0",new { express_id}).Count();
            conn.Close();
            return new ResponseMsg<int>() { code = 100, msg = "", data = count, mark1 = count2 };
        }
        /// <summary>
        /// 统计当日退单数量及待同步服务器数量(所有快递公司)
        /// </summary>
        /// <returns></returns>
        public ResponseMsg<int> getDayBackorderCount() {
            conn.Open();
            int count = conn.Query<int>("SELECT * FROM tb_backorder WHERE backorder_date between datetime('now','start of day','+0 seconds') and  datetime('now','start of day','+1 days','-1 seconds')").Count();
            int count2 = conn.Query<int>("SELECT * FROM tb_backorder WHERE sync_flag=0").Count();
            conn.Close();
            return new ResponseMsg<int>() { code = 100, msg = "", data = count, mark1 = count2 };
        }

        /// <summary>
        /// 执行一条SQL语句
        /// </summary>
        public void ExecuteSql(string sql) {
            try
            {
                conn.Execute(sql);
            }
            catch (Exception)
            {

            }
        }
    }
}

View Code

 

 

2. 登录实现

元旦三天假期,实现一个电商退单管理系统【二】

好了,接下来就可以调用网络方法了,通过NewtonSoft.json转换为通用返回对象,以系统登录为例:

/// <summary>
        /// 登录方法
        /// </summary>
        /// <param name="user_code"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public static async Task<ResponseMsg<UserModel>> Login(string user_code, string password) {
            string result= await getServerResponseAsync("login", "uname=" + user_code + "&ucode=" + password);
            return JsonConvert.DeserializeObject<ResponseMsg<UserModel>>(result);
        }

 

登录界面:

元旦三天假期,实现一个电商退单管理系统【二】

<Window x:Class="ordermanage.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ordermanage"
        xmlns:uc="clr-namespace:ordermanage.UC"
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:common="clr-namespace:ordermanage.Common"
        mc:Ignorable="d"
        DataContext="{Binding Source={StaticResource Locator},Path=Main}"
        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
        TextElement.FontWeight="Medium"
        TextElement.FontSize="14"
        WindowStartupLocation="CenterScreen"
        FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
        Title="MainWindow" Height="450" Width="800" ResizeMode="NoResize" WindowStyle="None" MouseLeftButtonDown="Window_MouseLeftButtonDown" Icon="Images/扫码-01.png">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.DialogHost.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Window.Background>
        <ImageBrush ImageSource="Images/bg.jpg"/>
    </Window.Background>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="300"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"></ColumnDefinition>
            <ColumnDefinition Width="600"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <uc:LoadingWait x:Name="_loading" Visibility="Collapsed"
                        Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Grid.ColumnSpan="3"
                        Height="450" Width="800"
                        HorizontalAlignment="Center" VerticalAlignment="Center"></uc:LoadingWait>
        <TextBlock Text="电商退单管理系统" Grid.Row="0" Grid.Column="1"
                   VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFFEFE" FontSize="24" FontWeight="Bold"
                   ></TextBlock>
        <materialDesign:PackIcon Kind="Close" Foreground="White"
                                 Grid.Row="0" Grid.Column="2"
                                 Cursor="Hand" 
                                 HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" Height="24" Width="24"                                  
                                 >
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonDown">
                    <i:InvokeCommandAction Command="{Binding ExitCommand}"></i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </materialDesign:PackIcon>
        <materialDesign:PackIcon Kind="Cog" Grid.Row="0" Grid.Column="2"
                                 Width="24" Height="24" Foreground="White"
                                 VerticalAlignment="Center" HorizontalAlignment="Right"
                                 Margin="0,0,35,0" Cursor="Hand">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonDown">
                    <i:InvokeCommandAction Command="{Binding SystemSetCommand}"></i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </materialDesign:PackIcon>
        <TextBox Name="txtUserCode" Grid.Row="1" Grid.Column="1" Width="260"
                 VerticalAlignment="Center" HorizontalAlignment="Center"
                 Style="{StaticResource MaterialDesignFloatingHintTextBox}"
                 materialDesign:HintAssist.Hint="登录名"
                 Text="{Binding UserCode}"
                 Foreground="#FF030B55" FontWeight="Bold"></TextBox>
        <PasswordBox x:Name="txtPassword" Grid.Row="1" Grid.Column="1"
                     VerticalAlignment="Center" HorizontalAlignment="Center"
                     Width="260" Margin="0,110,0,0"
                     common:PasswordBoxHelper.Password="{Binding Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                     materialDesign:HintAssist.Hint="密码" Foreground="#FF030B55"
                     Style="{StaticResource MaterialDesignFloatingHintPasswordBox}" FontWeight="Bold"
                     ></PasswordBox>
        <Button Name="btnLogin" Grid.Row="3" Grid.Column="1" Content="登录"
                VerticalAlignment="Top"
                Width="100"
                IsDefault="True"
                Command="{Binding LoginCommand}" Click="btnLogin_Click"
                 ></Button>
    </Grid>
</Window>

View Code

登录viewmodel:

元旦三天假期,实现一个电商退单管理系统【二】

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using ordermanage.Common;
using ordermanage.Model;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace ordermanage.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            ////if (IsInDesignMode)
            ////{
            ////    // Code runs in Blend --> create design time data.
            ////}
            ////else
            ////{
            ////    // Code runs "for real"
            ////}
        }
        private string _usercode;
        private string _password;

        public string UserCode {
            get { return _usercode; }
            set { _usercode = value;RaisePropertyChanged(); }
        }
        public string Password {
            get { return _password; }
            set { _password = value;RaisePropertyChanged(); }
        }
        /// <summary>
        /// 退出程序
        /// </summary>
        public RelayCommand ExitCommand {
            get {
                return new RelayCommand(() => {
                    Messenger.Default.Send<string>("exit", "ApplicationExitToken");
                });
            }
        }
        /// <summary>
        /// 系统设置
        /// </summary>
        public RelayCommand SystemSetCommand {
            get {
                return new RelayCommand(()=> { });
            }
        }
        public RelayCommand LoginCommand {
            get {
                return new RelayCommand(() => {
                    if (string.IsNullOrEmpty(UserCode) || string.IsNullOrEmpty(Password))
                    {
                        Messenger.Default.Send<ResponseMsg<UserModel>>(new ResponseMsg<UserModel>() { code = 200, msg = "用户名密码不能为空", data = null }, "LoginToken");
                    }
                    else {
                        Login();
                    }
                });
            }
        }
        /// <summary>
        /// 登录实现
        /// </summary>
        private async void Login() {
            ResponseMsg<UserModel> responseMsg = await HttpMethod.Login(UserCode, Password);
            Messenger.Default.Send<ResponseMsg<UserModel>>(responseMsg, "LoginToken");
        }
    }
}

View Code

登录cs代码:

元旦三天假期,实现一个电商退单管理系统【二】

using GalaSoft.MvvmLight.Messaging;
using ordermanage.Common;
using ordermanage.Model;
using System;
using System.Windows;
using System.Configuration;
using ordermanage.View;

namespace ordermanage
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Messenger.Default.Register<string>(this, "ApplicationExitToken", AppExit);
            Messenger.Default.Register<ResponseMsg<UserModel>>(this, "LoginToken", Login);
        }


        private void Login(ResponseMsg<UserModel> res)
        {
            this._loading.Visibility = Visibility.Collapsed;
            if (res.code == 100)
            {
                //登录成功
                UserModel user = res.data;
                Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                config.AppSettings.Settings["userid"].Value = user.userid.ToString();
                config.AppSettings.Settings["user_code"].Value = user.user_code;
                config.AppSettings.Settings["user_name"].Value = user.user_name;
                config.Save(ConfigurationSaveMode.Modified);
                ConfigurationManager.RefreshSection("appSettings");
                SelectExpress selectExpress = new SelectExpress();
                selectExpress.Show();
                this.Close();
            }
            else {
                //登录失败,显示失败信息
                MessageBox.Show(res.msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void AppExit(string obj)
        {
            if (MessageBox.Show("确实要退出程序吗?", "提示", MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.OK) == MessageBoxResult.OK) {
                this.Close();
            }
        }

        public static void openWindow()
        {
            SelectExpress selectExpress = new SelectExpress();
            selectExpress.Show();
        }


        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
        }

        private void WindowsHander_WindowsEvent1()
        {
            throw new NotImplementedException();
        }

        private void Window_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            this.DragMove();
        }

        private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            this._loading.Visibility = Visibility.Visible;
        }
    }
}

View Code

 3. 选择快递

元旦三天假期,实现一个电商退单管理系统【二】

快递公司不太多,图片也没有异步获取了。

 界面布局

元旦三天假期,实现一个电商退单管理系统【二】

<Window x:Class="ordermanage.View.SelectExpress"
        x:Name="SelectExpressWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ordermanage.View"
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:common="clr-namespace:ordermanage.Common"
        DataContext="{Binding Source={StaticResource Locator},Path=SelectExpress}"
        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
        TextElement.FontWeight="Medium"
        TextElement.FontSize="16"
        FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
        mc:Ignorable="d"
        ResizeMode="NoResize"
        WindowStartupLocation="CenterScreen"
        Title="SelectExpress" Height="600" Width="1000" WindowStyle="None" Activated="SelectExpressWindow_Activated">
    <Window.Background>
        <ImageBrush ImageSource="/ordermanage;component/Images/bg.jpg"/>
    </Window.Background>
    <Window.Resources>
        
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60"></RowDefinition>
            <RowDefinition Height="60"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <TextBlock Text="请选择需要扫码退单的快递公司" Grid.Row="0"
                   FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"
                   Foreground="LawnGreen">
        </TextBlock>
        <materialDesign:PackIcon Kind="Close" Foreground="White"
                                 Grid.Row="0"
                                 Cursor="Hand" 
                                 Background="LightSeaGreen"
                                 Opacity="0.5"
                                 HorizontalAlignment="Right" VerticalAlignment="Center" 
                                 Margin="0,0,6,0" Height="24" Width="24">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonDown">
                    <i:InvokeCommandAction Command="{Binding ExitCommand}"></i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </materialDesign:PackIcon>
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
            <TextBlock VerticalAlignment="Top" x:Name="tbInfo"></TextBlock>
            <Button Margin="10,0,0,0" VerticalAlignment="Top" Content="同步服务器" x:Name="btnSync" Click="btnSync_Click"></Button>
        </StackPanel>
        <ListBox x:Name="ImageList" Grid.Row="2">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="4"></UniformGrid>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Button Width="240" Height="Auto" 
                            Command="{Binding DataContext.ExpressImageCommand,ElementName=SelectExpressWindow}"
                            CommandParameter="{Binding express_id}"
                            BorderThickness="0" Background="Transparent">
                        <Image Stretch="Fill" Source="{Binding Path=express_log}">
                        </Image>
                    </Button>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

View Code

有点不太习惯c#的双向绑定方式,感觉不如vue方便。所以大部分代码写到了cs文件里

元旦三天假期,实现一个电商退单管理系统【二】

using GalaSoft.MvvmLight.Messaging;
using ordermanage.Common;
using ordermanage.DB;
using ordermanage.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Windows;

namespace ordermanage.View
{
    /// <summary>
    /// SelectExpress.xaml 的交互逻辑
    /// </summary>
    public partial class SelectExpress : Window
    {
        private string ServerUrl {
            get {
                return ConfigurationManager.AppSettings["server_url"];
            }
        }
        public SelectExpress()
        {
            InitializeComponent();
            Messenger.Default.Register<int>(this, "SelectExpressToken", openWindow);
            Messenger.Default.Register<string>(this, "SelectApplicationExitToken", AppExit);
            ShowInfo();
            ResponseMsg<List<ExpressModel>> response = HttpMethod.ExpressList();
            if (response.code == 100)
            {
                List<ExpressModel> list = response.data;
                for (int i = 0; i < list.Count(); i++)
                {
                    list[i].express_log = this.ServerUrl + "/Public/Uploads/express/" + list[i].express_log;
                }
                this.ImageList.ItemsSource = list;
            }
            //UpdateTable();
        }

        /// <summary>
        /// 首次打开,升级数据库
        /// </summary>
        private void UpdateTable()
        {
            var update_sql = ConfigurationManager.AppSettings["update_sql"];
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            if (string.IsNullOrEmpty(update_sql)) {
                MessageBox.Show("文件读取权限出现问题,请以管理员身份打开"+ConfigurationManager.AppSettings["userid"], "提示", MessageBoxButton.OK, MessageBoxImage.Error);
                this.Close();
            }
            if ("1".Equals(update_sql))
            {
                //更新数据库
                string sql = config.AppSettings.Settings["sql"].Value;
                if (!string.IsNullOrEmpty(sql))
                {
                    new BackorderBLL().ExecuteSql(sql);
                    //更新配置
                    config.AppSettings.Settings["update_sql"].Value = "0";
                    config.Save(ConfigurationSaveMode.Modified);
                    ConfigurationManager.RefreshSection("appSettings");
                }
            }
        }

        private void ShowInfo()
        {
            //统计信息
            ResponseMsg<int> Count = new BackorderBLL().getDayBackorderCount();
            this.tbInfo.Text = string.Format("今日共录入退单:{0}件,待同步服务器:{1}件", Count.data, Count.mark1);
            if (Count.mark1 > 0)
            {
                this.btnSync.Visibility = Visibility.Visible;
            }
            else
            {
                this.btnSync.Visibility = Visibility.Collapsed;
            }
        }

        private void AppExit(string obj)
        {
            string tips = "确实要退出程序吗?";
            int count = new BackorderBLL().getDayBackorderCount().mark1;
            if (count > 0) {
                tips = string.Format("你还有{0}条记录待同步至服务器,确定要退出了吗?",count);
            }
            if (MessageBox.Show(tips, "提示", MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.OK) == MessageBoxResult.OK)
            {
                this.Close();
            }
        }

        private void openWindow(int obj)
        {
            Home home = new Home(obj);
            home.txtExpressID.Text = obj.ToString();
            home.ShowDialog();
        }

        private void btnSync_Click(object sender, RoutedEventArgs e)
        {
            //调用网络同步订单
            List<Backorder> list = new BackorderBLL().getWaitforSyncBackorderList().data;
            ResponseMsg<List<Backorder>> response = HttpMethod.SyncBackorders(list);
            if (response.code == 100)
            {
                //同步成功,刷新本地数据库状态
                foreach (Backorder order in response.data)
                {
                    bool result = new BackorderBLL().updateBackorderSysncStatus(order).data;
                    if (result)
                    {
                        //本地库更新成功
                    }
                }
                //刷新按钮上的文字 
                ShowInfo();
                MessageBox.Show("同步成功", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            else
            {
                MessageBox.Show(response.msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void SelectExpressWindow_Activated(object sender, EventArgs e)
        {
            this.ShowInfo();
        }
    }
}

View Code

 

  4. 退货单入库

元旦三天假期,实现一个电商退单管理系统【二】

监听扫码输入代码是从网上找的,会监听所有输入,包括键盘等外接设备输入,对单号做了一定规则判断:

ScanHook

元旦三天假期,实现一个电商退单管理系统【二】

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace ordermanage.Common
{
    public class ScanHook
    {
        public delegate void ScanerDelegate(ScanerCodes codes);
        public event ScanerDelegate ScanerEvent;
        delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
        private int hKeyboardHook = 0;
        private ScanerCodes codes = new ScanerCodes();
        private HookProc hookproc;
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern bool UnhookWindowsHookEx(int idHook);
        [DllImport("user32", EntryPoint = "GetKeyNameText")]
        private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize);
        [DllImport("user32", EntryPoint = "GetKeyboardState")]
        private static extern int GetKeyboardState(byte[] pbKeyState);
        [DllImport("user32", EntryPoint = "ToAscii")]
        private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags);
        [DllImport("kernel32.dll")]
        public static extern IntPtr GetModuleHandle(string name);
        public ScanHook()
        {
        }
        public bool Start()
        {
            if (hKeyboardHook == 0)
            {
                hookproc = new HookProc(KeyboardHookProc);
                //GetModuleHandle 函数 替代 Marshal.GetHINSTANCE
                //防止在 framework4.0中 注册钩子不成功
                IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
                //WH_KEYBOARD_LL=13
                //全局钩子 WH_KEYBOARD_LL
                //  hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
                hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0);
            }
            return (hKeyboardHook != 0);
        }
        public bool Stop()
        {
            if (hKeyboardHook != 0)
            {
                return UnhookWindowsHookEx(hKeyboardHook);
            }
            return true;
        }
        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg));
            codes.Add(msg);
            if (ScanerEvent != null && msg.message == 13 && msg.paramH > 0 && !string.IsNullOrEmpty(codes.Result))
            {
                ScanerEvent(codes);
            }
            return 0;
        }
        public class ScanerCodes
        {
            private int ts = 300; // 指定输入间隔为300毫秒以内时为连续输入
            private List<List<EventMsg>> _keys = new List<List<EventMsg>>();
            private List<int> _keydown = new List<int>();   // 保存组合键状态
            private List<string> _result = new List<string>();  // 返回结果集
            private DateTime _last = DateTime.Now;
            private byte[] _state = new byte[256];
            private string _key = string.Empty;
            private string _cur = string.Empty;
            public EventMsg Event
            {
                get
                {
                    if (_keys.Count == 0)
                    {
                        return new EventMsg();
                    }
                    else
                    {
                        return _keys[_keys.Count - 1][_keys[_keys.Count - 1].Count - 1];
                    }
                }
            }
            public List<int> KeyDowns
            {
                get
                {
                    return _keydown;
                }
            }
            public DateTime LastInput
            {
                get
                {
                    return _last;
                }
            }
            public byte[] KeyboardState
            {
                get
                {
                    return _state;
                }
            }
            public int KeyDownCount
            {
                get
                {
                    return _keydown.Count;
                }
            }
            public string Result
            {
                get
                {
                    if (_result.Count > 0)
                    {
                        return _result[_result.Count - 1].Trim();
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            public string CurrentKey
            {
                get
                {
                    return _key;
                }
            }
            public string CurrentChar
            {
                get
                {
                    return _cur;
                }
            }
            public bool isShift
            {
                get
                {
                    return _keydown.Contains(160);
                }
            }
            public void Add(EventMsg msg)
            {
                #region 记录按键信息
                // 首次按下按键
                if (_keys.Count == 0)
                {
                    _keys.Add(new List<EventMsg>());
                    _keys[0].Add(msg);
                    _result.Add(string.Empty);
                }
                // 未释放其他按键时按下按键
                else if (_keydown.Count > 0)
                {
                    _keys[_keys.Count - 1].Add(msg);
                }
                // 单位时间内按下按键
                else if (((TimeSpan)(DateTime.Now - _last)).TotalMilliseconds < ts)
                {
                    _keys[_keys.Count - 1].Add(msg);
                }
                // 从新记录输入内容
                else
                {
                    _keys.Add(new List<EventMsg>());
                    _keys[_keys.Count - 1].Add(msg);
                    _result.Add(string.Empty);
                }
                #endregion
                _last = DateTime.Now;
                #region 获取键盘状态
                // 记录正在按下的按键
                if (msg.paramH == 0 && !_keydown.Contains(msg.message))
                {
                    _keydown.Add(msg.message);
                }
                // 清除已松开的按键
                if (msg.paramH > 0 && _keydown.Contains(msg.message))
                {
                    _keydown.Remove(msg.message);
                }
                #endregion
                #region 计算按键信息
                int v = msg.message & 0xff;
                int c = msg.paramL & 0xff;
                StringBuilder strKeyName = new StringBuilder(500);
                if (GetKeyNameText(c * 65536, strKeyName, 255) > 0)
                {
                    _key = strKeyName.ToString().Trim(new char[] { ' ', '\0' });
                    GetKeyboardState(_state);
                    if (_key.Length == 1 && msg.paramH == 0)
                    {
                        // 根据键盘状态和shift缓存判断输出字符
                        _cur = ShiftChar(_key, isShift, _state).ToString();
                        _result[_result.Count - 1] += _cur;
                    }
                    else
                    {
                        _cur = string.Empty;
                    }
                }
                #endregion
            }
            private char ShiftChar(string k, bool isShiftDown, byte[] state)
            {
                bool capslock = state[0x14] == 1;
                bool numlock = state[0x90] == 1;
                bool scrolllock = state[0x91] == 1;
                bool shiftdown = state[0xa0] == 1;
                char chr = (capslock ? k.ToUpper() : k.ToLower()).ToCharArray()[0];
                if (isShiftDown)
                {
                    if (chr >= 'a' && chr <= 'z')
                    {
                        chr = (char)((int)chr - 32);
                    }
                    else if (chr >= 'A' && chr <= 'Z')
                    {
                        chr = (char)((int)chr + 32);
                    }
                    else
                    {
                        string s = "`1234567890-=[];',./";
                        string u = "~!@#$%^&*()_+{}:\"<>?";
                        if (s.IndexOf(chr) >= 0)
                        {
                            return (u.ToCharArray())[s.IndexOf(chr)];
                        }
                    }
                }
                return chr;
            }
        }
        public struct EventMsg
        {
            public int message;
            public int paramL;
            public int paramH;
            public int Time;
            public int hwnd;
        }
    }
}

View Code

建立一个事件,当页面Load时开启监听,页面Unload时关闭监听

  private ScanHook listener = new ScanHook();
        private string express_name { get; set; }
        public Home(int express_id)
        {
            InitializeComponent();
            listener.ScanerEvent += Listener_ScanerEvent;
           
        }

 

private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            listener.Start();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            listener.Stop();
            Messenger.Default.Unregister(this);
        }

 

元旦三天假期,实现一个电商退单管理系统【二】

private void Listener_ScanerEvent(ScanHook.ScanerCodes codes)
        {
            //codes.KeyDownCount, codes.Event.message, codes.Event.paramH, codes.Event.paramL, codes.CurrentChar, codes.Result, codes.isShift, codes.CurrentKey
            //先入库 
            MediaPlayer player = new MediaPlayer();
            string userid = ConfigurationManager.AppSettings["userid"];
            ResponseMsg<Backorder> result = new DB.BackorderBLL().addNewBackorder(new Backorder { backorder_code=codes.Result,userid=int.Parse(userid),express_id=id,seq_no=this.txtSeqNO.Text,backorder_date=System.DateTime.Now});// 改为存在本地数据库 // HttpMethod.scan(codes.Result, userid,this.txtExpressID.Text);
            if (result.code == 100)
            {
                player.Open(new Uri(Environment.CurrentDirectory + "\\Sound\\success.mp3"));
                this.lstView.Items.Insert(0, result.data);
                //前台订单数量刷新一下
                this.btnShowDetail.Content = string.Format("你今日共退单:{0}件,待同步服务器:{1}件(点击查看清单)",result.mark1,result.mark2);
            }
            else {
                Uri mp3 = new Uri(Environment.CurrentDirectory + "\\Sound\\fail.mp3");
                player.Open(mp3);
                Backorder backorder = new Backorder();
                backorder.backorder_code = codes.Result;
                backorder.backorder_date = System.DateTime.Now;
                backorder.remark = result.msg;
                this.lstView.Items.Insert(0, backorder);
            }
            player.Play();
        }

View Code

 

同步服务器的代码,把当前快递下所有未同步服务器的订单找出来,转成json格式,然后post到服务器端

 //调用网络同步订单
            List<Backorder> list = new BackorderBLL().getWaitforSyncBackorderList(id).data;
            ResponseMsg<List<Backorder>> response = HttpMethod.SyncBackorders(list);
            if (response.code == 100)
            {
                //同步成功,刷新本地数据库状态
                foreach (Backorder order in response.data)
                {
                    bool result = new BackorderBLL().updateBackorderSysncStatus(order).data;
                    if (result) { 
                        //本地库更新成功
                    }
                }
                MessageBox.Show("同步成功", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            else {
                MessageBox.Show(response.msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }

 

目前项目里还有很多硬代码,优化后,再放开github的private。

 

to be continued….

 


开心洋葱 , 版权所有丨如未注明 , 均为原创丨未经授权请勿修改 , 转载请注明元旦三天假期,实现一个电商退单管理系统【二】
喜欢 (0)

您必须 登录 才能发表评论!

加载中……