瀏覽代碼

no message

zhejiushizhuce 2 年之前
父節點
當前提交
bb29c3a969

+ 1 - 0
package.json

@@ -69,6 +69,7 @@
     "react-select-country-list": "^2.2.3",
     "react-sizeme": "^3.0.2",
     "react-stickynode": "^4.1.0",
+    "react-swipeable-list": "^1.9.3",
     "react-toastify": "^9.1.1",
     "react-use": "^17.4.0",
     "react-use-measure": "^2.1.1",

+ 3 - 2
src/App.js

@@ -44,7 +44,7 @@ import Layout from "@layout/Layout";
 import {getBFP, getFetchStatus, getUserID, getUserInfo} from '@features/mainSlice';
 import {useDispatch, useSelector} from "react-redux";
 import {REQ_STATUS} from "@api/config";
-import {reqCustomService, reqGetRoutes, reqTurnplateIndex, reqWithdrawBaseInfo} from "@features/api";
+import {reqCustomService, reqGetRoutes, reqMailCheck, reqTurnplateIndex, reqWithdrawBaseInfo} from "@features/api";
 import LoadingScreen from "@components/LoadingScreen";
 import {reqPayConfig} from "@features/paySlice";
 import {connectHall, disconnectHall} from "@features/actions/hallClient";
