1. 程式人生 > >元旦三天假期,實現一個電商退單管理系統【二】

元旦三天假期,實現一個電商退單管理系統【二】

一、倉庫掃碼監聽客戶端實現

(一)功能分析

快遞小哥騎著小三輪,運送退貨快遞到倉庫,庫管開啟客戶端,選擇快遞公司後,遞給快遞一把掃碼槍,小哥滴滴滴,滴滴滴,一頓操作猛如虎,打完收功。倉管將資料提交伺服器,列印回單,整個客戶端流程結束。

 

 

倉庫的客戶端需要監聽掃碼槍輸入,計劃使用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....

&n