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


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

    (一)功能分析

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

     

    仓库的客户端需要监听扫码枪输入,计划使用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[] { ' ', '' });
                        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....

  • 相关阅读:
    猴子分香蕉
    打鱼晒网
    质数/素数
    三角形-->九九乘法表
    eclipse 导入gradle引入多模块项目,引入eclipse后变成了好几个工程
    linux 命令基础大全
    SQL Server 增加链接服务器
    Postgresql数据库部署之:Postgresql 存在session 会话不能删除数据库
    Postgresql数据库部署之:Postgresql本机启动和Postgresql注册成windows 服务
    Git常用命令使用大全
  • 原文地址:https://www.cnblogs.com/zhouyu629/p/14233428.html
Copyright © 2020-2023  润新知