@@ -144,7 +144,8 @@ const App = () => {
             //turnplate
             // dispatch(reqTurnplateIndex());
             //rest code
-            localStorageSet(LOCAL_WHEEL_INVITE_CODE,"")
+            localStorageSet(LOCAL_WHEEL_INVITE_CODE,"");
+            dispatch(reqMailCheck());
         }
         else
         {

+ 11 - 0
src/api/config.js

@@ -57,3 +57,14 @@ export const APP_TURNPLATE_KAN=BASE_PREFIX+"turnplate/kan";
 export const APP_TURNPLATE_BONUS=BASE_PREFIX+"turnplate/bonus";
 //帮别人砍
 export const APP_TURNPLATE_HELP=BASE_PREFIX+"turnplate/help";
+//mail
+// game/mail/list
+// game/mail/check
+// /game/mail/read
+// /game/mail/del
+// /game/mail/take
+export const APP_MAIL_LIST=BASE_PREFIX+"mail/list";
+export const APP_MAIL_CHECK=BASE_PREFIX+"mail/check";
+export const APP_MAIL_READ=BASE_PREFIX+"mail/read";
+export const APP_MAIL_DEL=BASE_PREFIX+"mail/del";
+export const APP_MAIL_TAKE=BASE_PREFIX+"mail/take";

+ 3 - 1
src/app/store.js

@@ -6,6 +6,7 @@ import GameListReducer from '@features/gameListSlice';
 import PayReducer from '@features/paySlice';
 import SocketReducer from '@features/socketSlice';
 import TurnplateReducer from '@features/turnplateSlice';
+import MailReducer from '@features/mailSlice';
 export default configureStore({
     reducer: {
         // todos: TodosReducer,
@@ -14,7 +15,8 @@ export default configureStore({
         gamelist:GameListReducer,
         pay:PayReducer,
         socket:SocketReducer,
-        turnplate:TurnplateReducer
+        turnplate:TurnplateReducer,
+        mail:MailReducer
     },
     middleware:(getDefaultMiddleware => {
         let middlewares=getDefaultMiddleware();

二進制
src/assets/mail/mailStatus1.png


二進制
src/assets/mail/mailStatus2.png


二進制
src/assets/mail/no_data.png


+ 2 - 0
src/assets/singlesvg/dot_more.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" id="Glyph" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M16,13c-1.654,0-3,1.346-3,3s1.346,3,3,3s3-1.346,3-3S17.654,13,16,13z" id="XMLID_287_"/><path d="M6,13c-1.654,0-3,1.346-3,3s1.346,3,3,3s3-1.346,3-3S7.654,13,6,13z" id="XMLID_289_"/><path d="M26,13c-1.654,0-3,1.346-3,3s1.346,3,3,3s3-1.346,3-3S27.654,13,26,13z" id="XMLID_291_"/></svg>

+ 3 - 1
src/constants/routes.js

@@ -24,7 +24,9 @@ const ROUTE_PATH={
     CHANGE_PAY_PASS:"#/changePayPass",
     CUSTOMER:"#/customer",
     PROMOTION:"#/promotion",
-    CASH_WHEEL:"#/cashwheel"
+    CASH_WHEEL:"#/cashwheel",
+    MESSAGE:"#/message",
+    MESSAGE_DETAIL:"#/messageDetail",
 }
 
 export {

+ 26 - 0
src/db/routes.js

@@ -269,4 +269,30 @@ export const routes=[
         lpath:"#/signin",//登录path
         subs:[]
     },
+    {
+        path: "#/message",
+        type: 1,
+        side: 0,
+        block:1,//块排序
+        title: "Message",
+        icon:"",
+        fill:";;",//icon 颜色 默认 选中 本身颜色
+        component:"mail",
+        login:0,//0,1 检不检查登录
+        lpath:"#/signin",//登录path
+        subs:[]
+    },
+    {
+        path: "#/messageDetail",
+        type: 1,
+        side: 0,
+        block:1,//块排序
+        title: "Message Detail",
+        icon:"",
+        fill:";;",//icon 颜色 默认 选中 本身颜色
+        component:"mailDetail",
+        login:0,//0,1 检不检查登录
+        lpath:"#/signin",//登录path
+        subs:[]
+    },
 ]

+ 80 - 7
src/features/api.js

@@ -11,7 +11,7 @@ import {
     APP_GAME_INFO,
     APP_GAME_LIST,
     APP_LOGIN,
-    APP_LOGOUT,
+    APP_LOGOUT, APP_MAIL_CHECK, APP_MAIL_DEL, APP_MAIL_LIST, APP_MAIL_READ, APP_MAIL_TAKE,
     APP_PAGE_MODULES,
     APP_PAYMENT_ORDER,
     APP_RECHARGE_CONFIG,
@@ -25,10 +25,11 @@ import {
 import Http from "@api/request";
 import {updateGameList} from "@features/pageSlice";
 import {APP_CHANNEL} from "@constants/app";
-import {updateInsurePass, updateUserInfo} from "@features/mainSlice";
-import {localStorageGet} from "@utils/helpers";
+import {updateInsurePass, updateUserInfo, updateUserScore} from "@features/mainSlice";
+import {formatCash, localStorageGet, sleep} from "@utils/helpers";
 import {LOCAL_CUSTOM_LOCK, LOCAL_WHEEL_INVITE_CODE} from "@constants/localkey";
 import {toast} from "react-toastify";
+import {preDelMail} from "@features/mailSlice";
 const i18n=global.i18n;
 export const buildReqReducer=(builder,thunk,callback,statusCB=null)=>{
     if (builder==null||thunk==null)return;
@@ -215,7 +216,7 @@ export const reqFirstPayConfig=createAsyncThunk(
             params:{
                 Channel:APP_CHANNEL.Channel,
                 ChannelNumber:APP_CHANNEL.ChannelNumber,
-                UserID:arg
+                // UserID:arg
             }
         }
         const response=await Http.get(params);
@@ -228,7 +229,7 @@ export const reqGetAccountsPayInfo=createAsyncThunk(
         let params={
             url:APP_ACCOUNTS_PAY_INFO,
             params:{
-                UserID:arg
+                // UserID:arg
             }
         }
         const response=await Http.get(params);
@@ -242,7 +243,7 @@ export const reqPaymentOrder=createAsyncThunk(
         let params={
             url:APP_PAYMENT_ORDER,
             params:{
-                userID:user.UserID,
+                // userID:user.UserID,
                 tranType:"0502",
                 payAmt:price,
                 goodsName:goodsName,
@@ -291,7 +292,7 @@ export const reqWithdrawInfo=createAsyncThunk(
         let params={
             url:APP_WITHDRAW_INFO,
             params:{
-                UserID:UserID
+                // UserID:UserID
             }
         }
         const response=await Http.get(params);
@@ -403,4 +404,76 @@ export const reqTurnplateHelp=createAsyncThunk(
         return response;
     }
 )
+//mail
+export const reqMailList=createAsyncThunk(
+    APP_MAIL_LIST,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_MAIL_LIST
+        }
+        const response=await Http.get(params);
+        return response;
+    }
+)
+export const reqMailCheck=createAsyncThunk(
+    APP_MAIL_CHECK,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_MAIL_CHECK
+        }
+        const response=await Http.get(params);
+        return response;
+    }
+)
+export const reqMailRead=createAsyncThunk(
+    APP_MAIL_READ,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_MAIL_READ,
+            params:{
+                MailID:arg
+            }
+        }
+        const response=await Http.get(params);
+        return response;
+    }
+)
+export const reqMailDel=createAsyncThunk(
+    APP_MAIL_DEL,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_MAIL_DEL,
+            params:{
+                MailID:arg
+            }
+        }
+        thunkAPI.dispatch(preDelMail(arg));
+        const response=await Http.get(params);
+        // const response={code:200};
+        return response;
+    }
+)
+export const reqMailTake=createAsyncThunk(
+    APP_MAIL_TAKE,
+    async (arg,thunkAPI)=>{
+        let params={
+            url:APP_MAIL_TAKE,
+            params:{
+                MailID:arg
+            }
+        }
+        const response=await Http.get(params);
+        if (response&&response.code===200){
+            let data=response.data;
+            if (data){
+                let NowScore=data.NowScore;
+                if (NowScore!=null){
+                    NowScore=Number(formatCash(NowScore));
+                    thunkAPI.dispatch(updateUserScore(NowScore));
+                }
+            }
+        }
+        return response;
+    }
+)
 //api

File diff suppressed because it is too large
+ 118 - 0
src/features/mailSlice.js


+ 8 - 2
src/features/mainSlice.js

@@ -341,7 +341,13 @@ export const Main=createSlice({
         },
         updateUserInfo:(state, action)=>{
             parseLogin(state,action,{},false);
-        }
+        },
+        updateUserScore:(state, action)=>{
+            let score=action.payload;
+            if (score!=null){
+                if(state.userInfo)state.userInfo.Score=Number(score);
+            }
+        },
     },
     extraReducers: (builder) => {
         // Add reducers for additional action types here, and handle loading state as needed
@@ -359,7 +365,7 @@ export const Main=createSlice({
         buildReqReducer(builder,reqCustomLeaveMsg,reducerCustomLeaveMsg,customLeaveMsgStatus);
     },
 })
-export const {logout,updateUserInfo} = Main.actions;
+export const {logout,updateUserInfo,updateUserScore} = Main.actions;
 //数据获取
 export const getFetchStatus=(state)=>{
     return state.main.status;

+ 8 - 3
src/layout/BottomNav/index.jsx

@@ -18,6 +18,8 @@ import {ReactComponent as TabPay} from '@assets/tabicon/pay.svg';
 import {ReactComponent as TabMessage} from '@assets/tabicon/message.svg';
 import {ReactComponent as TabSett} from '@assets/tabicon/sett.svg';
 import {ROUTE_PATH} from "@constants/routes";
+import {useSelector} from "react-redux";
+import {getMailNew} from "@features/mailSlice";
 const i18n=global.i18n;
 
 const BottomNav = () => {
@@ -25,6 +27,7 @@ const BottomNav = () => {
     const [settingsPopupOpen, setSettingsPopupOpen] = useState(false);
     const isStoreRoute = useStoreRoute();
     const {setCartOpen} = useShopProvider();
+    const mailNew=useSelector(getMailNew);
 
     // return (
     //     <div className={styles.container}>
@@ -87,15 +90,17 @@ const BottomNav = () => {
                 </div>
                 <span className={styles.yellow_txt}>{i18n.t("tab.deposit")}</span>
             </NavLink>
-            <button className={styles.button}>
+            <NavLink className={styles.button} to={ROUTE_PATH.MESSAGE}>
+                {
+                    mailNew>0?(<div className={styles["dont-badge"]}></div>):(<></>)
+                }
                 {/*<svg className={"icon"}>*/}
                 {/*    <use xlinkHref={`${symbol_m}#icon_Account`} />*/}
                 {/*</svg>*/}
                 <TabMessage className={styles.icon}/>
                 <span>{i18n.t("tab.message")}</span>
-            </button>
+            </NavLink>
             <NavLink className={styles.button} to={ROUTE_PATH.CUSTOMER}>
-                <div className={styles["dont-badge"]}></div>
                 {/*<svg className={"icon"}>*/}
                 {/*    <use xlinkHref={`${symbol}#icon_Chat`} />*/}
                 {/*</svg>*/}

+ 14 - 0
src/locales/languages/en_US.json

@@ -353,5 +353,19 @@
       "btn_share": "Share with friends",
       "btn_giveup": "Give up the withdrawal of {0} BRL"
     }
+  },
+  "mail":{
+    "btn_del": "Delete",
+    "btn_DeleteAll": "Delete all",
+    "btn_Delete": "Delete",
+    "btn_Receive": "Receive",
+
+    "txtMail": {
+      "titleTheme": "Theme:",
+      "titleTime": "Time:"
+    },
+    "tips":"Ops,There are no new emails at the moment!!",
+    "TopTip_qxlqyxjl": "Please receive the mail reward first",
+    "TopTip_jscsyydyj": "All emails that have been read and have received attachment rewards will be deleted"
   }
 }

+ 10 - 0
src/pages/Mail.jsx

@@ -0,0 +1,10 @@
+import WMail from "@widgets/WMail";
+
+const Mail=({...props})=>{
+    return (
+        <>
+            <WMail {...props}/>
+        </>
+    )
+}
+export default Mail;

+ 10 - 0
src/pages/MailDetail.jsx

@@ -0,0 +1,10 @@
+import WMailDetail from "@widgets/WMail/WMailDetail/WMailDetail";
+
+const MailDetail=({...props})=>{
+    return (
+        <>
+            <WMailDetail {...props}/>
+        </>
+    )
+}
+export default MailDetail;

+ 5 - 1
src/pages/index.jsx

@@ -17,6 +17,8 @@ const WithdrawPassEdit=lazy(()=>import("@pages/WithdrawPassEdit"));
 const CustomerService=lazy(()=>import("@pages/CustomerService"));
 const Promotion=lazy(()=>import("@pages/Promotion"));
 const CashWheel=lazy(()=>import("@pages/CashWheel"));
+const Mail=lazy(()=>import("@pages/Mail"));
+const MailDetail=lazy(()=>import("@pages/MailDetail"));
 export default {
     ModulePage:ModulePage,
     Home:Home,
@@ -32,7 +34,9 @@ export default {
     wdPassEdit:WithdrawPassEdit,
     customer:CustomerService,
     promotion:Promotion,
-    cashwheel:CashWheel
+    cashwheel:CashWheel,
+    mail:Mail,
+    mailDetail:MailDetail
 }
 export const Header={
     deposit: DepositHeader,

+ 1 - 0
src/style.scss

@@ -9,3 +9,4 @@
 @import "fonts/icomoon/style.css";
 @import "fonts/swisscbt/style.css";
 @import "fonts/roboto/style.css";
+@import "styles/swipeable";

+ 193 - 0
src/styles/_swipeable.scss

@@ -0,0 +1,193 @@
+.leading-actions {
+  display: flex;
+}
+
+.leading-actions > * {
+  width: 0;
+  overflow-x: hidden;
+}
+
+.swipeable-list {
+  flex: 1;
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
+}
+
+.swipeable-list-item {
+  position: relative;
+  transition: max-height 0.5s ease-in-out;
+  max-height: 1000px;
+  transform-origin: top;
+  overflow: hidden;
+  width: 100%;
+}
+
+.swipeable-list-item--remove {
+  max-height: 0;
+  transition: max-height 0.35s ease-out;
+}
+
+.swipeable-list-item__content {
+  width: 100%;
+  align-items: center;
+  box-sizing: border-box;
+  height: 100%;
+  display: flex;
+}
+
+.swipeable-list-item__content--return {
+  //transition: transform 0.5s ease-in-out;
+  transition: transform 0.3s ease-in-out;
+}
+
+@keyframes content-return-leading-ms {
+  20% {
+    transform: translateX(100%);
+  }
+  100% {
+    transform: translateX(0);
+  }
+}
+
+.swipeable-list-item__content--return-leading-ms {
+  animation-duration: 0.5s;
+  animation-name: content-return-leading-ms;
+  animation-timing-function: linear;
+}
+
+@keyframes content-return-trailing-ms {
+  20% {
+    transform: translateX(-100%);
+  }
+  100% {
+    transform: translateX(0);
+  }
+}
+
+.swipeable-list-item__content--return-trailing-ms {
+  animation-duration: 0.5s;
+  animation-name: content-return-trailing-ms;
+  animation-timing-function: linear;
+}
+
+.swipeable-list-item__content--remove {
+  transition: transform 0.5s ease-in-out;
+}
+
+.swipeable-list-item__leading-actions {
+  position: absolute;
+  display: flex;
+  width: 0px;
+  overflow: hidden;
+  height: 100%;
+}
+
+.swipeable-list-item__leading-actions--return {
+  transition: width 0.5s ease-in-out;
+}
+
+@keyframes actions-return-ms {
+  20% {
+    width: 100%;
+  }
+  100% {
+    width: 0;
+  }
+}
+
+.swipeable-list-item__actions--return-ms {
+  animation-duration: 0.5s;
+  animation-name: actions-return-ms;
+  animation-timing-function: linear;
+}
+
+.swipeable-list-item__leading-actions > * {
+  overflow: hidden;
+  display: flex;
+  justify-content: flex-end;
+  transform: scale(1);
+  transform-origin: center left;
+  transition: transform 0.2s ease-out;
+}
+
+.swipeable-list-item__trailing-actions {
+  display: flex;
+  width: 0px;
+  position: absolute;
+  right: 0;
+  top: 0;
+  justify-content: flex-end;
+  overflow: hidden;
+  height: 100%;
+}
+
+.swipeable-list-item__trailing-actions--return {
+  //transition: width 0.5s ease-in-out;
+  transition: width 0.3s ease-in-out;
+}
+
+.swipeable-list-item__trailing-actions > * {
+  overflow: hidden;
+  transform: scale(1);
+  transform-origin: center right;
+  transition: transform 0.2s ease-out;
+}
+
+.swipeable-list-item__leading-actions--scaled > * {
+  transform: scale(1.2);
+  transform-origin: center left;
+  transition: transform 0.2s ease-in;
+}
+
+.swipeable-list-item__trailing-actions--scaled > * {
+  transform: scale(1.2);
+  transform-origin: center right;
+  transition: transform 0.2s ease-in;
+}
+
+.swipe-action {
+  display: flex;
+  width: 100%;
+  align-items: stretch;
+}
+
+.swipe-action > * {
+  flex: 1;
+}
+
+.swipe-action__grayed > * {
+  background-color: gray !important;
+}
+
+.swipe-action__leading > * {
+  justify-content: flex-start;
+}
+
+.swipe-action__trailing > * {
+  justify-content: flex-end;
+}
+
+.swipe-action__leading--full-swipe-rest,
+.swipe-action__trailing--full-swipe-rest {
+  width: 0;
+  transition: width 0.2s;
+}
+
+.swipe-action__leading--full-swipe-main > * {
+  justify-content: flex-end;
+}
+
+.swipe-action__trailing--full-swipe-main > * {
+  justify-content: flex-start;
+}
+
+.trailing-actions {
+  display: flex;
+}
+
+.trailing-actions > * {
+  width: 0;
+  overflow-x: hidden;
+}

+ 1 - 0
src/utils/helpers.js

@@ -127,6 +127,7 @@ export const localStorageSet=(key,value)=>{
     localStorage.setItem(key,value);
 }
 export const getHttpMsg=(data)=>{
+    if (data==null)return ""
     let result=data.result;
     if (result==null)result=data.msg;
     let msg="";

+ 158 - 0
src/widgets/WMail/WMailDetail/WMailDetail.jsx

@@ -0,0 +1,158 @@
+import styles from './styles.module.scss';
+import {useDispatch, useSelector} from "react-redux";
+import {getMail, getMailStatus, getReadID} from "@features/mailSlice";
+import Spring from "@components/Spring";
+import React, {useEffect} from "react";
+import {formatCash, formatDate} from "@utils/helpers";
+import USD from "@assets/lvicon/USD.black.png";
+import SvgIcon from "@components/SvgIcon";
+import {reqMailDel, reqMailRead, reqMailTake} from "@features/api";
+import {REQ_STATUS} from "@api/config";
+import SmallLoading from "@components/SmallLoading";
+import {toast} from "react-toastify";
+const i18n=global.i18n;
+const WMailDetail=({close})=>{
+    const mailID=useSelector(getReadID);
+    const data=useSelector(getMail(mailID));
+    const dispatch=useDispatch();
+    const mailTakeStatus=useSelector(getMailStatus("MAIL_TAKE"));
+    useEffect(()=>{
+        let MailStatus=0;
+        if (data){
+            MailStatus=parseInt(data.MailStatus);
+            if (MailStatus===1){
+                let MailID=data.MailID;
+                dispatch(reqMailRead(MailID));
+            }
+        }
+    },[])
+    const onTake=()=>{
+        if (mailTakeStatus===REQ_STATUS.LOADING)return;
+        if (data){
+            let MailID=data.MailID;
+            dispatch(reqMailTake(MailID));
+        }
+    }
+    const checkLockItem=(item)=>{
+        if (item){
+            let MailStatus=parseInt(item.MailStatus);
+            let strkBonus=item.BonusString;
+            if (strkBonus!=null&&strkBonus!==""){
+                let blist=strkBonus.split(";");
+                let bonus_0=blist[0];
+                if (bonus_0!=null&&bonus_0!=""){
+                    let arr=bonus_0.split(",");
+                    let bonus=Number(arr[1]);
+                    if (bonus!=NaN&&bonus>0){
+                        if (MailStatus<3)return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+    const onDelItem=(item)=>{
+        let locked=checkLockItem(item);
+        if (locked){
+            toast.warn(i18n.t("mail.TopTip_qxlqyxjl"));
+            return;
+        }
+        dispatch(reqMailDel(item.MailID));
+        if (close)close();
+    }
+    const renderBonus=()=>{
+        let strkBonus=data.BonusString;
+        // strkBonus="30000,10000;30000,1000;30000,0;30000,0;30000,0;30000,0;30000,0;30000,0;30000,0;"
+        if (strkBonus===null||strkBonus==="")return (<></>);
+
+        let blist=strkBonus.split(";");
+        for (let i = 0; i < blist.length; i++) {
+            let bonus=blist[i];
+            if (bonus){
+                if (bonus===""){
+                    blist.splice(i,1);
+                    i--;
+                    continue;
+                }
+                let arr=bonus.split(",");
+                let num=Number(arr[1]);
+                if (num<=0||num===NaN){
+                    blist.splice(i,1);
+                    i--;
+                }
+            }
+        }
+        if (blist.length===0)return (<></>);
+        let MailStatus=parseInt(data.MailStatus);
+        let disabled=MailStatus>=3;
+        // disabled=true
+        return (
+            <div className={styles.bonus_con}>
+                <div className={styles.bonus_list}>
+                    {
+                        blist.map((item,index)=>{
+                            if (item===null||item==="")return (<></>);
+                            let bonus=item.split(",");
+                            let num=bonus[1];
+                            return (
+                                <div key={index} className={styles.bonus_box}>
+                                    <img className={styles.coin} src={USD}/>
+                                    <div className={styles.num}>{"x"+formatCash(num,0)}</div>
+                                </div>
+                            )
+                        })
+                    }
+                </div>
+                <button className={`button-green ${styles.rec_btn}`} disabled={disabled} onClick={()=>{
+                    onTake();
+                }}>
+                    {
+                        mailTakeStatus===REQ_STATUS.LOADING?(<SmallLoading fill={"#fff"} />):(<>{i18n.t("mail.btn_Receive")}</>)
+                    }
+                </button>
+            </div>
+        )
+    }
+    const renderContent=()=>{
+        let CreateTime=data.CreateTime;
+        if (CreateTime){
+            CreateTime=CreateTime.split(".").shift();
+        }
+        return (
+            <Spring type="slideUp" className={styles.content}>
+                <div className={styles.mail_title_con}>
+                    <div className={styles.item_con}>
+                        <div className={styles.title_item}>
+                            <div>{i18n.t("mail.txtMail.titleTheme")}</div>
+                            <div>{data.TitleString}</div>
+                        </div>
+                        <button onClick={()=>{
+                            onDelItem(data);
+                        }}>
+                            <SvgIcon className={styles.icon} herf={"icon_Delete"}></SvgIcon>
+                        </button>
+                    </div>
+                    <div className={styles.title_item}>
+                        <div>{i18n.t("mail.txtMail.titleTime")}</div>
+                        <div>{CreateTime}</div>
+                    </div>
+                    <div className={styles.item_line}></div>
+                </div>
+                <div className={styles.mail_con}>
+                    <div className={styles.mail_content}>
+                        {data.TextString}
+                    </div>
+                </div>
+                {renderBonus()}
+            </Spring>
+        )
+    }
+    return (
+        <div className={styles.container}>
+            {
+                data==null?(<></>):(renderContent())
+            }
+        </div>
+    )
+}
+export default WMailDetail;

+ 102 - 0
src/widgets/WMail/WMailDetail/styles.module.scss

@@ -0,0 +1,102 @@
+.container{
+  width: 100%;
+  height: 100%;
+  background-color: var(--widget-bg);
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  padding: 1rem 1rem;
+}
+.content{
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  & .mail_title_con{
+    display: flex;
+    flex-direction: column;
+    color: var(--header);
+    gap: 0.5rem;
+    width: 100%;
+    & .item_con{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      & .icon{
+        width: 1.2rem;
+        height: 1.2rem;
+        fill: var(--text);
+      }
+    }
+    & .title_item{
+      display: flex;
+      gap: 0.5rem;
+    }
+    & .item_line{
+      width: 100%;
+      border-top: 1px solid var(--card-line);
+    }
+  }
+  & .mail_con{
+    display: flex;
+    flex-direction: column;
+    overflow-y: auto;
+    width: 100%;
+    flex: 1;
+    & .mail_content{
+      white-space: pre-line;
+      word-wrap: break-word;
+      word-break: break-all;
+      color: var(--text);
+    }
+  }
+  & .bonus_con{
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 2rem;
+    background: linear-gradient(to right, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.01));
+    gap: 1rem;
+    & .bonus_list{
+      display: flex;
+      flex-direction: row;
+      overflow-x: auto;
+      gap: 0.5rem;
+      & .bonus_box{
+        display: flex;
+        position: relative;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        min-width: 3.2rem;
+        min-height: 3.2rem;
+        max-width: 3.2rem;
+        max-height: 3.2rem;
+        background-color: var(--card-bg);
+        border-radius: var(--border-radius);
+        border: 1px solid var(--primary-color);
+        color: var(--header);
+        & .coin{
+          width: 1.2rem;
+          height: 1.2rem;
+        }
+        & .num{
+          display: flex;
+          //position: absolute;
+          //bottom: 0;
+        }
+      }
+    }
+    & .rec_btn{
+      max-width: 35%;
+      &:disabled{
+        background-color: var(--pay-btn-bg);
+        background-image: var(--pay-btn-bg);
+        cursor: default;
+      }
+    }
+  }
+}

+ 257 - 0
src/widgets/WMail/index.jsx

@@ -0,0 +1,257 @@
+import styles from './styles.module.scss';
+import img_empty from "@assets/mail/no_data.png";
+import Spring from "@components/Spring";
+import styled from 'styled-components/macro';
+import React, {useEffect, useState} from "react";
+import {useDispatch, useSelector} from "react-redux";
+import {getMailList, getMailStatus, readMail} from "@features/mailSlice";
+import {SwipeableList, SwipeableListItem, SwipeAction, TrailingActions,Type as ListType} from "react-swipeable-list";
+import {ReactComponent as MailIcon} from "@assets/singlesvg/email.svg";
+import {ReactComponent as DotMenu} from "@assets/singlesvg/dot_more.svg";
+import {formatDate, formatTime} from "@utils/helpers";
+import SvgIcon from "@components/SvgIcon";
+import img_status_1 from "@assets/mail/mailStatus1.png";
+import img_status_2 from "@assets/mail/mailStatus2.png";
+import {useNavigate} from "react-router";
+import {ROUTE_PATH} from "@constants/routes";
+import {reqMailDel, reqMailList} from "@features/api";
+import {REQ_STATUS} from "@api/config";
+import SmallLoading from "@components/SmallLoading";
+import {toast} from "react-toastify";
+const imgStatus=[null,img_status_2,img_status_1,img_status_1,img_status_1,img_status_1];
+const i18n=global.i18n;
+const SwiperItem=styled(SwipeableListItem)`
+    position: relative;
+    transition: max-height 0.5s ease-in-out;
+    max-height: 1000px;
+    transform-origin: top;
+    overflow: hidden;
+    width: 100%;
+    margin-bottom:0.5rem;
+    scroll-snap-align: start;
+    & .swipeable-list-item__content{
+        width: 100%;
+        align-items: center;
+        box-sizing: border-box;
+        height: 100%;
+        display: flex;
+    }
+`
+const ActionContent = styled.div`
+  width:100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 8px;
+  font-weight: 500;
+  box-sizing: border-box;
+  color: #eee;
+  user-select: none;
+  background-color:#bc281e;
+  font-size:0.8rem;
+  & .icon{
+    width:2rem;
+    height:2rem;
+    fill:#eee;
+  }
+`;
+const WMail=({close})=>{
+    const list=useSelector(getMailList);
+    const [delIndex,setDelIndex]=useState(-1);
+    const dispatch =useDispatch();
+    const navigate=useNavigate();
+    const mailListStatus=useSelector(getMailStatus("MAIL_LIST"));
+    useEffect(()=>{
+        if (list){
+            for (let i = 0; i < list.length; i++) {
+                const item = document.querySelector('#listItem-'+i);
+                if (item){
+                    let open=i===delIndex;
+                    const childDivs = item.querySelectorAll('div');
+                    for (let i = 0; i <childDivs.length ; i++) {
+                        let child=childDivs[i];
+                        let classList=child.classList;
+                        if (classList.contains("swipeable-list-item__content")){
+                            //    swipeable-list-item__content--return
+                            if (!classList.contains("swipeable-list-item__content--return")){
+                                classList.add("swipeable-list-item__content--return");
+                            }
+                            // transform: translateX(-50px);
+                            child.style.transform=open?"translateX(-50px)":"translateX(-0px)";
+                        }
+                        if (classList.contains("swipeable-list-item__trailing-actions")){
+                            if (!classList.contains("swipeable-list-item__trailing-actions--return")){
+                                classList.add("swipeable-list-item__trailing-actions--return");
+                            }
+                            if (!classList.contains("test-actions-opened")){
+                                classList.add("test-actions-opened");
+                            }
+                            child.style.width=open?"50px":"0px";
+                        }
+                    }
+                }
+            }
+        }
+    },[delIndex]);
+    useEffect(()=>{
+        dispatch(reqMailList());
+    },[])
+    const checkLockItem=(item)=>{
+        if (item){
+            let MailStatus=parseInt(item.MailStatus);
+            let strkBonus=item.BonusString;
+            if (strkBonus!=null&&strkBonus!==""){
+                let blist=strkBonus.split(";");
+                let bonus_0=blist[0];
+                if (bonus_0!=null&&bonus_0!=""){
+                    let arr=bonus_0.split(",");
+                    let bonus=Number(arr[1]);
+                    if (bonus!=NaN&&bonus>0){
+                        if (MailStatus<3)return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+    const onClickDetail=(mailID)=>{
+        dispatch(readMail(mailID));
+        navigate(ROUTE_PATH.MESSAGE_DETAIL);
+    }
+    const onDelItem=(item)=>{
+        setDelIndex(-1);
+        let locked=checkLockItem(item);
+        if (locked){
+            toast.warn(i18n.t("mail.TopTip_qxlqyxjl"));
+            return;
+        }
+        setTimeout(()=>{
+            dispatch(reqMailDel(item.MailID))
+        },300)
+    }
+    const onDelAll=()=>{
+        let onSure=()=>{
+            if (list){
+                for (let i = 0; i < list.length; i++) {
+                    let item=list[i];
+                    let locked=checkLockItem(item);
+                    if (locked){
+                        toast.warn(i18n.t("mail.TopTip_qxlqyxjl"));
+                        return;
+                    }
+                }
+            }
+            dispatch(reqMailDel(0))
+        }
+        let data={
+            content:i18n.t("mail.TopTip_jscsyydyj"),
+            sureCB:onSure,
+        }
+        global.msgBoxShow(data)
+    }
+    const renderEmpty=()=>{
+        return (
+            <div className={styles.empty}>
+                <img className={styles.empty_img} src={img_empty}/>
+            </div>
+        )
+    }
+    const renderTrailing=(item)=>{
+        return (
+            <TrailingActions>
+                <SwipeAction
+                    destructive={false}
+                    onClick={() =>{
+                        onDelItem(item);
+                    }}
+                >
+                    <ActionContent >
+                        <SvgIcon className={"icon"} herf={"icon_Delete"}/>
+                        {i18n.t("mail.btn_del")}
+                    </ActionContent>
+                </SwipeAction>
+            </TrailingActions>
+        )
+    }
+    const renderItem=(item,index)=>{
+        // blockSwipe={delIndex===index}
+        let MailStatus=parseInt(item.MailStatus);
+        let CreateTime=item.CreateTime;
+        if (CreateTime){
+            CreateTime=CreateTime.split(".").shift();
+        }
+        return (
+            <SwiperItem key={index} trailingActions={renderTrailing(item)} trailingActionsOpened={delIndex===index}>
+                <button className={styles.item} onClick={()=>{
+                    // setDelIndex(index);
+                    onClickDetail(item.MailID);
+                }}>
+                    <div className={styles.item_content}>
+                        <div className={styles.left_icon_con}>
+                            <img className={styles.icon} src={imgStatus[MailStatus]}/>
+                        </div>
+                        <div className={styles.mail_con}>
+                            <div className={styles.title_con}>
+                                <div className={styles.title}>{item.TitleString}</div>
+                                <div>{CreateTime}</div>
+                            </div>
+                            <div className={styles.mail_content}>
+                                {item.TextString}
+                            </div>
+                        </div>
+                        <div className={styles.right_icon_con}>
+                            <button onClick={(event)=>{
+                                setDelIndex(index);
+                                event.stopPropagation();
+                            }}>
+                                <DotMenu className={styles.dot_more}/>
+                            </button>
+                            <SvgIcon className={styles.arrow} herf={"icon_Arrow"}/>
+                        </div>
+                    </div>
+                    <div className={styles.item_line}/>
+                </button>
+            </SwiperItem>
+        )
+    }
+    const renderMailList=()=>{
+        if (list==null)return (<></>);
+        return (
+            <Spring type="slideUp" className={styles.content}>
+                <SwipeableList className={styles.swipe_list} fullSwipe={false} threshold={0.5} type={ListType.IOS}>
+                    {
+                        list.map((item,index)=>{
+                            return (
+                                renderItem(item,index)
+                            )
+                        })
+                    }
+                </SwipeableList>
+                <button className={"button-green"} onClick={()=>{
+                    onDelAll();
+                }}>
+                    {i18n.t("mail.btn_DeleteAll")}
+                </button>
+            </Spring>
+        )
+    }
+    const renderView=()=>{
+        if (mailListStatus===REQ_STATUS.LOADING)return(<SmallLoading></SmallLoading>);
+        if (list==null||list.length===0){
+            return renderEmpty();
+        }
+        else
+        {
+            return renderMailList();
+        }
+    }
+    return (
+        <div className={styles.container}>
+            {/*{renderEmpty()}*/}
+            {/*{renderMailList()}*/}
+            {renderView()}
+        </div>
+    )
+}
+export default WMail;

+ 119 - 0
src/widgets/WMail/styles.module.scss

@@ -0,0 +1,119 @@
+.container{
+  width: 100%;
+  height: 100%;
+  background-color: var(--widget-bg);
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  padding: 1rem 1rem;
+}
+.content{
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  flex: 1;
+}
+.swipe_list{
+  //display: flex;
+  //flex-direction: column;
+  gap: 0.5rem;
+  width: 100%;
+  position: relative;
+  overflow-y: auto;
+  scroll-snap-type: y mandatory;
+  scroll-behavior: smooth;
+  //-webkit-overflow-scrolling: touch;
+  flex: 1;
+  & .item{
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 0.5rem;
+    & .mail_con{
+      display: flex;
+      width: 100%;
+      flex-direction: column;
+      color: var(--text);
+      gap: 0.25rem;
+      & .mail_content{
+        width: 100%;
+        display: -webkit-box;
+        word-break: break-all;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        -webkit-line-clamp: 1;
+        -webkit-box-orient: vertical;
+        text-align: left;
+      }
+      & .title_con{
+        display: flex;
+        width: 100%;
+        align-items: center;
+        justify-content: space-between;
+        & .title{
+          font-size: 1rem;
+          font-weight: 600;
+          color: var(--header);
+        }
+      }
+    }
+    & .item_content{
+      width: 100%;
+      position: relative;
+      display: flex;
+      align-items: flex-start;
+      justify-content: space-between;
+      gap: 1rem;
+      & .left_icon_con{
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 3rem;
+        & .icon{
+          width: 2rem;
+          height: auto;
+          fill: var(--text);
+        }
+      }
+      & .right_icon_con{
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        flex-direction: column;
+        height: 3rem;
+        & .dot_more{
+          width: 1.5rem;
+          height: 1rem;
+          fill: var(--text);
+        }
+        & .arrow{
+          width: 1rem;
+          height: 1rem;
+          fill: var(--text);
+        }
+      }
+    }
+    & .item_line{
+      width: 100%;
+      border-top: 1px solid var(--card-line);
+    }
+  }
+  & .actions{
+    width: 0px;
+  }
+}
+.empty{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  gap: 2rem;
+  color: var(--text);
+  font-size: 1rem;
+  & .empty_img{
+    opacity: 0.6;
+    margin-top: 50%;
+    width: 45%;
+    height: auto;
+  }
+}

Some files were not shown because too many files changed in this diff