1404/06/05-first

This commit is contained in:
MojtabaSoumi 2025-08-27 17:38:02 +03:30
parent fe949613ee
commit c2381a981a
129 changed files with 13603 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

4135
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "macharta",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@lucky-canvas/vue": "^0.1.11",
"@types/node": "^24.3.0",
"@vitejs/plugin-vue-jsx": "^5.0.1",
"@vueuse/core": "^13.7.0",
"ant-design-vue": "^4.2.6",
"axios": "^1.11.0",
"jalaliday": "^3.1.0",
"lodash": "^4.17.21",
"lucky-canvas": "^1.7.27",
"moment": "^2.30.1",
"moment-jalaali": "^0.10.4",
"pinia": "^3.0.3",
"vant": "^4.9.21",
"vue": "^3.5.18",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.7.0",
"less": "^4.4.1",
"typescript": "~5.8.3",
"unocss": "^66.4.2",
"vite": "^7.1.2",
"vue-tsc": "^3.0.5"
}
}

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

89
src/App.vue Normal file
View File

@ -0,0 +1,89 @@
<template>
<AConfigProvider
direction="rtl"
:theme="{
token: {
fontFamily: 'iran-sans',
colorPrimary: colors.primary,
colorLink: colors.primary,
colorLinkActive: colors.primary,
colorLinkHover: colors['primary']
}
}"
>
<div class="relative ">
<!-- <InstallPWA v-if="userStore.user" class="fixed left-0 bottom-24 w-full flex justify-center z-50"/>-->
<!-- <AAffix :offset-top="0">-->
<!-- <div class="shadow-md primaryColor">-->
<!-- <div class="container mx-auto max-w-[92rem]">-->
<!-- <Navbar class="md:hidden" v-if="router.currentRoute.value.meta.showNavbar" />-->
<!-- <div class="hidden md:block relative" v-if="router.currentRoute.value.meta.showMenu">-->
<!-- <Menu />-->
<!-- <div class="flex justify-center absolute top-0 bottom-0 left-4">-->
<!-- <div class="hidden md:flex items-center">-->
<!-- <ADropdown placement="bottom">-->
<!-- <div-->
<!-- class="bg-[#E2E8F0] p-2 rounded-full flex justify-center items-center cursor-pointer w-12 h-12">-->
<!--&lt;!&ndash; <img :src="avatar" alt="">&ndash;&gt;-->
<!-- </div>-->
<!-- <template #overlay>-->
<!-- <div class="w-[334px] backUserSetting1 shadow-lg py-6 px-4 rounded-md">-->
<!-- <UserSetting />-->
<!-- </div>-->
<!-- </template>-->
<!-- </ADropdown>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="flex justify-center absolute top-0 bottom-0 p-2 right-4">-->
<!--&lt;!&ndash; <img :src="logo" alt="">&ndash;&gt;-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </AAffix>-->
<AAffix offset-top="0" >
<div v-if="router.currentRoute.value.meta.showNavbar" class="flex px-4 items-center pt-10 mb-3 bg-gradient-to-b from-[#EAEAEA] ">
<div class="bg-[#C1FCE2] flex items-center relative rounded-full h-6vh w-1/2">
<div class="absolute bottom-0"><img src="@/assets/img/boy_3752622%201.svg"></div>
<div class=" text-center w-full font-700 pr-8 ">امیر علی</div>
</div>
<div class="flex gap-3 w-1/2 justify-end ">
<div><img src="@/assets/img/alertIcon.png"></div>
<div><img src="@/assets/img/menu_8917404%201.png"></div>
</div>
</div>
</AAffix>
<RouterView v-slot="{ Component }">
<transition mode="out-in" name="fade">
<KeepAlive>
<Component class="container mx-auto max-w-[90rem] px-4 overflow-y-auto py-6 h-75vh" :is="Component" />
</KeepAlive>
</transition>
</RouterView>
<!-- <Affix offset-bottom="3" class="w-full " >-->
<div class="fixed z-2 bottom-0 w-full bg-gradient-to-t from-[#EAEAEA] pt-15 px-4 pb-3">
<div class=" border-1 border-black rounded-3xl box-border" style="box-shadow: 0px 3.75px 16px 0px #00000030;">
<Menu v-if="router.currentRoute.value.meta.showMenu" />
</div>
</div>
<!-- </Affix>-->
</div>
</AConfigProvider>
</template>
<script setup lang="ts">
import {colors} from "@/themeConfig.ts";
import router from "@/router";
import Menu from "@/components/Menu.vue";
import {route} from "vant/es/composables/use-route";
const keepAliveList = ['product-list']
</script>
<style scoped>
</style>

4
src/assets/css/ant.less Normal file
View File

@ -0,0 +1,4 @@
.ant-modal-content {
padding: 16px !important;
}

43
src/assets/css/fonts.less Normal file
View File

@ -0,0 +1,43 @@
@import (less) 'var.less';
@font-face {
font-family: @prefixCls-font-family;
src: url('../fonts/iranSans/woff2/IRANSansWeb(FaNum).woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: @prefixCls-font-family;
src: url('../fonts/iranSans/woff2/IRANSansWeb(FaNum)_Light.woff2') format('woff2');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: @prefixCls-font-family;
src: url('../fonts/iranSans/woff2/IRANSansWeb(FaNum)_UltraLight.woff2') format('woff2');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: @prefixCls-font-family;
src: url('../fonts/iranSans/woff2/IRANSansWeb(FaNum)_Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: @prefixCls-font-family;
src: url('../fonts/iranSans/woff2/IRANSansWeb(FaNum)_Bold.woff2') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: @prefixCls-font-family;
src: url('../fonts/iranSans/woff2/IRANSansWeb(FaNum)_Black.woff2') format('woff');
font-weight: 900;
font-style: normal;
}

View File

@ -0,0 +1,302 @@
body {
background: #EAEAEA;
color: #000000;
}
body .menuItem{
color: #5F6368;
fill: #5F6368;
stroke: #5F6368;
}
body .menuItemSelect svg{
color: #081023;
fill: #ff0c0c;
stroke: #2563EB;
}
body .textColor1{
color: #616161;
}
body .textColor2{
color: #9E9E9E;
}
body .ant-tabs-tab-btn{
color: #000000;
}
body .text_Primary{
color: #424242;
}
body .text_Secondary{
color: #2563EB;
}
body .background_Primary{
background: #424242;
}
body .background_Secondary{
background: #F5F5F5;
}
body .primaryColor{
color: #000000 ;
}
body .ant-menu{
color: #FFFFFFFF ;
}
body .text_green{
color: #4CAF50FF ;
}
body .background_green{
background: #4CAF50FF ;
}
body .text_red{
color: #F44336FF ;
}
body .background_red{
background: #F44336FF ;
}
body .ant-table-cell{
background: #ffffff ;
border-color: #e5e7eb ;
}
body .ant-table-wrapper .ant-table-thead>tr>th {
background: #fafafa ;
border-color: #e5e7eb ;
}
body .ant-table-wrapper .ant-table-thead > tr > th::before,
.ant-table-wrapper .ant-table-thead > tr > td::before {
background-color: #F44336FF;
}
body .ab-base-table .ant-table-wrapper {
border: none ;
}
body .ant-table-wrapper .ant-table {
color: #000000;
}
body :where(.css-dev-only-do-not-override-ajjz7p).ant-btn-primary {
background-color: #000000 ;
}
body .ant-modal .ant-modal-content {
background-color: #ffffff ;
}
body .ant-modal-title{
background-color: #ffffff ;
color: #000000;
}
body .ant-input{
background: #ffffff ;
border-color: #e5e7eb ;
color: #000000 ;
}
body .ant-input-group-addon{
background: #2563EB ;
border-color: #e5e7eb ;
color: #ffffff;
}
body .ant-form-item .ant-form-item-label >label {
color: #000000 ;
}
//User Setting
body .backUserSetting1 {
background-color: #ffffff ;
}
body .backUserSetting2 {
background-color: #fafafa ;
border-color: #e5e7eb ;
}
body .iconUserSetting {
stroke: #000000 ;
fill: #000000 ;
}
body .okButtonUserSetting {
background-color: #2563EB ;
border-color: #2563EB ;
color: #ffffff ;
}
body .cancelButtonUserSetting {
color: #000000 ;
border-color: #e5e7eb ;
background-color: #ffffff ;
}
//Security
body .backSecurity {
background-color: #ffffff ;
}
body .ant-input-password {
//border: none ;
border-color: #e5e7eb ;
background-color: #ffffff ;
}
body .ant-input-password.input {
//color: @bg ;
}
//Quit
body .exit {
color: #F44336 ;
background-color: #F44336 ;
}
body .exitButton {
color: #000000 ;
background-color: #ffffff ;
}
//Login
body .backLogin {
background-color: #ffffff;
}
body .textColorLogin1 {
color: #424242;
}
body .textColorLogin2 {
color: #9E9E9E;
}
body .buttonLogin {
background-color: #2563EB ;
color: #ffffff ;
}
body .textColorLogin1 {
color: #424242;
}
body .ab-login .ant-tabs-nav-wrap {
background: #f9fafb ;
}
body .buttonHover{
border-color: #e5e7eb ;
}
//BuySell
body .BuySell_Style{
//background: #FAFAFA;
border-color: #e5e7eb;
}
body .BuySell_text1{
color: #616161;
}
body .BuySell_text2{
color: #000000 ;
}
body .BuySell_button{
background-color: #2563EB ;
color: #ffffff ;
}
body .BuySell_cancelButton{
background-color: #ffffff ;
color: #000000 ;
}
body .BuySell_button_{
border-color: #e5e7eb ;
background: #ffffff ;
}
body .BuySell_back{
border-color: #e5e7eb ;
background: #ffffff ;
}
body .mobileBuySell_back{
background: #ffffff ;
color: #000000 ;
}
//hover style
body .ant-input:hover {
border-color: #2563EB;
border-inline-end-width: 1px;
}
//mobile Header
body .backHeader{
background: #ffffff ;
}
//transaction card
body .backCard{
background: #ffffff;
}
body .textCard1{
color: #616161;
}
body .textCard2{
color: #BDBDBD;
}
body .textCard3{
color: #9E9E9E;
}
body .MobileNavbar{
background: #ffffff;
border-color: #e5e7eb;
color: #000000;
}
body .back_SearchBoxCard{
background: #ffffff ;
border-color: #e5e7eb ;
}
body .back_SearchBoxCard2{
background: #ffffff;
}
body .searchIcon{
stroke: #d9d9d9 ;
}
body .transferIcon{
stroke: #000000;
}
//ADrawer
body .ADrawer{
background: #ffffff;
border-color: #e5e7eb;
}
body .icon_MobileNavbar{
stroke: #000000 ;
}
body .drawer-body{
background: #112234FF ;
}
//order card
body .orderCard_text{
color: #616161 ;
}
body .orderCard_back{
background: #dfdfdf;
}
body :where(.css-dev-only-do-not-override-1yhcnou).ant-float-btn-primary .ant-float-btn-body{
background-color: #2563EB;
}
body :where(.css-dev-only-do-not-override-1yhcnou).ant-input-affix-wrapper{
background-color: #ffffff;
border-color: #d9d9d9;
}
body .ant-pagination .ant-pagination-item a {
color: #616161;
}
body .ant-select {
background-color: #ffffff;
}
body :where(.css-dev-only-do-not-override-ajjz7p).ant-select:not(.ant-select-customize-input) .ant-select-selector {
position: relative;
background-color: #ffffff;
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
body :where(.css-dev-only-do-not-override-1yhcnou).ant-btn-default:not(:disabled):hover {
border-color: #2563EB;
}
body :where(.css-dev-only-do-not-override-1yhcnou).ant-tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn{
//color: #2563EB;
}
body .ant-select-selector, .ant-tooltip-open, .ant-select-selection-item, .ant-select-item .ant-select-item-option{
color: #616161;
}
body .ant-tabs-nav-wrap{
justify-content: center;
}
body .avatarIcon {
fill: #5F6368;
stroke: #5F6368;
}
//User Card
body .userCard:hover {
border-color: #2563EB;
}
body .userCard {
background-color: #ffffff;
}
//product Price Card
body .productPriceCard {
background-color: #ffffff;
}
body .backProductPriceCard {
background-color: #2563EB;
}
body .iconProductPriceCard {
color: #2563EB;
background-color: rgba(37, 99, 235, 0.29);
}

59
src/assets/css/main.less Normal file
View File

@ -0,0 +1,59 @@
@import (less) 'var.less';
@import (less) 'fonts.less';
@import (less) 'ant.less';
@import (less) 'macharta.less';
html {
scroll-behavior: smooth;
}
body {
font-family: @prefixCls-font-family !important;
background-color: @prefixCls-background-color-mobile;
direction: rtl !important;
height: unset !important;
}
@media (min-width: 768px) {
body {
background-color: @prefixCls-background-color;
}
}
pre, .ant-btn, span, label, input, div {
font-family: @prefixCls-font-family !important;
}
.en-font {
font-family: Arial !important;
direction: rtl !important;
}
.ant-form .ant-form-item .ant-form-item-label, .ant-form .ant-form-item .ant-form-item-control {
flex: 0 0 100%;
max-width: 100%;
}
.ant-form-item .ant-form-item-label {
margin: 0;
padding: 0 0 8px;
white-space: initial;
text-align: start;
}
.ant-modal .ant-modal-footer {
margin-top: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

15
src/assets/css/var.less Normal file
View File

@ -0,0 +1,15 @@
@prefixCls: 'ab';
@border-radius-base: 8px;
@primary-color: #1d4ed8;
// Border color
@border-color-base: @gray-100; // base border outline a component
// Form
@form-item-label-font-size: 14px;
//variables
@prefixCls-font-family: 'iran-sans';
@prefixCls-background-color-mobile: #fafafa;
@prefixCls-background-color: #fff;
@gray-50: #f6f7f9;
@gray-100: #e8eaee;

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,10 @@
{
"vuesax-bulk:shop": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path opacity=\"0.4\" d=\"M21.37 11.39V17.38C21.37 20.14 19.13 22.38 16.37 22.38H7.63C4.87 22.38 2.63 20.14 2.63 17.38V11.46C3.39 12.28 4.47 12.75 5.64 12.75C6.9 12.75 8.11 12.12 8.87 11.11C9.55 12.12 10.71 12.75 12 12.75C13.28 12.75 14.42 12.15 15.11 11.15C15.88 12.14 17.07 12.75 18.31 12.75C19.52 12.75 20.62 12.26 21.37 11.39Z\" fill=\"#292D32\"/>\n<path d=\"M14.99 1.25H8.98997L8.24997 8.61C8.18997 9.29 8.28997 9.93 8.53997 10.51C9.11997 11.87 10.48 12.75 12 12.75C13.54 12.75 14.87 11.89 15.47 10.52C15.65 10.09 15.76 9.59 15.77 9.08V8.89L14.99 1.25Z\" fill=\"#292D32\"/>\n<path opacity=\"0.6\" d=\"M22.36 8.27L22.07 5.5C21.65 2.48 20.28 1.25 17.35 1.25H13.51L14.25 8.75C14.26 8.85 14.27 8.96 14.27 9.15C14.33 9.67 14.49 10.15 14.73 10.58C15.45 11.9 16.85 12.75 18.31 12.75C19.64 12.75 20.84 12.16 21.59 11.12C22.19 10.32 22.46 9.31 22.36 8.27Z\" fill=\"#292D32\"/>\n<path opacity=\"0.6\" d=\"M6.59002 1.25C3.65002 1.25 2.29002 2.48 1.86002 5.53L1.59002 8.28C1.49002 9.35 1.78002 10.39 2.41002 11.2C3.17002 12.19 4.34002 12.75 5.64002 12.75C7.10002 12.75 8.50002 11.9 9.21002 10.6C9.47002 10.15 9.64002 9.63 9.69002 9.09L10.47 1.26H6.59002V1.25Z\" fill=\"#292D32\"/>\n<path d=\"M11.35 16.66C10.08 16.79 9.12 17.87 9.12 19.15V22.38H14.87V19.5C14.88 17.41 13.65 16.42 11.35 16.66Z\" fill=\"#292D32\"/>\n</svg>\n",
"vuesax-bulk:strongbox-2": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path opacity=\"0.4\" d=\"M16.19 2H7.82002C4.18002 2 2.01001 4.17 2.01001 7.81V16.18C2.01001 19.82 4.18002 21.99 7.82002 21.99H16.19C19.83 21.99 22 19.82 22 16.18V7.81C22 4.17 19.83 2 16.19 2Z\" fill=\"#292D32\"/>\n<path d=\"M16 9.23999H18C18.55 9.23999 19 8.78999 19 8.23999V8C19 6.34 17.66 5 16 5H8C6.34 5 5 6.34 5 8V8.5C5 9.05 5.45 9.5 6 9.5H7.34C8.65 9.5 9.84 10.44 9.97 11.75C10.12 13.25 8.95 14.51 7.48 14.51H6C5.45 14.51 5 14.96 5 15.51V16.01C5 17.67 6.34 19.01 8 19.01H16C17.66 19.01 19 17.67 19 16.01V15.76C19 15.21 18.55 14.76 18 14.76H16C15.59 14.76 15.25 14.42 15.25 14.01C15.25 13.6 15.59 13.26 16 13.26H18C18.55 13.26 19 12.81 19 12.26V11.75C19 11.2 18.55 10.75 18 10.75H16C15.59 10.75 15.25 10.42 15.25 10C15.25 9.58 15.59 9.23999 16 9.23999Z\" fill=\"#292D32\"/>\n<path d=\"M7 13H6C5.45 13 5 12.55 5 12C5 11.45 5.45 11 6 11H7C7.55 11 8 11.45 8 12C8 12.55 7.55 13 7 13Z\" fill=\"#292D32\"/>\n</svg>\n",
"vuesax-bulk:receipt-2": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path opacity=\"0.4\" d=\"M20 7.04V16.96C20 18.48 19.86 19.56 19.5 20.33C19.5 20.34 19.49 20.36 19.48 20.37C19.26 20.65 18.97 20.79 18.63 20.79C18.1 20.79 17.46 20.44 16.77 19.7C15.95 18.82 14.69 18.89 13.97 19.85L12.96 21.19C12.56 21.73 12.03 22 11.5 22C10.97 22 10.44 21.73 10.04 21.19L9.02002 19.84C8.31002 18.89 7.05999 18.82 6.23999 19.69L6.22998 19.7C5.09998 20.91 4.10002 21.09 3.52002 20.37C3.51002 20.36 3.5 20.34 3.5 20.33C3.14 19.56 3 18.48 3 16.96V7.04C3 5.52 3.14 4.44 3.5 3.67C3.5 3.66 3.50002 3.65 3.52002 3.64C4.09002 2.91 5.09998 3.09 6.22998 4.3L6.23999 4.31C7.05999 5.18 8.31002 5.11 9.02002 4.16L10.04 2.81C10.44 2.27 10.97 2 11.5 2C12.03 2 12.56 2.27 12.96 2.81L13.97 4.15C14.69 5.11 15.95 5.18 16.77 4.3C17.46 3.56 18.1 3.21 18.63 3.21C18.97 3.21 19.26 3.36 19.48 3.64C19.5 3.65 19.5 3.66 19.5 3.67C19.86 4.44 20 5.52 20 7.04Z\" fill=\"#292D32\"/>\n<path d=\"M16 11H8C7.59 11 7.25 10.66 7.25 10.25C7.25 9.84 7.59 9.5 8 9.5H16C16.41 9.5 16.75 9.84 16.75 10.25C16.75 10.66 16.41 11 16 11Z\" fill=\"#292D32\"/>\n<path d=\"M14 14.5H8C7.59 14.5 7.25 14.16 7.25 13.75C7.25 13.34 7.59 13 8 13H14C14.41 13 14.75 13.34 14.75 13.75C14.75 14.16 14.41 14.5 14 14.5Z\" fill=\"#292D32\"/>\n</svg>\n",
"vuesax-bulk:convert-card": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path opacity=\"0.4\" fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M22 14.25C22.4142 14.25 22.75 14.5858 22.75 15C22.75 19.2842 19.2842 22.75 15 22.75C14.7298 22.75 14.4805 22.6047 14.3474 22.3695C14.2142 22.1344 14.2179 21.8458 14.3569 21.6141L15.4069 19.8641C15.62 19.5089 16.0807 19.3938 16.4359 19.6069C16.791 19.82 16.9062 20.2807 16.6931 20.6359L16.4218 21.0881C19.1909 20.4456 21.25 17.9666 21.25 15C21.25 14.5858 21.5858 14.25 22 14.25Z\" fill=\"#292D32\"/>\n<path opacity=\"0.4\" fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M7.57821 2.91194C4.8091 3.55436 2.75 6.03342 2.75 9C2.75 9.41421 2.41421 9.75 2 9.75C1.58579 9.75 1.25 9.41421 1.25 9C1.25 4.71579 4.71579 1.25 9 1.25C9.2702 1.25 9.51952 1.39534 9.65265 1.63047C9.78578 1.8656 9.78214 2.15417 9.64312 2.38587L8.59313 4.13587C8.38002 4.49105 7.91933 4.60623 7.56414 4.39312C7.20896 4.18001 7.09378 3.71932 7.30689 3.36413L7.57821 2.91194Z\" fill=\"#292D32\"/>\n<path opacity=\"0.4\" d=\"M12 15.7V16.31H2V15.7C2 13.94 2.44 13.5 4.22 13.5H9.78C11.56 13.5 12 13.94 12 15.7Z\" fill=\"#292D32\"/>\n<path d=\"M2 16.31V17.81V19.8C2 21.56 2.44 22 4.22 22H9.78C11.56 22 12 21.56 12 19.8V17.81V16.31H2Z\" fill=\"#292D32\"/>\n<path opacity=\"0.4\" d=\"M22 4.2V4.81H12V4.2C12 2.44 12.44 2 14.22 2H19.78C21.56 2 22 2.44 22 4.2Z\" fill=\"#292D32\"/>\n<path d=\"M12 4.81V6.31V8.3C12 10.06 12.44 10.5 14.22 10.5H19.78C21.56 10.5 22 10.06 22 8.3V6.31V4.81H12Z\" fill=\"#292D32\"/>\n</svg>\n",
"vuesax-bulk:bag-happy": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n<path opacity=\"0.4\" d=\"M19.24 5.58H18.84L15.46 2.2C15.19 1.93 14.75 1.93 14.47 2.2C14.2 2.47 14.2 2.91 14.47 3.19L16.86 5.58H7.14L9.53 3.19C9.8 2.92 9.8 2.48 9.53 2.2C9.26 1.93 8.82 1.93 8.54 2.2L5.17 5.58H4.77C3.87 5.58 2 5.58 2 8.14C2 9.11 2.2 9.75 2.62 10.17C2.86 10.42 3.15 10.55 3.46 10.62C3.75 10.69 4.06 10.7 4.36 10.7H19.64C19.95 10.7 20.24 10.68 20.52 10.62C21.36 10.42 22 9.82 22 8.14C22 5.58 20.13 5.58 19.24 5.58Z\" fill=\"#292D32\"/>\n<path d=\"M19.65 10.7H4.36002C4.07002 10.7 3.75002 10.69 3.46002 10.61L4.72002 18.3C5.00002 20.02 5.75002 22 9.08002 22H14.69C18.06 22 18.66 20.31 19.02 18.42L20.53 10.61C20.25 10.68 19.95 10.7 19.65 10.7ZM12 18.5C9.66002 18.5 7.75002 16.59 7.75002 14.25C7.75002 13.84 8.09002 13.5 8.50002 13.5C8.91002 13.5 9.25002 13.84 9.25002 14.25C9.25002 15.77 10.48 17 12 17C13.52 17 14.75 15.77 14.75 14.25C14.75 13.84 15.09 13.5 15.5 13.5C15.91 13.5 16.25 13.84 16.25 14.25C16.25 16.59 14.34 18.5 12 18.5Z\" fill=\"#292D32\"/>\n</svg>",
"vuesax-bulk:bulk-more":"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n<path opacity=\"0.4\" d=\"M20.0001 5.85001C20.0001 7.20864 18.8987 8.31001 17.5401 8.31001C16.1815 8.31001 15.0801 7.20863 15.0801 5.85001C15.0801 4.49139 16.1815 3.39001 17.5401 3.39001C18.8987 3.39001 20.0001 4.49139 20.0001 5.85001Z\" fill=\"#6B7280\" stroke=\"#6B7280\"/>\n<path d=\"M6.46 8.81001C8.09476 8.81001 9.42 7.48478 9.42 5.85001C9.42 4.21525 8.09476 2.89001 6.46 2.89001C4.82524 2.89001 3.5 4.21525 3.5 5.85001C3.5 7.48478 4.82524 8.81001 6.46 8.81001Z\" fill=\"#6B7280\"/>\n<path d=\"M17.5401 21.11C19.1748 21.11 20.5001 19.7848 20.5001 18.15C20.5001 16.5152 19.1748 15.19 17.5401 15.19C15.9053 15.19 14.5801 16.5152 14.5801 18.15C14.5801 19.7848 15.9053 21.11 17.5401 21.11Z\" fill=\"#6B7280\"/>\n<path opacity=\"0.4\" d=\"M6.46 21.11C8.09476 21.11 9.42 19.7848 9.42 18.15C9.42 16.5152 8.09476 15.19 6.46 15.19C4.82524 15.19 3.5 16.5152 3.5 18.15C3.5 19.7848 4.82524 21.11 6.46 21.11Z\" fill=\"#6B7280\"/>\n</svg>",
"vuesax-bulk:people": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M18 7.16C17.94 7.15 17.87 7.15 17.81 7.16C16.43 7.11 15.33 5.98 15.33 4.58C15.33 3.15 16.48 2 17.91 2C19.34 2 20.49 3.16 20.49 4.58C20.48 5.98 19.38 7.11 18 7.16Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M16.9699 14.44C18.3399 14.67 19.8499 14.43 20.9099 13.72C22.3199 12.78 22.3199 11.24 20.9099 10.3C19.8399 9.59004 18.3099 9.35003 16.9399 9.59003\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M5.96998 7.16C6.02998 7.15 6.09998 7.15 6.15998 7.16C7.53998 7.11 8.63998 5.98 8.63998 4.58C8.63998 3.15 7.48998 2 6.05998 2C4.62998 2 3.47998 3.16 3.47998 4.58C3.48998 5.98 4.58998 7.11 5.96998 7.16Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M6.99994 14.44C5.62994 14.67 4.11994 14.43 3.05994 13.72C1.64994 12.78 1.64994 11.24 3.05994 10.3C4.12994 9.59004 5.65994 9.35003 7.02994 9.59003\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M12 14.63C11.94 14.62 11.87 14.62 11.81 14.63C10.43 14.58 9.32996 13.45 9.32996 12.05C9.32996 10.62 10.48 9.46997 11.91 9.46997C13.34 9.46997 14.49 10.63 14.49 12.05C14.48 13.45 13.38 14.59 12 14.63Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M9.08997 17.78C7.67997 18.72 7.67997 20.26 9.08997 21.2C10.69 22.27 13.31 22.27 14.91 21.2C16.32 20.26 16.32 18.72 14.91 17.78C13.32 16.72 10.69 16.72 9.08997 17.78Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n",
"vuesax-bulk:user": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M20.5899 22C20.5899 18.13 16.7399 15 11.9999 15C7.25991 15 3.40991 18.13 3.40991 22\" stroke=\"#292D32\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>\n"
}

1025
src/assets/icons/icons.json Normal file

File diff suppressed because one or more lines are too long

BIN
src/assets/img/Iconback.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

BIN
src/assets/img/Iconnext.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

BIN
src/assets/img/audio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 57 KiB

BIN
src/assets/img/cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

BIN
src/assets/img/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

BIN
src/assets/img/iconplay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

BIN
src/assets/img/image 2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="163" height="163" viewBox="0 0 163 163">
<image x="8" width="147" height="163" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJMAAACjCAYAAACOnyy4AAAESklEQVR4nO3dQVIcNwAFUJFi4SU3gBtkbgA5iXOE+AQhJ/AVnFskK8gN8A3wLkuWWYVUV/VUxkrjDPAZST3vVanKjaHc3fPdEpJaKityXUp5HLBcr+Uj+K6Dc2AlhIkYYSLmZOBb+bGUstk5vpjLaO7nsnVXSvkw4HWU0w7O4aWmIF2NeepfGfU/wX+o5ogRJmJGajPVbaTpz2cNz+etPMztpq1h21A9uxm0U/K15WaUD0g1R4wwEdNz10A9ZrWKX59f4GLhXnQ5ntdzA/yxg3PoVZefm2qOGGEippc201nVh8S31cNId3P/FPPNOcY+pFTpYoxSNUeMMBEjTMQIEzHCRIwwESNMxLTqtJz6RS53jo91EHdVPJmIESZihImYVm2maVD3x53jd43OYy0+VgO9v5ZSPh362lqF6UyjO6qecfFHi5NQzREjTMQIEzHCRIwwESNMxAgTMcJEjDARI0zECBMxwkSMMBEjTMQIEzHCRIwwESNMxAgTMSNvxMO/6sVkmywu68lEjDARI0zEtFqcvN6wbzO/SMjL/FRK+bzzk/XumgfRqgHe5GJXbArSbevLU80RI0zECBMxwkSMMBEjTMT0Mjb310JXgfWbnlZ3A3Sxo1MvOypOwXlffa3LrUM7YSdM1k2YiOm5zcRg7B4+Jm0m1k2YiBEmYnoO00lVms/XaeR24V50yZOJGGEiRpiI6bmfqbaZN/DZml5A+H7neKRr2XU3l60/Sym/7xw/VH/frZHe6K1vaBcj5QEPVe///ai/bKjmiBEmYkZeuOLTQnWw2246r3bb7MV03l92zqV+h3At1feqTNvd/12VxwalPoertd5w1RwxwkTMmhf7mtohv1Rfa9EXVc/LMgkQAAAAAI7EdaOxt9eW1SzQYTiFGGEiRpiI6XkS/s0zv7/e9WAUL1lg/4cer80qKGOyCgrrJkzEtJoc977Tyf6jqNuTv80vb259bvHiZqswTQ3ly+pro76R20L9UsLUgH+3c/ylxUmp5ogRJmIOVc1dVY/mS9Va1KbqY7uo7vftIdYvOGSYfj7Qv3WMNntc85uHSTVHjDAR81bVXL0w17mPrKnzqg01zAJiZe5UG3Gi2rGU5w6i70U1R4wwEZNqM9Xd+2dPfB99OFv4zF7ddZDqODT3aHyvzoJqjhhhIkaYiHlpPamNtH7PzoYnEzHCRIwwEbNPp+Vm3kGJ41KP3334v8HhfcK01FvK+j17VEM1R4wwESNMxAgTMcJEjDARs9Q1UL8w6WUAyrzYyDdf7HwqTF6YpLa0as1XYVLNESNMxAgTMcJEjDARI0zECBMxp3Nn1O6qY/XCpbAXTyZihIkYYSLmZJ44bo43r3XryUSMMBEjTMQIEzHCRIwwESNMxAgTMcJEjDARI0zECBMxwkSMMBEjTMScLix6eVHNCYcl93PZWlw89dpOkcoe5boOjmqOGGEiRpjIKKX8A71aXsXRpXaeAAAAAElFTkSuQmCC"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/assets/img/user_120.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

6
src/baseColor.json Normal file
View File

@ -0,0 +1,6 @@
{
"primary": "#1D4ED8",
"secondary": "#16A34A",
"background": "#FFFFFF",
"bgm": "#FAFAFA"
}

View File

@ -0,0 +1,2 @@
import BaseDatePicker from './src/BaseDatePicker.vue'
export default BaseDatePicker

View File

@ -0,0 +1,47 @@
<template>
<DatePicker v-model:value="localValue" :locale="locale" class="w-full" />
<!-- <DatePicker :locale="locale" class="w-full" />-->
</template>
<script setup lang="ts">
import locale from 'ant-design-vue/es/date-picker/locale/fa_IR'
import { DatePicker } from 'ant-design-vue'
import { computed } from 'vue'
import moment from 'moment-jalaali'
//@ts-ignore
import fa from 'moment/src/locale/fa'
moment.locale('fa', fa)
moment.loadPersian()
// import momentj from 'moment-jalaali'
import dayjs from 'dayjs'
interface Prop {
value?: string | number | boolean | string[] | number[]
}
const emit = defineEmits(['update:value'])
const props = withDefaults(defineProps<Prop>(), { allowClear: true, hasTooltip: true })
const localValue = computed({
get() {
const dayjsDate = props.value ? dayjs(props.value) : null
// console.log('test', dayjsDate)
// if (dayjsDate !== null)
// console.log('get')
return dayjsDate
},
set(value) {
const date = dayjs(value).format('YYYY-MM-DD')
// const formatDate = moment.utc(date).format('YYYY-MM-DD')
console.log(moment(date, 'jYYYY/jM/jD HH:mm').format('YYYY-M-D'))
emit('update:value', moment(date, 'jYYYY/jM/jD HH:mm').format('YYYY-M-D'))
}
})
</script>
<style lang="less">
</style>

View File

@ -0,0 +1,24 @@
import dayjs from 'dayjs'
import updateLocale from 'dayjs/plugin/updateLocale'
import jalaliday from 'dayjalali'
dayjs.extend(jalaliday)
dayjs.extend(updateLocale)
dayjs.calendar('jalali') // Jalali Calendar
const months = [
'فروردین',
'اردیبهشت',
'خرداد',
'تیر',
'مرداد',
'شهریور',
'مهر',
'آبان',
'آذر',
'دی',
'بهمن',
'اسفند',
]
dayjs.updateLocale('fa', {
monthsShort: months,
months,
})

View File

@ -0,0 +1,2 @@
import BaseSwitch from './src/BaseSwitch.vue'
export default BaseSwitch

View File

@ -0,0 +1,34 @@
<template>
<ASwitch v-model:checked="checked"></ASwitch>
</template>
<script setup lang="ts">
interface Props {
value?: boolean
}
const props = withDefaults(defineProps<Props>(), {})
const emit = defineEmits<{
(e: 'update:value', val: boolean): void
}>()
const checked = ref<boolean>(false)
watch(
checked,
() => {
emit('update:value', checked.value)
},
{ immediate: true }
)
watch(
() => props.value,
() => {
//if (props.value !== undefined)
checked.value = props.value
},
{ immediate: true }
)
</script>
<style lang="less"></style>

View File

@ -0,0 +1,89 @@
import type { ColumnTypeFilter, FilterOP } from '@core/type/baseTable'
/*interface FilterOperator {
label: string
value: FilterOP
// showIn: ColumnTypeFilter[]
}*/
//type ColumnTypeFilter = 'Date' | 'Time' | 'DateTime' | 'Select' | 'Text' | 'Boolean' | 'Number'
/*export const filterOperatorList: FilterOperator[] = [
{
value: 'eq',
label: 'برابر'
// showIn: ['Date', 'Time', 'DateTime', 'Select', 'Text', 'Boolean', 'Number']
},
{
value: 'not',
label: 'نابرابر'
},
{
value: 'gt',
label: 'بزرگتر'
},
{
value: 'lt',
label: 'کوچکتر'
},
{
value: 'gte',
label: 'بزرگتر مساوی'
},
{
value: 'lte',
label: 'کوچکتر مساوی'
},
{
value: 'sw',
label: 'شروع با'
},
{
value: 'ew',
label: 'خاتمه با'
},
{
value: 'has',
label: 'شامل'
},
/!*{
value: 'bet',
label: 'بین'
},*!/
{
value: 'null',
label: 'تهی'
},
{
value: 'nn',
label: 'تهی نباشد'
},
{
value: 'in',
label: 'شامل در'
}
]*/
export const MapFilterOpToFaString: Record<FilterOP, string> = {
sw: 'شروع با',
bet: '',
gte: 'بزرگتر مساوی',
has: 'شامل',
lte: 'کوچکتر مساوی',
not: 'نابرابر',
eq: 'برابر',
ew: 'خاتمه با',
null: 'تهی',
lt: 'کوچکتر',
in: 'شامل در',
gt: 'بزرگتر',
nn: 'تهی نباشد'
}
export const availableFilterType: Record<ColumnTypeFilter, FilterOP[]> = {
Text: ['eq', 'sw', 'ew', 'has', 'in', 'nn', 'not', 'null'],
Number: ['eq', 'gt', 'lt', 'gte', 'lte', 'not', 'nn', 'in', 'null'],
Boolean: ['eq', 'not', 'nn', 'null', 'in'],
Date: ['eq', 'gt', 'lt', 'gte', 'lte', 'sw', 'ew', 'has', 'not', 'null', 'in', 'nn'],
Time: ['eq', 'gt', 'lt', 'gte', 'lte', 'sw', 'ew', 'has', 'not', 'null', 'in', 'nn'],
DateTime: ['eq', 'gt', 'lt', 'gte', 'lte', 'sw', 'ew', 'has', 'not', 'null', 'in', 'nn'],
Select: ['eq', 'gt', 'lt', 'gte', 'lte', 'sw', 'ew', 'nn', 'not', 'null', 'has', 'in']
}

View File

@ -0,0 +1,18 @@
import type { BaseTableEmit } from '@core/type/baseTable'
function useBaseTable() {
let reload = (): any => {
throw 'base table not register reload function'
}
const runReload = () => {
reload()
}
const register = (data?: BaseTableEmit) => {
if (data && data.reload) {
reload = data.reload
}
}
return [register, runReload]
}
export default useBaseTable

View File

@ -0,0 +1,2 @@
import BaseTable from './src/BaseTable.vue'
export default BaseTable

View File

@ -0,0 +1,134 @@
import type { VNode, Component } from 'vue'
interface BaseTableColumn {
title?: string //pass
name?: string //pass
dataIndex: string // pass
key: string //pass
slot?: string //pass
//customRenderer?:
width?: number // pass
type?: ColumnTypeFilter
filterable?: boolean
sortable?: boolean
draggable?: boolean
show?: boolean //pass
fixed?: boolean | string //string is (left/right) //pass
align?: 'center' | 'right' | 'left' //pass
ellipsis?: boolean
children?: BaseTableColumn[]
resizable?: boolean //pass - no support for rtl on resize
responsive?: string[]
customRender?: (data: { text: string; record: any; index: number }) => VNode | string //pass
customCell?: (record: any, rowIndex: number, column: any) => void
className?: string
filterComponentProps?: any
filterComponent?: Component | VNode
filterField?: string
sortField?: string
}
//boolean switch - boolean checkbox
//for client
type ColumnTypeFilter = 'Date' | 'Time' | 'DateTime' | 'Select' | 'Text' | 'Boolean' | 'Number'
//for server
type FilterType = 'number' | 'date' | 'text' | 'select' | 'switch'
export const mapColumnTypeFilterToFilterType: Record<ColumnTypeFilter, FilterType> = {
Date: 'date',
Time: 'date',
DateTime: 'date',
Select: 'select',
Text: 'text',
Boolean: 'switch',
Number: 'number'
}
interface TableInput {
limit?: number //number of record that return
filters?: Filter[]
filtersOpt?: any
key?: any
order?: string
search?: string | null
select?: string[]
sort?: TableSort | null
page?: number //current page
titles?: Record<string, string>
report_title?: string
total?: number
}
interface TableInputMongo {
f: any[]
o: {}
p: {
s: number
c: number
}
s: {}
}
interface TableSort {
key: string
order: 'asc' | 'desc'
}
interface Filter {
cr: 'and' | 'or'
index?: number
field: string
type: FilterType
op: FilterOP | null
value: any
hidden?: boolean
}
type FilterOP =
| 'gt' //>
| 'lt' //<
| 'eq' //=
| 'gte' //>=
| 'lte' //<=
| 'bet' //between
| 'not' //<>
| 'nn' // not null
| 'null' // null
| 'sw' //'like', $this->value . '%'
| 'ew' //'like', '%' . $this->value
| 'has' // 'like', '%' . $this->value . '%'
| 'in' // in array
//const available
//const availableFilterType = {}
interface TableOutput<T> {
rows: T[]
query: TableInput
}
interface TableOutputMongo<T> {
rows: T[]
total: number
page: number
te: TableInputMongo
}
interface BaseTableEmit {
reload: () => void
}
export type {
BaseTableColumn,
TableInput,
TableOutput,
BaseTableEmit,
Filter,
FilterType,
FilterOP,
ColumnTypeFilter,
TableSort,
TableInputMongo,
TableOutputMongo
//availableFilterType
}

View File

@ -0,0 +1,740 @@
<template>
<div :class="[prefixCls]">
<ASpin tip="در حال دریافت اطلاعات..." :indicator="indicator" :spinning="loading">
<!-- <div ref="skeletonWidth" class="w-full"></div>-->
<!-- <transition>-->
<!-- TODO Check -->
<!-- relative h-full-->
<div class="scrollable-container" id="components-affix-demo-target" ref="containerRef">
<!--- TODO remove AFFix -->
<AAffix :target="() => containerRef">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-1">
<!-- <AInputSearch placeholder="جستجو" enter-button />-->
<AInput
@keyup.enter="handleSearchText"
placeholder="جستجو"
class="pl-0"
size="small"
v-model:value="searchText"
>
<template #suffix>
<BasicButton
type="text"
icon="vuesax-linear:search-normal-1"
icon-size="15"
class="text-gray cursor-pointer"
@click="handleSearchText"
/>
</template>
</AInput>
<div class="flex gap-1 items-center">
<BasicButton
type="text"
class="text-gray-500"
suffixIcon
size="small"
icon="vuesax-linear:refresh"
iconSize="17"
@click="reload"
>
</BasicButton>
<ABadge :count="activeFilterCount">
<BasicButton
type="text"
class="text-gray-500 p-0"
suffixIcon
size="small"
icon="vuesax-linear:filter"
iconSize="17"
@click="showFilterModal = true"
>
فیلتر
</BasicButton>
</ABadge>
<!-- <BasicButton
type="text"
class="text-gray-500"
suffixIcon
size="small"
icon="vuesax-linear:setting-2"
iconSize="17"
>
</BasicButton>
<BasicButton
type="text"
class="text-gray-500"
suffixIcon
size="small"
icon="vuesax-linear:printer"
iconSize="17"
>
</BasicButton>
<BasicButton
type="text"
class="text-gray-500"
suffixIcon
size="small"
icon="vuesax-linear:direct-inbox"
iconSize="17"
>
</BasicButton>-->
</div>
</div>
<div>
<slot name="extraHeader" />
</div>
</div>
</AAffix>
<ATable
:columns="_columns"
:data-source="data"
v-bind="_props"
:pagination="false"
:scroll="{ y: '62.7vh' }"
@resizeColumn="handleResizeColumn"
>
<template #bodyCell="{ ...data }">
<template v-for="item in slotColumns">
<template v-if="data.column.key === item.key">
<slot :name="item.slot" v-bind="data"></slot>
</template>
</template>
</template>
<!-- <template #loading> sdfsdfsdfsd </template>-->
</ATable>
<!-- absolute bottom-0 w-full-->
<!-- absolute bottom-0-->
<div class="flex items-center justify-between mt-5 w-full">
<span> {{ totalRecord }} مورد یافت شد </span>
<div class="flex items-center gap-2 justify-between">
<APagination
:pageSize="tableInput?.limit"
:current="tableInput?.page"
:total="totalRecord"
show-less-items
:show-size-changer="false"
@change="handleChangePage"
/>
<ASelect
:options="pageSizeOptions"
:value="tableInput?.limit"
@change="handleChangePageSize"
></ASelect>
</div>
</div>
</div>
</ASpin>
<!-- <div v-else>
<ContentLoader
:width="skeletonWidthSize.width"
height="500"
:viewBox="`0 0 ${skeletonWidthSize.width} 500`"
backgroundColor="#f5f5f5"
foregroundColor="#dbdbdb"
>
&lt;!&ndash; top &ndash;&gt;
<rect x="5" y="0" rx="3" ry="3" width="130" height="32" />
<circle :cx="skeletonWidthSize.width - 495" cy="15" r="15" />
<circle :cx="skeletonWidthSize.width - 445" cy="15" r="15" />
<circle :cx="skeletonWidthSize.width - 395" cy="15" r="15" />
<rect :x="skeletonWidthSize.width - 345" y="0" rx="3" ry="3" width="64" height="32" />
<circle :cx="skeletonWidthSize.width - 235" cy="15" r="15" />
<rect :x="skeletonWidthSize.width - 205" y="0" rx="3" ry="3" width="205" height="32" />
&lt;!&ndash; framework &ndash;&gt;
&lt;!&ndash; <rect x="5" y="39" rx="3" ry="3" :width="skeletonWidthSize.width - 5" height="5" />&ndash;&gt;
&lt;!&ndash; <rect x="5" y="39" rx="3" ry="3" width="5" :height="skeletonHeight" />&lt;!&ndash;&ndash;&gt;&ndash;&gt;
&lt;!&ndash; <rect
:x="skeletonWidthSize.width - 5"
y="39"
rx="3"
ry="3"
width="5"
:height="skeletonHeight"
/>&ndash;&gt;
&lt;!&ndash; <rect
x="5"
:y="skeletonHeight + 39"
rx="3"
ry="3"
:width="skeletonWidthSize.width - 5"
height="5"
/>&ndash;&gt;
&lt;!&ndash; items &ndash;&gt;
<rect x="5" :y="50" rx="3" ry="3" :width="skeletonWidthSize.width - 5" height="60" />
<rect x="5" :y="120" rx="3" ry="3" :width="skeletonWidthSize.width - 5" height="60" />
<rect x="5" :y="190" rx="3" ry="3" :width="skeletonWidthSize.width - 5" height="60" />
<rect x="5" :y="260" rx="3" ry="3" :width="skeletonWidthSize.width - 5" height="60" />
<rect x="5" :y="330" rx="3" ry="3" :width="skeletonWidthSize.width - 5" height="60" />
&lt;!&ndash; bottom &ndash;&gt;
<rect x="5" :y="skeletonHeight + 60" rx="3" ry="3" :width="60" height="31" />
<circle :cx="90" :cy="skeletonHeight + 75" r="15" />
<circle :cx="125" :cy="skeletonHeight + 75" r="15" />
<circle :cx="160" :cy="skeletonHeight + 75" r="15" />
<rect
:x="skeletonWidthSize.width - 200"
:y="420"
rx="3"
ry="3"
:width="skeletonWidthSize.width - 25"
height="31"
/>
</ContentLoader>
</div>-->
<!-- </transition>-->
<BasicModal
@close="handleCancelFilter"
v-model:visible="showFilterModal"
title="فیلتر"
width="60%"
@ok="applyFilter"
:wrapClassName="`${prefixCls}-filter-modal`"
>
<div>
<div class="flex items-center gap-2">
<ASelect placeholder="مرتب سازی"
class="flex-1"
:options="sortableColumns"
v-model:value="orderColumnKey"
allow-clear
></ASelect>
<BaseButton
@click="toggleOrder"
type="text"
class="text-gray-500"
suffixIcon
:iconClass="`${order === 'asc' ? 'rotate-180' : ''}`"
size="small"
icon="vuesax-linear:sort"
iconSize="17"
>
</BaseButton>
</div>
<ADivider class="!my-2" />
<!-- {{ filterColumns }}-->
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2" v-for="(item, index) in filters" :key="index">
<div class="flex-1 flex gap-1">
<ASelect
v-model:value="filters[index].cr"
class="!w-14"
v-if="index != 0"
:options="[
{ label: 'و ', value: 'and' },
{ label: 'یا ', value: 'or' }
]"
/>
<!-- <span v-else-if="filters.length > 1" class="w-15"></span>-->
<!-- <div class="grid grid-cols-1">
</div>-->
<ASelect placeholder="انتخاب کنید"
@change="handleChangeFilterColumn(filters[index].field, index)"
:options="filterColumns"
class="flex-1"
v-model:value="filters[index].field"
/>
<!-- @change="handleChangeFilterColumn(filters[index].field)"-->
<!-- @change="handleChangeFilterColumn(filters[index].field)"-->
<!-- filterOperatorList-->
</div>
<!-- <ASelect
:options="getFilterOp(filters[index].field)"
class="flex-1"
v-model:value="filters[index].op"
/>-->
<div class="flex-1">
<!-- 11111-->
<!-- {{ filters[index] }}-->
<Component placeholder="مقدار را وارد کنید"
v-bind="getComponentProps(filters[index].field)"
:is="getComponentFilter(filters[index].field)"
class="flex-1"
v-model:value="filters[index].value"
/>
<!-- v-model:checked="filters[index].value"-->
</div>
<!-- {{ filters[index].field }}-->
<BasicButton
icon="vuesax-linear:trash"
iconSize="18"
iconClass="text-red"
@click="handleDeleteFilter(index)"
type="text"
></BasicButton>
</div>
</div>
<BasicButton icon="vuesax-linear:add" type="text" @click="handleAddFilter" class="mt-3">
افزودن فیلتر
</BasicButton>
</div>
</BasicModal>
</div>
</template>
<script lang="tsx" setup>
import type {
BaseTableColumn,
BaseTableEmit,
Filter,
TableInput,
TableInputMongo,
TableOutput,
TableOutputMongo
} from '../model/baseTable'
import { Input, Select } from 'ant-design-vue'
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
import { LoadingOutlined } from '@ant-design/icons-vue'
//import { filterOperatorList } from '../hooks/filterOperator'
import { computed, ref } from 'vue'
import { mapColumnTypeFilterToFilterType } from '../model/baseTable'
import {
availableFilterType
//MapFilterOpToFaString
} from '../hooks/filterOperator'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import { usePrefix } from '@/composable/usePrefix'
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker'
import BaseSwitch from '@/components/BasicPacks/BaseSwitch'
import {cloneDeep,get,omit} from 'lodash'
//import { mapColumnTypeFilterToFilterType } from '@core/type/baseTable'
//import { useElementSize } from '@vueuse/core'
//import { ContentLoader } from 'vue-content-loader'
//skeleton
//const skeletonWidth = ref(null)
/*const skeletonWidthSize = reactive(
useElementSize(skeletonWidth, { width: 0, height: 0 }, { box: 'border-box' })
)*/
//const skeletonHeight = 357
/*const skeletonHeight = ref(null)
const skeletonHeightSize = reactive(
useElementSize(skeletonHeight, { width: 0, height: 0 }, { box: 'border-box' })
)*/
/*
watch(skeletonHeightSize, (v) => {
console.log('ttt', v)
})
*/
interface Props {
bordered?: boolean
driver?: 'laravel-sql' | 'mongodb'
mode?: 'simple' | 'pro'
columns?: BaseTableColumn[]
defaultPageLimit?: number
scroll?: { x: true | string | number; y?: string | number }
api?: (params?: TableInput | TableInputMongo) => Promise<TableOutput<any> | TableOutputMongo<any>>
pageSize?: number[]
}
const props = withDefaults(defineProps<Props>(), {
bordered: false,
driver: 'laravel-sql',
mode: 'pro',
columns: () => [],
scroll: () => ({ x: true }), //y: '100%' //y: 600
pageSize: () => [5, 10, 20, 50, 100],
defaultPageLimit: 10
})
const tableInput = ref<TableInput>({ page: 1, limit: props.defaultPageLimit, select: [] })
const tableOutput = ref<TableOutput<any> | TableOutputMongo<any> | null>(null)
const { prefixCls } = usePrefix('base-table')
const searchText = ref<string | null>('')
const showFilterModal = ref<boolean>(false)
const filters = ref<Filter[]>([])
const order = ref<'asc' | 'desc'>('asc')
const orderColumnKey = ref<string | null>(null)
const containerRef = ref()
//const route = useRoute()
const loading = ref<boolean>(false)
const _props = computed(() => {
return omit(props, ['columns', 'pagination', 'dataSource'])
})
const emit = defineEmits<{
(e: 'register', data: BaseTableEmit): void
}>()
const mapComponent = {
Text: Input,
Date: BaseDatePicker,
Time: BaseDatePicker,
DateTime: BaseDatePicker,
Select: Select,
Boolean: BaseSwitch, //Switch,
Number: Input
}
const indicator = h(LoadingOutlined, {
style: {
fontSize: '24px'
},
spin: true
})
const transformTableInput = computed(() => {
if (props.driver == 'mongodb') {
const data: TableInputMongo = {
f: [],
s: {},
p: { c: tableInput.value.page as number, s: tableInput.value.limit as number },
o: {}
}
return { te: data }
//Object
}
return tableInput.value
})
function getComponentProps(columnDataIndex: string) {
const column = _columns.value.find((column) => {
return column.dataIndex == columnDataIndex || column.filterField == columnDataIndex
})
if (column && column.filterComponentProps) return column.filterComponentProps
return {}
}
/*watch(showFilterModal, () => {
console.log('change filter modal show')
})*/
function toggleOrder() {
if (order.value == 'asc') order.value = 'desc'
else order.value = 'asc'
}
function findColumn(columnDataIndex: string): BaseTableColumn | undefined {
const cols = get(props, 'columns', [])
return cols.find((column) => {
return column.dataIndex == columnDataIndex || column.filterField == columnDataIndex
})
}
function getComponentFilter(columnDataIndex: string) {
// console.log('ttt', columnDataIndex)
const column = findColumn(columnDataIndex)
//console.log('col', column)
if (column) {
if (Object.hasOwn(column, 'filterComponent')) {
//@ts-ignore
return markRaw(toRaw(column.filterComponent))
}
if (column.type && Object.hasOwn(mapComponent, column.type)) {
//console.log('aaa', column.type)
return mapComponent[column.type]
}
}
return Input
}
//column: Partial<BaseTableColumn>, filter: Filter
function handleChangeFilterColumn(columnDataIndex: string, index: number) {
// console.log(filters.value[index])
filters.value[index].value = ''
const column = findColumn(columnDataIndex)
//console.log('col', column)
if (column) {
//console.log()
filters.value[index].type = mapColumnTypeFilterToFilterType[column?.type ?? 'Text']
filters.value[index].op = get(availableFilterType[column?.type ?? 'Text'], '[0]', null)
filters.value[index].op = 'has'
//filter.type = mapColumnTypeFilterToFilterType[column?.type ?? 'Text']
}
//console.log('aaa', a, b, c)
//return
//filter.type = mapColumnTypeFilterToFilterType[column?.type ?? 'Text']
//console.log('ffff', filter.type, column)
//console.log(a, b, c, d)
}
async function applyFilter() {
filters.value = filters.value.filter((item) => {
return item.field != ''
})
tableInput.value.filters = cloneDeep(filters.value)
pageReset()
showFilterModal.value = false
await callApi()
}
const activeFilterCount = computed(() => {
return tableInput.value?.filters?.length ?? 0
})
function handleCancelFilter() {
filters.value = cloneDeep(tableInput.value.filters ?? [])
showFilterModal.value = false
orderColumnKey.value = null
}
const pageSizeOptions = computed(() => {
return props.pageSize.map((item) => {
return { label: `${item} ردیف`, value: item }
})
})
const _columns = computed(() => {
const cols = cloneDeep(get(props, 'columns', [])).filter((column) => {
return (
(Object.hasOwn(column, 'show') && column.show == true) || !Object.hasOwn(column, 'show')
//&& !Object.hasOwn(column, 'slot')
)
})
cols.forEach((column) => {
if (!Object.hasOwn(column, 'align')) {
column.align = 'center'
}
//@ts-ignore
column.dataIndex = column.dataIndex.split('.')
//if(isArray(column.dataIndex))
/*if(!isArray(column.dataIndex)){
}*/
//column.dataIndex = column.dataIndex.split('.')
/*if(){
column.dataIndex=
}*/
})
return cols
})
const filterColumns = computed(() => {
const cols = get(props, 'columns', [])
.filter((column) => {
return !Object.hasOwn(column, 'filterable') || column.filterable
})
.map((column) => {
return {
type: column.type,
label: column.title,
value: get(column, 'filterField', column.dataIndex)
} //type: column.type ?? 'Text'
})
//console.log('cols', cols)
return cols
})
const sortableColumns = computed(() => {
const cols = get(props, 'columns', [])
.filter((column) => {
return !Object.hasOwn(column, 'sortable') || column.sortable
})
.map((column) => {
return {
//type: column.type,
label: column.title,
value: get(column, 'sortField', get(column, 'filterField', column.dataIndex))
} //type: column.type ?? 'Text'
})
//console.log('cols', cols)
return cols
})
/*
const filterOperators = computed(() => {
//return filterOperatorList
return
})
*/
async function handleSearchText() {
tableInput.value.search = searchText.value
pageReset()
await callApi()
}
function handleDeleteFilter(index: number) {
filters.value.splice(index, 1)
}
function handleAddFilter() {
filters.value.push({
field: null,
type: 'text',
value: '',
cr: 'and',
op: null,
hidden: false
})
//console.log('sadsadasd', filters.value)
}
const data = computed(() => {
if (tableOutput.value != null) {
const temp = get(tableOutput.value, 'rows', [])
/* temp.push(...temp)
temp.push(...temp)
temp.push(...temp)
temp.push(...temp)*/
return temp //get(tableOutput.value, 'rows', [])
}
return []
})
/*function getFilterOp(columnDataIndex: string) {
// console.log('aaa', columnDataIndex)
// console.log('find', findColumn(columnDataIndex))
const column = findColumn(columnDataIndex)
if (column) {
if (Object.hasOwn(availableFilterType, column.type ?? 'Text')) {
const temp = availableFilterType[column.type ?? 'Text']
// console.log('teeee', temp)
return temp.map((item) => ({
label: MapFilterOpToFaString[item],
value: item
}))
}
return []
//return availableFilterType.console.log('column: ', column)
}
return []
}*/
const totalRecord = computed(() => {
/* if (props.driver === 'mongodb') {
return get(tableInput.value, 'total', 0)
}*/
return get(tableInput.value, 'total', 0)
})
console.log(totalRecord.value,'aaaa')
const slotColumns = computed(() => {
return _columns.value.filter((column) => Object.hasOwn(column, 'slot'))
})
function handleResizeColumn(w: number, col: any) {
col.width = w
}
async function handleChangePageSize(value: number, option: any) {
if (tableInput.value) {
tableInput.value.limit = value
}
await reload()
}
function pageReset() {
tableInput.value.page = 1
}
async function handleChangePage(page: number, pageSize: number) {
tableInput.value.page = page
await callApi()
}
async function reload() {
await callApi()
}
async function callApi() {
const api = get(_props.value, 'api', null)
if (api != null) {
try {
loading.value = true
if (orderColumnKey.value != null) {
tableInput.value.sort = { key: orderColumnKey.value, order: order.value }
if (order.value == 'asc') {
tableInput.value.order = `+${orderColumnKey.value}`
} else {
tableInput.value.order = `-${orderColumnKey.value}`
}
} else {
delete tableInput.value.order
delete tableInput.value.sort
}
const data = await api(cloneDeep(transformTableInput.value)) //tableInput.value
transformTableOutput(data)
} catch (e) {
console.log(e)
} finally {
loading.value = false
}
}
}
function transformTableOutput(output: TableOutput<any> | TableOutputMongo<any>) {
console.log('1111', data)
if (props.driver === 'mongodb') {
tableOutput.value = output.data
tableInput.value = {
page: output.data.page,
limit: get(output.data, 'te.p.s', 10),
total: output.data.total
}
//TODO for set query
//tableInput.value = output.query
//TODO for set search text
//searchText.value = output.query.search ?? ''
//TODO for ser filters
//filters.value=''
//TODO for set order columns
} else {
tableInput.value = output.query
tableOutput.value = output
searchText.value = output.query.search ?? ''
filters.value = cloneDeep(output?.output?.filters ?? []).filter((item) => {
return Object.hasOwn(item, 'hidden') && item.hidden == false
})
if (output.query.order != undefined) {
if (output.query.order?.startsWith('+') || output.query.order?.startsWith('-')) {
orderColumnKey.value = output.query.order?.slice(1)
} else {
orderColumnKey.value = output.query.order
}
if (output.query.order?.startsWith('-')) {
order.value = 'desc'
} else {
order.value = 'asc'
}
}
}
}
onMounted(async () => {
await callApi()
emit('register', { reload })
//console.log('router', route.query)
})
defineExpose({
loading
})
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-base-table';
.@{prefix} {
@apply h-full;
.ant-table-wrapper {
border: 1px solid #eee;
border-radius: var(--border-radius-base);
}
.ant-spin-container,
.ant-spin-nested-loading {
height: 100%;
@apply relative;
}
.ant-badge-count {
background-color: @primary-color;
}
&-filter-modal {
.ant-select {
width: 100%;
}
}
:where(.css-dev-only-do-not-override-1yhcnou).ant-btn.ant-btn-sm{
height: unset;
}
}
</style>

View File

@ -0,0 +1,68 @@
<!--
<template>
&lt;!&ndash; <van-action-sheet v-model:show="show" title="Title">&ndash;&gt;
<van-action-sheet>
<div><slot/></div>
</van-action-sheet>
</template>
<script setup lang="ts"></script>
<style scoped></style>-->
<template>
<VanActionSheet :z-index="1000" teleport="body" dir="rtl">
<div class="justify-center cursor-pointer flex mt-1" @click="$emit('update:show', false)">
<span class="rounded-sm w-12 border-t-2 border-t-solid border-t-gray-500 mt-2"></span>
</div>
<div class="mx-4 mb-5 mt-3 max-h-90% overflow-y-auto">
<slot></slot>
<div
class="grid gap-3 mb-5 mt-2"
:class="[!props.showOk || !props.showCancel ? 'grid-cols-1' : 'grid-cols-12']"
v-if="props.showButton && (props.showCancel || props.showOk)"
>
<AButton
v-if="props.showCancel"
@click="$emit('cancel')"
type="primary"
:class="[{ 'col-span-4': props.showOk }]"
size="large"
ghost
block
>
{{ props.cancelText }}
</AButton>
<AButton
@click="$emit('ok')"
type="primary"
:class="[{ 'col-span-8': props.showCancel }]"
size="large"
block
v-if="props.showOk"
>
{{ props.okText }}
</AButton>
</div>
</div>
</VanActionSheet>
</template>
<script lang="ts" setup>
interface Props {
cancelText?: string
showButton?: boolean
showCancel?: boolean
showOk?: boolean
okText?: string
}
const props = withDefaults(defineProps<Props>(), {
cancelText: 'بازگشت',
showButton: false,
showCancel: true,
showOk: true,
okText: 'ثبت',
})
</script>
<style lang="less"></style>

View File

@ -0,0 +1,65 @@
<template>
<AButton :type="props.type" :size="props.size" :class="[prefixCls, _class]" :loading="props.loading">
<!-- py-1 px-2-->
<div
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2"
:class="[{ 'flex-row-reverse': suffixIcon }]"
>
<slot v-if="$slots.default"></slot>
<Icon :size="iconSize" :icon="icon" v-if="icon" :class="iconClass" />
</div>
</AButton>
</template>
<script lang="ts" setup>
// import { usePrefix } from '@core/composable/usePrefix'
import {computed, useSlots} from 'vue'
import {baseButtonIconSize} from "@/themeConfig";
import Icon from "@/components/BasicPacks/BasicIcon.vue";
import { usePrefix } from '@/composable/usePrefix'
interface Props {
icon?: string
iconSize?: string
iconClass?: string
suffixIcon?: boolean
type?: string
size?: string
loading?: boolean
// prefixIcon?: string
}
const slot = useSlots()
const _class = computed(() => {
if (!slot.default) {
return '!px-1 !py-1'
}
return ''
})
const _loading = computed(() => {
if (props.loading){
return 'flex'
} else return 'unset'
})
const { prefixCls } = usePrefix('base-button')
const props = withDefaults(defineProps<Props>(), {
iconSize: baseButtonIconSize,
// iconLeft: false,
iconClass: ''
})
</script>
<style lang="less">
@prefix:~'@{prefixCls}-base-button';
.@{prefix}{
//width: fit-content;
height: auto;
/*padding: 8px;*/
display: v-bind(_loading);
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<span
ref="refIcon"
:style="{ color }"
:class="[prefixCls]"
class="!flex"
v-html="tempIcon"
></span>
</template>
<script lang="ts" setup>
import icons from '@/components/BasicPacks/tools/icons/icons.json'
import {computed, onMounted, ref, withDefaults, defineProps} from "vue";
import { usePrefix } from '@/composable/usePrefix'
interface Props {
icon: string
color?: string | null
size?: string | number
}
const props = withDefaults(defineProps<Props>(), {
color: null,
})
const { prefixCls } = usePrefix('icons')
const tempIcon = ref<string>()
const refIcon = ref<HTMLDivElement>()
tempIcon.value = icons[`${props.icon}`] as string
const color = computed(() => {
return props.color
})
onMounted(() => {
if (props.size) {
const svg: SVGElement = refIcon.value?.children[0] as SVGElement
svg.setAttribute('width', props.size as string)
svg.setAttribute('height', props.size as string)
}
})
</script>
<style lang="less" scoped>
@prefix: ~'@{prefixCls}-icons';
.@{prefix} {
& :deep(svg path) {
stroke: currentColor;
}
/* & :deep(svg path) {
fill: currentColor;
}*/
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<div :class="isLine?'flex justify-between items-center':''">
<div class="flex items-center gap-1">
<div class="text-gray-400">{{ title }}:</div>
<BasicIcon v-if="info" icon="vuesax-linear:info-circle" size="18" color="orange" @click="infoVisible = true"/>
</div>
<div v-if="value" class="font-bold mt-1">{{ value }}</div>
<div v-else class="mt-1">
<slot></slot>
</div>
</div>
<van-dialog v-model:show="infoVisible" class="updateButton" confirm-button-text="متوجه شدم"
show-confirm-button @confirm="update()">
<div class="flex flex-col items-center p-2">
<!-- <div class="home_appear_fade_in_up delay-100">به روز رسانی</div>-->
<div class="home_appear_fade_in_up mt-2 text-sm">{{info}}</div>
</div>
</van-dialog>
</template>
<script setup lang="ts">
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { update } from 'lodash'
import { ref } from 'vue'
// import anim from '@/assets/lottie/animInfo.json'
interface Prop {
title: string
info?: string
isLine?: boolean
value?: string | number
}
const props = withDefaults(defineProps<Prop>(),{
isLine: false
})
const infoVisible = ref<boolean>()
</script>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,116 @@
<template>
<div :class="[prefixCls]">
<div>
{{ props.title }}
</div>
<a-input-password
v-if="isPassword"
v-model:value="localValue"
:addon-after="props.addonAfter"
:addon-before="props.addonBefore"
:disabled="props.disabled"
:placeHolder="props.placeHolder"
:size="props.size"
:type="props.type"
class=""
@change="handlerChange"
/>
<a-input
v-else
v-model:value="localValue"
:addon-after="props.addonAfter"
:addon-before="props.addonBefore"
:disabled="props.disabled"
:placeHolder="props.placeHolder"
:size="props.size"
:type="props.type === 'price'? 'tel':props.type"
:inputmode="props.inputmode"
:pattern="props.pattern"
class=""
@change="handlerChange"
/>
</div>
</template>
<script lang="ts" setup>
import {computed} from "vue";
import {usePrice} from "@/components/BasicPacks/BasicPriceInput/src/utils/usePrice";
import { usePrefix } from '@/composable/usePrefix'
//Props
interface Prop {
title?: String
value?: String | Number
size?: String
type?: String
inputmode?: String
pattern?: String
isPassword?: boolean
disabled?: boolean
addonAfter?: String
addonBefore?: String
addonBgColor?: String
addonTextColor?: String
placeHolder?: String
}
const addonBgColor = computed(() => props.addonBgColor)
const addonTextColor = computed(() => props.addonTextColor)
//emits
const localValue = computed({
set(value) {
// console.log(value?.toString().replaceAll(',', ''))
props.type === 'price' ? emits('update:value', value?.toString().replaceAll(',', '')) : emits('update:value', value)
},
get() {
let a = props.type === 'price' ? props.value ? usePrice(props.value as number, true, '').trim() : '' : props.value
// console.log(props.value)
return a
},
})
const props = withDefaults(defineProps<Prop>(), {
isPassword: false
})
const emits = defineEmits(['update:value', 'onchange'])
const handlerChange = (e: any) => {
// emits('onchange', e.target.value)
props.type === 'price' ? emits('update:value', e.target.value?.toString().replaceAll(',', '')) : emits('update:value', e.target.value)
props.type === 'price' ? emits('onchange', e.target.value?.toString().replaceAll(',', '')) : emits('onchange', e.target.value)
// console.log('sas')
}
//css style
const {prefixCls} = usePrefix('input')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-input';
.@{prefix} {
/* .ant-input-group-addon:first-child {
!* border: #d9d9d9 1px solid;
border-radius: 0 6px 6px 0;*!
}*/
.ant-input-group {
direction: ltr;
}
.ant-input-group > .ant-input-rtl:first-child,
.ant-input-group-rtl .ant-input-group-addon:first-child {
border-radius: @border-radius-base 0 0 @border-radius-base;
}
.ant-input-group > .ant-input:last-child,
.ant-input-group-addon:last-child {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
.ant-input-group-addon {
background-color: v-bind(addonBgColor);
color: v-bind(addonTextColor);
}
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<a-modal
centered
v-model:open="localValue"
:title="props.title"
@ok="emits('ok')"
@cancel="emits('close')"
@close="emits('close')"
:width="props.width"
:ok-text="props.okText"
:cancel-text="props.cancelText"
:wrapClassName="props.wrapClassName"
>
<template v-if="props.customFooter" #footer>
<slot name="footer" />
</template>
<a-spin :spinning="props.loading">
<div class="overflow-y-auto py-2 px-1 relative max-h-[80vh]" id="body">
<slot />
</div>
</a-spin>
</a-modal>
</template>
<script setup lang="ts">
//define emit
import { computed } from 'vue'
const emits = defineEmits(['ok', 'close', 'update:visible'])
//define props
interface Prop {
visible: boolean
title?: string
okText?: string
width?: string
cancelText?: string
customFooter?: boolean
loading?: boolean
wrapClassName?: string
}
const props = withDefaults(defineProps<Prop>(), {
visible: false,
cancelText: 'انصراف',
okText: 'ثبت',
customFooter: false,
loading: false,
wrapClassName: ''
})
const localValue = computed({
get() {
return props.visible
},
set(value) {
emits('update:visible', value)
}
})
</script>
<style>
.ant-modal-content {
border-radius: 0.6rem;
padding-top: 0.5rem;
}
</style>

View File

@ -0,0 +1,3 @@
import BasicPriceInput from './src/BasicPriceInput.vue'
export default BasicPriceInput

View File

@ -0,0 +1,36 @@
<template>
<BasicInput v-model:value="_val">
<template v-for="(_, slotKey) in $slots" #[slotKey]="data">
<slot :name="slotKey" v-bind="data || {}"></slot>
</template>
</BasicInput>
</template>
<script lang="ts" setup>
import {usePrice} from './utils/usePrice'
import {computed} from "vue";
import BasicInput from "../../BasicInput.vue";
interface Props {
value?: string | number
}
const emit = defineEmits<{
(e: 'update:value', val: string | number): void
}>()
const props = withDefaults(defineProps<Props>(), {})
const _val = computed({
set(val: string) {
// const temp = val.toString().replaceAll(',', '')
const temp = val.toString().replaceAll(',', '')
emit('update:value', temp)
},
get() {
return props.value ? usePrice(props.value as number, true, '').trim() : ''
}
})
</script>
<style lang="less"></style>

View File

@ -0,0 +1,17 @@
export function usePrice(price: number | 0, digit = true, unit = 'ریال') {
let _price = price?.toString()
if (digit) {
_price = _price?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
return `${_price} ${unit}`
}
export function useTowDecimal(num: string | number): string | number {
const temp = num.toString().match(/^-?\d+(?:\.\d{0,2})?/)
if (temp && temp.length > 0) return temp[0]
return Math.round(+num * 100) / 100
}
export function useFormat(num: string | number, unit = '') {
return usePrice(Math.abs(+useTowDecimal(num)), true, unit)
}

View File

@ -0,0 +1,126 @@
<template>
<div :class="[prefixCls]">
<div>
{{ props.title }}
</div>
<a-select
class="w-full"
:size="props.size"
v-model:value="localValue"
:disabled="props.disabled"
show-search
:mode="props.mode"
:style="props.style"
:multiple="props.multiple"
@change="handleChange"
:placeholder="props.placeholder"
:allowClear="props.allowClear"
:filter-option="filterOption"
>
<a-select-option
v-for="(val, i) in props.items"
:key="i"
:label="get(val, props.valueName)"
:value="get(val, props.keyName)"
>
<a-tooltip v-if="props.hasTooltip" placement="left" class="cursor-pointer">
<template #title>
<span>{{ get(val, props.valueName) }}</span>
</template>
<div class="truncate max-w-40">{{ get(val, props.valueName) }}</div>
</a-tooltip>
<div v-else>{{ get(val, props.valueName) }}</div>
</a-select-option>
</a-select>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@/composable/usePrefix'
import { get } from 'lodash'
import { computed, onMounted } from 'vue'
//Props
interface Prop {
title?: string
value?: string | number | boolean | string[] | number[]
valueText?: string | number | boolean | string[] | number[]
style?: string
size?: string
keyName: string
disabled?: boolean
mode?: string
valueName: string
multiple?: boolean
items?: any[]
placeholder?: string
allowClear?: boolean
hasTooltip?: boolean
}
const props = withDefaults(defineProps<Prop>(), { allowClear: true, hasTooltip: true })
// console.log('props.items ====>', props.items)
//define emits
const emit = defineEmits(['update:value', 'change', 'update:valueText'])
onMounted(() => {
for (let val in props.items) {
// console.log('new', get(props.items[val], props.valueName))
}
})
const localValue = computed({
get() {
return props.value
},
set(value) {
emit('update:value', value)
}
})
function setValueText(val: any) {
// console.log(val, get(val, props.valueName))
localTextValue.value = get(val, props.valueName)
}
const localTextValue = computed({
get() {
return props.valueText
},
set(value) {
emit('update:valueText', value)
}
})
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
const handleChange = (e: any) => {
// console.log('e ====>', e)
setValueText(props.items.find((x) => get(x, props.keyName) === e))
emit(
'change',
props.items.find((x) => get(x, props.keyName) === e)
)
}
//css style
const { prefixCls } = usePrefix('select')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-select';
.@{prefix} {
//padding: 5px;
/* .ant-select {
.ant-select-selector {
border-color: @primary-color;
border-radius: @border-radius-base;
}
}*/
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<div v-if="!props.loading">
<div class="gap-2">
<a-tag
v-for="item in props.items"
:color="props.color"
:closable="props.closable"
@close="emits('delete', item.id)"
class="cursor-pointer"
@click="emits('edit', item.id)"
>{{ item.name }}
</a-tag>
<a-tag
v-if="props.hasAdd"
style="background: #fff; border-style: dashed; margin-top: 0.5rem"
@click="emits('add')"
class="cursor-pointer"
>
<div class="flex items-center">
<BaseIcon icon="vuesax-linear:add" size="0.7rem" />
<div class="mr-1">افزودن</div>
</div>
</a-tag>
</div>
</div>
<div v-else>
<ContentLoader
width="350"
height="100"
viewBox="0 0 350 100"
backgroundColor="#f5f5f5"
foregroundColor="#dbdbdb"
>
<rect x="0" y="0" rx="3" ry="3" width="80" height="20" />
<rect x="90" y="0" rx="3" ry="3" width="80" height="20" />
<rect x="180" y="0" rx="3" ry="3" width="80" height="20" />
<rect x="270" y="0" rx="3" ry="3" width="80" height="20" />
</ContentLoader>
</div>
</template>
<script setup lang="ts">
import { ContentLoader } from 'vue-content-loader'
//define emits
const emits = defineEmits(['add', 'edit', 'delete'])
//define props
interface Prop {
items?: Array<string>
color: string
loading?: boolean
closable?: boolean
hasAdd?: boolean
}
const props = withDefaults(defineProps<Prop>(), {
closable: false,
hasAdd: true
})
/*watch(props, (val) => {
console.log('val ====>', val)
})*/
</script>
<style scoped></style>

View File

@ -0,0 +1,4 @@
export interface TagItem {
id: number | undefined
name: string | undefined
}

View File

@ -0,0 +1,52 @@
<template>
<div :class="[prefixCls]">
<div>
{{ props.title }}
</div>
<a-textarea
v-model:value="localValue"
class=""
:type="props.type"
:show-count="props.showCount"
:placeHolder="props.placeHolder"
/>
</div>
</template>
<script setup lang="ts">
import {computed} from "vue";
import { usePrefix } from '@/composable/usePrefix'
//Props
interface Prop {
title?: string
value: string | null
showCount?: boolean
maxlength?: number
placeHolder?: string
}
const props = defineProps<Prop>()
const emit = defineEmits(['update:value'])
const localValue = computed({
get() {
return props.value
},
set(value) {
emit('update:value', value)
}
})
//css style
const { prefixCls } = usePrefix('input-area')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-input-area';
.@{prefix} {
}
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,69 @@
<template>
<a-upload-dragger
v-model:fileList="fileList"
name="file"
:multiple="props.multiple"
:action="props.url"
@change="handleChange"
:headers="props.headers"
:maxCount="props.maxCount"
:showUploadList="true"
>
<div class="py-10 px-5">
<BaseIcon icon="vuesax-linear:document-upload" size="40" color="#4A5568" />
<p class="ant-upload-text">برای آپلود، روی فایل کلیک کنید یا فایل را درون این قسمت بکشید</p>
</div>
</a-upload-dragger>
</template>
<script lang="ts" setup>
import { InboxOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { defineComponent, ref } from 'vue'
import type { UploadChangeParam } from 'ant-design-vue'
const handleChange = (info: UploadChangeParam) => {
// console.log(info)
const status = info.file.status
if (status !== 'uploading') {
// console.log(info.file, info.fileList)
/*emits('ok', info.file.response.id)*/
}
if (status === 'done') {
// message.success(`${info.file.name} file uploaded successfully.`)
console.log(fileList)
emits('ok', info.file.response.id)
} else if (status === 'error') {
message.error(`خطا در بارگذاری فایل`)
} else if (status === 'removed') {
emits('delete', info.file.uid)
}
}
//props
interface Prop {
url: string
multiple?: boolean
fileList?: Array<FileList>
maxCount?: number
headers?: any
}
const props = defineProps<Prop>()
//emits
const emits = defineEmits(['update:fileList', 'ok', 'delete'])
// const fileList = ref([])
const fileList = computed({
get() {
return props.fileList || ([] as Array<FileList>)
},
set(value) {
emits('update:fileList', value)
}
})
</script>
<style scoped></style>

View File

@ -0,0 +1,4 @@
export interface FileList {
uid?: any | null | undefined
name?: any | null | undefined
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
<template>
<span
ref="refIcon"
:style="{ color, fill: color }"
class="!flex examplee"
v-html="tempIcon"
></span>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import icons from '../assets/icons/bulk-icon.json'
// import { usePrefix } from '@/composable/usePrefix'
interface Props {
icon: string
color?: string | null
size?: string | number
}
const props = withDefaults(defineProps<Props>(), {
// color: '#D1D5DB',
size: undefined
})
// const siza=computed(()=>{
// return `w-[${props.size}px] h-[${props.size}px]`
// })
// const { prefixCls } = usePrefix('icons')
const tempIcon = ref<string>()
const refIcon = ref<HTMLDivElement>()
tempIcon.value = icons[`${props.icon}`] as string
const color = computed(() => {
return props.color
})
onMounted(() => {
if (props.size) {
const svg: SVGElement = refIcon.value?.children[0] as SVGElement
svg.setAttribute('width', props.size as string)
svg.setAttribute('height', props.size as string)
}
})
</script>
<style lang="less">
//@prefix:~ '@{prefixCls}-icons';
//
//.@{prefix} {
// & :deep(svg path) {
// stroke: currentColor;
// }
//}
.examplee svg path {
//stroke: v-bind(color);
fill: v-bind(color);
}
</style>

View File

@ -0,0 +1,20 @@
<template>
<div
:style="{ color: props.textColor, backgroundColor: props.bgColor }"
class="w-fit p-1 rounded-lg"
>
<slot />
</div>
</template>
<script setup lang="ts">
//props
interface Prop {
textColor: string
bgColor: string
}
const props = defineProps<Prop>()
</script>
<style scoped></style>

View File

@ -0,0 +1,90 @@
<template>
<!-- <div v-if="modelValue">-->
<van-dialog class="oneButtonDialog">
<div class="my-4">
<div class="w-full flex justify-center">
<slot name="img">{{ text2 }}</slot>
</div>
<div class="w-full flex justify-center text-[18px] font-semibold">
<slot name="title">{{ text1 }}</slot>
</div>
<div class=" border-t border-solid border-[#E5E7EB] pt-4 mx-2 mt-6 font-semibold text-[15px]">
<slot name="button">{{ text3 }}</slot>
</div>
</div>
</van-dialog>
<!-- <van-dialog style="background-color: #2b3f6e" show-cancel-button cancelButtonText="بیخیال" confirmButtonText="بله" cancelButtonColor="">-->
<!-- <div class="w-full mt-6 text-white ">-->
<!-- <div class="w-full flex mb-[10px] ">-->
<!-- <Icon icon="vuesax-linear:info-circle" class="mx-4" size="25"/>-->
<!-- <span class="text-lg flex-1">-->
<!-- <slot name="title">{{text1}}</slot>-->
<!-- </span>-->
<!-- </div>-->
<!-- <div class="flex justify-start mb-6 mt-8" style="font-weight:700;">-->
<!-- <slot name="button">{{text2}}</slot>-->
<!-- </div>-->
<!-- </div>-->
<!-- </van-dialog>-->
<!-- </div>-->
</template>
<script lang="ts" setup>
// import Icon from "/@/assets/icons/Icon.vue";
import {ref, watch} from 'vue';
const show = ref(true);
const props = withDefaults(defineProps<{
// modelValue: boolean,
timeOut: number,
text1?: string,
text2?: string,
text3?: string
// top?: string,
}>(), {})
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
watch(() => props.modelValue, (value) => {
if (props.timeOut !== -1) showNotify()
})
const showNotify = () => {
console.log('notify', show.value)
setTimeout(() => {
emit('update:modelValue', false)
}, props.timeOut);
};
</script>
<style lang="less">
.notify-enter-active {
animation: notify-in 0.2s;
}
.notify-leave-active {
animation: notify-in 0.2s reverse;
}
.oneButtonDialog .van-dialog__confirm, .van-dialog__cancel {
display: none !important;
}
.van-dialog__confirm, .van-dialog__cancel {
//background-color: ;
}
.oneButtonDialog .van-dialog__confirm .van-button__text {
color: #1C2D56;
font-weight: 600;
font-size: 15px;
}
.oneButtonDialog .van-dialog__cancel .van-button__text {
color: #6b7280;
font-weight: 600;
font-size: 15px;
}
</style>

52
src/components/Icon.vue Normal file
View File

@ -0,0 +1,52 @@
<template>
<span
ref="refIcon"
:style="{ color }"
:class="[prefixCls]"
class="!flex"
v-html="tempIcon"
></span>
</template>
<script lang="ts" setup>
import icons from '@/components/BasicPacks/tools/icons/icons.json'
import {usePrefix} from "@/utils/usePrefix";
import {computed, onMounted, ref, withDefaults, defineProps} from "vue";
interface Props {
icon: string
color?: string | null
size?: string | number
}
const props = withDefaults(defineProps<Props>(), {
color: null,
})
const { prefixCls } = usePrefix('icons')
const tempIcon = ref<string>()
const refIcon = ref<HTMLDivElement>()
tempIcon.value = icons[`${props.icon}`] as string
const color = computed(() => {
return props.color
})
onMounted(() => {
if (props.size) {
const svg: SVGElement = refIcon.value?.children[0] as SVGElement
svg.setAttribute('width', props.size as string)
svg.setAttribute('height', props.size as string)
}
})
</script>
<style lang="less" scoped>
@prefix: ~'@{prefixCls}-icons';
.@{prefix} {
& :deep(svg path) {
stroke: currentColor;
}
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<div :class="[prefixCls]">
<AButton class="w-full flex justify-center" :size="props.size" :type="props.type" :disabled="disabled">
<template class="flex items-center gap-2">
<Icon :icon="props.icon" :size="props.iconSize" />
<slot></slot>
</template>
</AButton>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@/composable/usePrefix'
import Icon from '@/components/Icon.vue'
interface Props {
icon: string
iconSize?: number | string
size?: string
type?: string
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
iconSize: 20,
size: 'middle',
type: 'default',
disabled: false
})
const { prefixCls } = usePrefix('icon-button')
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-icon-button';
.@{prefix} {
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<div class="">
<div class="text-gray-400">{{ title }}:</div>
<div v-if="value" class="font-bold mt-1">{{ value }}</div>
<slot v-else></slot>
</div>
</template>
<script setup lang="ts">
interface Prop {
title: string
value?: string | number
}
const props = defineProps<Prop>()
</script>
<style scoped lang="less">
</style>

View File

@ -0,0 +1,89 @@
<template>
<div class="install-container">
<button v-show="pwaStore.showInstall" @click="install" class="install-btn fade-slide-in">
<!-- <button @click="install" class="install-btn fade-slide-in">-->
نصب اپلیکیشن
</button>
</div>
</template>
<script setup lang="ts">
import { usePWAStore } from '@/store/usePWAStore'
const pwaStore = usePWAStore()
const install = async () => {
if (!pwaStore.deferredPrompt) return
pwaStore.deferredPrompt.prompt()
const result = await pwaStore.deferredPrompt.userChoice
if (result.outcome === 'accepted') {
console.log('User accepted install')
}
pwaStore.clearPrompt()
}
</script>
<style scoped>
/*.install-btn {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
width: 5rem;
cursor: pointer;
}*/
.install-container {
display: flex;
justify-content: center;
}
@keyframes fadeSlideIn {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.fade-slide-in {
animation: fadeSlideIn 0.5s ease forwards;
}
.install-btn {
padding: 0.8rem 2rem;
background: linear-gradient(to right, #214368, #2b5a92);
color: #ffffff;
border: 1px solid #315d96;
border-radius: 14px;
font-size: 16px;
font-weight: 600;
box-shadow:
0 8px 15px rgba(0, 0, 0, 0.2),
0 0 0 2px rgba(255, 255, 255, 0.05) inset;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(2px);
}
.install-btn:hover {
background: linear-gradient(to right, #2b5a92, #214368);
box-shadow:
0 10px 20px rgba(33, 67, 104, 0.6),
0 0 0 2px rgba(255, 255, 255, 0.08) inset;
transform: translateY(-3px);
}
.install-btn:active {
transform: scale(0.97);
box-shadow:
0 4px 8px rgba(33, 67, 104, 0.4),
0 0 0 2px rgba(255, 255, 255, 0.1) inset;
}
</style>

201
src/components/Menu.vue Normal file
View File

@ -0,0 +1,201 @@
<template>
<div :class="[prefixCls]">
<!-- <AMenu-->
<!-- class="p-2 flex bg-[#28C792] rounded-3xl justify-evenly md:justify-center h-5.4rem md:h-unset"-->
<!-- v-model:selected-keys="current"-->
<!-- mode="horizontal"-->
<!-- @change="changeHandler"-->
<!-- >-->
<!-- <template v-for="item in items">-->
<!-- <AMenuItem-->
<!-- class="!px-3 md:w-[180px]"-->
<!-- :key="item.key"-->
<!-- v-if="item.show"-->
<!-- @click="item.key === 'mobileProfile' ? showProfile() : changeHandler(item?.key)"-->
<!-- >-->
<!-- <div class="flex flex-col items-center gap-1">-->
<!-- <BulkIcon-->
<!-- class="menuItemSelect"-->
<!-- :icon="`vuesax-bulk:${item?.icon}`"-->
<!-- v-if="item.key === current[0]"-->
<!-- />-->
<!-- <Icon :icon="`vuesax-linear:${item?.icon}`" class="menuItem" v-else/>-->
<!-- <span class="text-xs " :class=" item.key === current[0] ? 'text_Secondary' : 'textColor1' ">{{-->
<!-- item?.title-->
<!-- }}</span>-->
<!-- </div>-->
<!-- </AMenuItem>-->
<!-- </template>-->
<!-- </AMenu>-->
<div class=" p-2 flex flex bg-[#28C792] rounded-3xl justify-evenly items-center md:justify-center md:h-unset ">
<div class=" md:w-[180px]" v-for="item in items" @click="item.key === 'mobileProfile' ? showProfile() : changeHandler(item?.key)">
<div class="flex justify-center bg-[#FABF3D] border-black border-1 px-4 py-1 rounded-t-6 rounded-b-4" v-show="item.key==current[0] ||item.childrenName==current[0] ">
<img :src="item.href">
</div>
<div class=" flex justify-center bg-[#28C792] px-4 py-1 " v-show="item.key!=current[0] && item.childrenName!=current[0] ">
<img :src="item.href">
</div>
</div>
</div>
</div>
<ProfileDrawer v-model:open="open" @close="handleDrawerClose"/>
</template>
<script setup lang="ts">
import {onMounted, ref, computed, watch, onBeforeUnmount} from 'vue'
import {usePrefix} from '@/composable/usePrefix'
import Icon from '@/components/Icon.vue'
import router from '@/router'
import BulkIcon from '@/components/BulkIcon.vue'
import ProfileDrawer from '@/components/ProfileDrawer.vue'
import {useUserStore} from '@/store/user'
import homeImg from '@/assets/img/home.png'
import homeImgSelected from '@/assets/img/home.png'
import storyImg from '@/assets/img/audio.png'
import storyImgSelected from '@/assets/img/audio.png'
import luckywheelImg from '@/assets/img/luckywheel.png'
import luckywheelImgSelected from '@/assets/img/luckywheel.png'
import userImg from '@/assets/img/user_120.png'
const open = ref<boolean>(false)
const isMobile = ref(false)
const userStore = useUserStore()
const {prefixCls} = usePrefix('footer')
const current = ref<string[]>([''])
const items = computed(() => [
{
show: true,
title: 'خانه',
icon: 'home',
key: 'home',
href: homeImg,
childrenName:'',
hrefSelected: homeImg
},
{
show: true,
title: 'داستان ها',
icon: 'storys',
key: 'storys',
childrenName:'storyPage',
href: storyImg,
hrefSelected: homeImg
},
{
show: true,
title: 'گردونه',
icon: '',
key: 'luckyWheel',
href: luckywheelImg,
childrenName:'',
hrefSelected: homeImg
},
/* {
show: true,
title: 'تراکنش ها',
icon: 'receipt-2',
key: 'transactions'
},*/
// {
// show: true,
// title: 'سفارشات',
// icon: 'bag-happy',
// key: 'orders'
// },
/*{
show: true,
title: 'برداشت',
icon: 'convert-card',
key: 'transfer'
},*/
// {
// show: userStore.user?.type_str === 'نماینده',
// title: 'مشتریان',
// icon: 'people',
// key: 'users',
// },
{
show: isMobile.value,
title: 'پروفایل',
icon: 'user',
key: 'mobileProfile',
href: userImg,
childrenName:'',
hrefSelected: userImg
}
])
const checkDevice = () => {
isMobile.value = window.innerWidth <= 768
}
const showProfile = () => {
open.value = true
}
const currentRoute = computed(() => {
return router.currentRoute.value.name
})
const setCurrentValue = () => {
const currentRoute = router.currentRoute.value.name
current.value = [currentRoute as string]
}
const changeHandler = (key: string) => {
console.log(key)
router.push({name: key})
}
const handleDrawerClose = () => {
const currentRouteName = router.currentRoute.value.name
current.value = [currentRouteName as string]
}
watch(router.currentRoute, () => {
setCurrentValue()
})
onMounted(() => {
setCurrentValue()
window.addEventListener('resize', checkDevice)
checkDevice()
})
onBeforeUnmount(() => {
window.removeEventListener('resize', checkDevice)
})
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-footer';
.@{prefix} {
.ant-menu-horizontal > .ant-menu-item-selected::after {
border: none !important;
}
.ant-menu-light.ant-menu-horizontal > .ant-menu-item::after {
border: none !important;
}
.ant-menu-horizontal {
border: none !important;
}
.ant-menu::before {
display: none !important;
}
.ant-menu-horizontal::after {
display: none !important;
}
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<div class="fixed top-0 w-inherit p-4 z-1000">
<div class="md:hidden p-2 border MobileNavbar rounded-md grid grid-cols-3">
<Icon
class="self-start icon_MobileNavbar"
icon="vuesax-linear:arrow-right-1"
@click="router.go(-1)"
/>
<div class="text-base font-semibold text-center whitespace-nowrap">{{title}}</div>
</div>
</div>
</template>
<script setup lang="ts">
import router from '@/router'
interface Prop {
title: string
}
const props = defineProps<Prop>()
</script>
<style scoped lang="less">
</style>

36
src/components/Navbar.vue Normal file
View File

@ -0,0 +1,36 @@
<template>
<div class="backHeader" :class="[prefixCls]">
<div class="p-4">
<div
class="flex justify-between items-center p-2 rounded border border-solid borderHeader"
>
<div v-if="mode === 'marandigold' " class="flex items-center ">
<img :src="logo" class="h-8" alt="logo">
<span class="text-sm font-bold mr-2">اپلیکیشن معاملاتی آبشده مرندی</span>
</div>
<span v-if="mode !== 'marandigold' " class="text-sm font-medium">{{ userStore.user?.name }}</span>
<Icon icon="vuesax-linear:profile-circle" size="30" @click="open = true" class="avatarIcon" />
</div>
</div>
<ProfileDrawer v-model:open="open" />
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@/composable/usePrefix'
import ProfileDrawer from '@/components/ProfileDrawer.vue'
import { ref } from 'vue'
import { useUserStore } from '@/store/user'
import logo from '@/assets/img/marandiLogo.svg'
import Icon from '@/components/Icon.vue'
const mode = import.meta.env.MODE
const { prefixCls } = usePrefix('header')
const open = ref<boolean>(false)
const userStore = useUserStore()
</script>
<style scoped lang="less">
@prefix: ~'@{prefixCls}-header';
.@{prefix} {
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<div class="" :class="[prefixCls]" v-if="item.show">
<ACard class="border-none shadow-md primaryColor" v-if="mode !== 'marandigold'">
<div class="flex flex-col">
<div class="flex gap-2 justify-between w-full items-center">
<div class="flex items-center gap-2 mb-2">
<!-- <img v-if="item?.icon" :src="item.icon" alt="" class="w-6 h-6">
<img v-else :src="defImg" alt="" class="w-6 h-6">-->
<h2 class="text-sm font-semibold text-center">{{ props.item?.title }}</h2>
</div>
<div class="flex items-center gap-1">
<span class="text-sm font-medium">{{ item.active ? 'باز' : 'بسته' }}</span>
<span
class="size-2 rounded-full mt-0.5"
:class="[item.active ? 'background_green' : 'background_red']"
></span>
</div>
</div>
<div class="flex gap-2 text-base font-bold">
<BasicButton
v-if="buyOpen"
class="w-full text-center px-4 py-2 border rounded-md cursor-pointer font-bold !text-green-600 BuySell_button_ box-shadow"
:class="{ 'glow-effect': buyOpen }"
@click="item.active && emit('buy')"
>
{{ usePrice(item.buy ?? 0, true, '') }}
</BasicButton>
<BasicButton
v-else
class="w-full text-center content-center px-4 py-2 rounded-md cursor-pointer font-bold BuySell_button_ !text-green-600"
>{{ 'غیرفعال' }}</BasicButton
>
<BasicButton
v-if="sellOpen"
class="w-full text-center px-4 py-2 border rounded-md cursor-pointer font-bold BuySell_button_ !text-red-600"
:class="{ 'glow-effect-red': sellOpen }"
@click="item.active && emit('sell')"
>
{{ usePrice(item.sell ?? 0, true, '') }}
</BasicButton>
<BasicButton
v-else
class="w-full text-center content-center px-4 py-2 BuySell_button_ rounded-md cursor-pointer font-bold !text-red-600"
>{{ 'غیرفعال' }}</BasicButton
>
</div>
<div class="flex justify-between items-center mt-3">
<div class="flex items-center gap-1 justify-center w-full">
<span class="text-xs font-semibold textColor2">آخرین بروز رسانی :</span>
<!-- <span class="text-sm font-bold text-gray-700">{{// toJalali(item?.updated_at)}}</span>-->
<span class="text-sm font-bold textColor1">{{ item?.updated_at }}</span>
</div>
</div>
</div>
</ACard>
<div v-else class="rounded-lg p2 bg-gradient-to-l from-[#62a1b1] to-[#b5f5df]">
<div class="flex flex-col">
<div class="flex gap-2 justify-between w-full items-center">
<div class="flex items-center gap-2 mb-2">
<div class="bg-gray-100 rounded-full p3 w-15 h-15">
<img v-if="item?.icon" :src="item.icon" alt="" class="w-full" />
<img v-else :src="logo" alt="" class="w-full" />
</div>
<div>
<h2 class="text-sm font-semibold text-black">{{ props.item?.title }}</h2>
<div class="flex items-center gap-1 justify-center w-full">
<span class="text-xs font-semibold text-gray-600">آخرین قیمت در </span>
<!-- <span class="text-sm font-bold text-gray-700">{{// toJalali(item?.updated_at)}}</span>-->
<span class="text-sm font-bold text-gray-600">{{ item?.updated_at.split(' ')[0] }}</span>
</div>
</div>
</div>
<div class="flex items-center gap-1">
<span class="text-sm font-medium text-gray-600">{{ item.active ? 'باز' : 'بسته' }}</span>
<span
class="size-2 rounded-full mt-0.5"
:class="[item.active ? 'background_green' : 'background_red']"
></span>
</div>
</div>
<div class="flex gap-2 text-base font-bold">
<BasicButton
v-if="buyOpen"
type="link"
class="w-full text-center px-4 py-2 border rounded-md cursor-pointer font-bold !text-green-600 BuySell_button_ box-shadow border-unset "
:class="{ 'glow-effect': buyOpen }"
@click="item.active && emit('buy')"
>
<div>
<span>{{ item.buy.toString().slice(0,3) }}</span>
<span>,</span>
<span class="font-bold text-2xl">{{ item.buy.toString().slice(3,6) }}</span>
<span>,</span>
<span class="text-xs">{{ item.buy.toString().slice(6, 9) }}</span>
</div>
</BasicButton>
<BasicButton
v-else
type="link"
class="w-full text-center content-center px-4 py-2 rounded-md cursor-pointer font-bold BuySell_button_ !text-green-600 border-unset !bg-transparent"
>{{ 'غیرفعال' }}</BasicButton
>
<BasicButton
v-if="sellOpen"
type="link"
class="w-full text-center px-4 py-2 border rounded-md cursor-pointer font-bold BuySell_button_ !text-red-600 border-unset "
:class="{ 'glow-effect-red': sellOpen }"
@click="item.active && emit('sell')"
>
<div>
<span>{{ item.sell.toString().slice(0,3) }}</span>
<span>,</span>
<span class="font-bold text-2xl">{{ item.sell.toString().slice(3,6) }}</span>
<span>,</span>
<span class="text-xs">{{ item.sell.toString().slice(6,9) }}</span>
</div> </BasicButton>
<BasicButton
v-else
type="link"
class="w-full text-center content-center px-4 py-2 BuySell_button_ rounded-md cursor-pointer font-bold !text-red-600 border-unset !bg-transparent"
>{{ 'غیرفعال' }}</BasicButton
>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@/composable/usePrefix'
import { usePrice } from '../composable/usePrice'
import type { Product, ProductShow } from '@/model/product'
import { toJalali } from '../utils/useDateTime'
import defImg from '@/assets/img/logo_desktop.svg'
import { computed } from 'vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
interface Props {
item: ProductShow
}
const props = withDefaults(defineProps<Props>(), {})
const emit = defineEmits<{
(e: 'sell'): void
(e: 'buy'): void
}>()
const mode = import.meta.env.MODE
const logo = import.meta.env.VITE_PROJECT_LOGO
const { prefixCls } = usePrefix('product-card')
const buyOpen = computed(() => {
return props.item.active && props.item.open_buy
})
const sellOpen = computed(() => {
return props.item.active && props.item.open_sell
})
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-product-card';
.@{prefix} {
.ant-card-body {
padding: 8px !important;
}
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<div :class="[prefixCls]">
<ADrawer
class="rounded-t-2xl ADrawer"
:open="props.open"
height="500"
placement="bottom"
:header-style="{ display: 'none' }"
:body-style="{ padding: '16px', height: 'fit-content' }"
@close="visible = false"
@click:outside="visible = false"
>
<template #closeIcon></template>
<UserSetting />
</ADrawer>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@/composable/usePrefix'
import UserSetting from '@/components/UserSetting.vue'
import { computed, watch } from 'vue'
import router from '@/router'
interface Props {
open: boolean
}
const props = withDefaults(defineProps<Props>(), {
open: false
})
watch(router.currentRoute,()=>{
visible.value = false
})
const emit = defineEmits<{
(e: 'update:open', value: boolean): void
}>()
const visible = computed({
get() {
return props.open
},
set(value) {
emit('update:open', value)
if (!value) emit('close')
}
})
const { prefixCls } = usePrefix('profile-drawer')
</script>
<style scoped lang="less">
@prefix: ~'@{prefixCls}-profile-drawer';
.@{prefix} {
&-drawer {
.ant-drawer .ant-drawer-content {
border-radius: 16px 16px 0 0 !important;
}
}
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<div v-if="store.user?.type === 1 ">
<BasicButton
class="mt-2 w-full border-0 px0 configButton configIcon"
:class="setActionSheetItemColor('home')"
icon="vuesax-linear:user"
icon-size="16"
@click="stopSheet('home')"
>
<BasicIcon
class="configIcon"
icon="vuesax-linear:arrow-left-1"
/>
<div class="flex w-full justify-right" >
مشتریان
</div>
</BasicButton>
<!-- <BasicButton
class="mt-2 w-full border-0 px0 configButton configIcon"
:class="setActionSheetItemColor('home')"
icon="vuesax-linear:trend-up"
icon-size="16"
@click="stopSheet('packages')"
>
<BasicIcon
class="configIcon"
icon="vuesax-linear:arrow-left-1"
/>
<div class="flex w-full justify-right" >
سودها
</div>
</BasicButton>-->
</div>
<!-- <BasicButton
class="mt-2 w-full border-0 px0 configButton configIcon"
:class="setActionSheetItemColor('home')"
icon="vuesax-linear:receipt-item"
icon-size="16"
@click="stopSheet('transaction')"
>
<BasicIcon
class="configIcon"
icon="vuesax-linear:arrow-left-1"
/>
<div class="flex w-full justify-right" >
گزارشات
</div>
</BasicButton>-->
<BasicButton
class="mt-2 w-full border-0 px0 configButton configIcon"
:class="setActionSheetItemColor('home')"
icon="vuesax-linear:convertshape"
icon-size="16"
@click="stopSheet('transfer')"
>
<BasicIcon
class="configIcon"
icon="vuesax-linear:arrow-left-1"
/>
<div class="flex w-full justify-right" >
برداشت
</div>
</BasicButton>
</template>
<script setup lang="ts">
import {computed, onMounted, ref, watch} from "vue";
import router from "@/router";
import { openResult, openConfirm, rejectOrder, confirmOrder, orderResult, stsOfferInfo } from '@/utils/order'
import BasicActionSheet from "@/components/BasicPacks/BasicActionSheet.vue";
import { useUserStore } from '@/store/user'
// const { prefixCls } = usePrefix('settin-users')
const store = useUserStore()
const emit = defineEmits(['close'])
const currentRoute = computed(() => router.currentRoute.value.name)
const stopSheet=(routeName:string)=>{
router.push({name: routeName})
emit('close')
}
function setActionSheetItemColor(val: string) {
if (currentRoute.value === val) {
return '!text-primary !bg-blue-50'
} else if (val === '') {
return '!text-red !bg-red-50'
} else {
return 'text-gray-400'
}
}
</script>
<style scoped lang="less">
@prefix: ~'@{prefixCls}-settin-users';
.@{prefix} {
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<div :class="[prefixCls]">
<div class="flex flex-col gap-4">
<div
class="flex justify-between items-center p-4 backUserSetting2 textColor1 rounded-md"
>
<div class="flex gap-1 items-center">
<Icon icon="vuesax-linear:user" class="iconUserSetting" />
<span class="text-base font-bold">{{ userStore.user?.name }}</span>
</div>
<ATag class="border-none px-6 py-[6px] font-semibold buttonLogin">{{
userStore.user?.type_str
}}</ATag>
</div>
<div>
<div v-if="userStore.user?.item && baseURL !== 'https://emexgold.com'" class="p-2 border-1px border-gray-100 rounded">
<div class="">لینک درگاه:</div>
<div class="mb-2 flex gap-2 w-full justify-between items-center mt-1" ref="url">
<a :href="`https://www.payping.ir/d/${userStore.user?.item}`"
target="_blank">{{ `https://www.payping.ir/d/${userStore.user?.item}` }}</a>
<BasicIcon v-if="userStore.user?.item" icon="vuesax-linear:copy" class="cursor-pointer"
@click="copyText(`https://www.payping.ir/d/${userStore.user?.item}`)" />
</div>
</div>
<div v-if="userStore.user?.deposit_id" class="p-2 border-1px border-gray-100 rounded mt-2">
<div class="">شناسه:</div>
<div class="w-full justify-between items-center flex mt-1">
<div class="mb-2">{{ userStore.user?.deposit_id ?? '_' }}</div>
<BasicIcon v-if="userStore.user.deposit_id" icon="vuesax-linear:copy" color="#2563EB"
class="cursor-pointer"
@click="copyText(userStore.user?.deposit_id)" />
</div>
</div>
</div>
<div
class="flex justify-between items-center p-4 rounded-md backUserSetting2 cursor-pointer hover:bg-gray-100"
@click="router.push({ name: 'profile' })"
>
<div class="flex gap-4 items-center">
<Icon icon="vuesax-linear:user-edit" class="iconUserSetting"/>
<span class="text-base font-medium textColor1">اطلاعات کاربری</span>
</div>
<icon icon="vuesax-linear:arrow-left-1" class="iconUserSetting" />
</div>
<div
class="flex justify-between items-center p-4 rounded-md backUserSetting2 cursor-pointer hover:bg-gray-100"
@click="router.push({ name: 'security' })"
>
<div class="flex gap-4 items-center">
<Icon icon="vuesax-linear:shield-tick" class="iconUserSetting" />
<span class="text-base font-medium textColor1">امنیت</span>
</div>
<icon icon="vuesax-linear:arrow-left-1" class="iconUserSetting" />
</div>
<div
v-if="userStore.user?.type_str === 'نماینده' "
class="flex justify-between items-center p-4 rounded-md backUserSetting2 cursor-pointer hover:bg-gray-100"
@click="router.push({ name: 'packages' })"
>
<div class="flex gap-4 items-center">
<Icon icon="vuesax-linear:setting-2" class="iconUserSetting" />
<span class="text-base font-medium textColor1">تنظیمات قیمت</span>
</div>
<icon icon="vuesax-linear:arrow-left-1" class="iconUserSetting" />
</div>
<div
class="flex justify-between items-center p-4 rounded-md backUserSetting2 cursor-pointer hover:bg-gray-100"
@click="exit"
>
<div class="flex gap-4 items-center">
<Icon icon="vuesax-linear:login" class="iconUserSetting" />
<span class="text-base font-medium textColor1">خروج</span>
</div>
<icon icon="vuesax-linear:arrow-left-1" class="iconUserSetting" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { usePrefix } from '@/composable/usePrefix'
import router from '@/router'
import Icon from '@/components/Icon.vue'
import { message, Modal } from 'ant-design-vue'
import { useUserStore } from '@/store/user'
import { onMounted, ref } from 'vue'
const { prefixCls } = usePrefix('user-setting')
const userStore = useUserStore()
const baseURL = ref(window.location.origin)
const exit = () => {
Modal.confirm({
title: 'آیا می‌خواهید از برنامه خارج شوید؟',
okText: 'بی خیال',
okType: 'default',
okButtonProps: {
class: 'exitButton'
},
cancelText: 'بله',
cancelButtonProps: { type: 'primary', danger: true },
onOk() {
console.log('logout canceled')
},
onCancel() {
userStore.logout()
}
})
}
function copyText(val: string) {
// nextTick(() => {
navigator.clipboard.writeText(val)
message.info('لینک رونوشت شد')
// })
}
</script>
<style lang="less">
@prefix: ~'@{prefixCls}-user-setting';
.@{prefix} {
}
</style>

164
src/composable/useAxios.ts Normal file
View File

@ -0,0 +1,164 @@
import { baseURL } from '@/themeConfig'
import axios from 'axios'
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import { getToken, removeToken } from '@/utils'
import router from '@/router'
import { get } from 'lodash'
import { message } from 'ant-design-vue'
interface Meta {
errorMessage?: 'message' | 'notification' | 'toast' | 'none'
}
interface MyAxiosRequestConfig<D = any> extends AxiosRequestConfig<D> {
meta?: Meta
}
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json'
}
const _axios = axios.create({
baseURL,
headers
})
_axios.interceptors.request.use(
(config) => {
//console.log('request config', config)
if (config.headers) {
const token = getToken()
if (token) {
//@ts-ignore
config.headers['Authorization'] = 'Bearer ' + token
}
}
return config
},
(error) => {
//console.log('this is error on interceptors.request', error)
Promise.reject(error)
}
)
_axios.interceptors.response.use(
(response) => {
//console.log('this is response')
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
// console.log('this is fulfilled on interceptors.response', response.data)
return response.data
},
function (error) {
console.log('this is axios error', error)
if (error.response.data && error.response.data.message) {
checkStatus(error.response.data.message, error.response.status, error.config)
}
//console.log('this is error on interceptors.response', error.config.meta)
if (error.response && (error.response.status == 401)) {
//debugger
removeToken()
router.push({ name: 'login' })
}
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
//const { response, code, message, config } = error || {}
//const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none'
//const msg: string = response?.data?.error?.message ?? ''
//checkStatus(error?.response?.status, msg)
return Promise.reject(error)
}
)
function checkStatus(msg: string, code: number, config: any) {
//const { code, data, message } = data
const errorMessage = get(config, 'meta.errorMessage', 'message')
/*if (config.meta) {
if (config.meta.message) {
errorMessage = config.meta.message
}
}*/
if (errorMessage == 'none') return
switch (errorMessage) {
case 'message':
message.error(msg)
break
default:
message.error(msg)
}
}
const myAxios = {
//AxiosResponse<T>
post<T = any, R = T, D = any>(
url: string,
data?: D,
config?: MyAxiosRequestConfig<D>
): Promise<R> {
return _axios.post(url, data, config)
},
request<T = any, R = AxiosResponse<T>, D = any>(config: MyAxiosRequestConfig<D>): Promise<R> {
return _axios.request(config)
},
//AxiosResponse<T>
get<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
return _axios.get(url, config)
},
//AxiosResponse<T>
delete<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
return _axios.delete(url, config)
},
//AxiosResponse<T>
head<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
return _axios.head(url, config)
},
//AxiosResponse<T>
options<T = any, R = T, D = any>(url: string, config?: MyAxiosRequestConfig<D>): Promise<R> {
return _axios.options(url, config)
},
//AxiosResponse<T>
put<T = any, R = T, D = any>(
url: string,
data?: D,
config?: MyAxiosRequestConfig<D>
): Promise<R> {
return _axios.put(url, data, config)
},
//AxiosResponse<T>
patch<T = any, R = T, D = any>(
url: string,
data?: D,
config?: MyAxiosRequestConfig<D>
): Promise<R> {
return _axios.patch(url, data, config)
},
//AxiosResponse<T>
postForm<T = any, R = T, D = any>(
url: string,
data?: D,
config?: MyAxiosRequestConfig<D>
): Promise<R> {
return _axios.postForm(url, data, config)
},
//AxiosResponse<T>
putForm<T = any, R = T, D = any>(
url: string,
data?: D,
config?: MyAxiosRequestConfig<D>
): Promise<R> {
return _axios.putForm(url, data, config)
},
//AxiosResponse<T>
patchForm<T = any, R = T, D = any>(
url: string,
data?: D,
config?: MyAxiosRequestConfig<D>
): Promise<R> {
return _axios.patchForm(url, data, config)
}
}
export default myAxios
export { _axios }

View File

@ -0,0 +1,131 @@
import {onMounted, ref} from 'vue'
import moment from 'moment'
//sample 00:30 - 1:50 - 00:59
export default function useCountdownTimer(_startTime = '00:00:30', format = 'HH:mm:ss', _endTime = '00:00:00', fromNow = false) {
let interval: number
let startTime = moment(_startTime, 'HH:mm:ss')
//const isActiveMenu=
const endTime = moment(_endTime, 'HH:mm:ss')
const time = ref('')
let tempHook: () => void
let tempHookStop: () => void
let tempHookStart: () => void
let tempHookRestart: () => void
function decrement() {
if (!fromNow) {
if (startTime > endTime) {
startTime.subtract('1', 'second')
time.value = startTime.format(format)
} else {
clearInterval(interval)
if (tempHook) {
tempHook()
}
}
} else {
const nowTime = moment().format(format)
time.value = moment(nowTime).fromNow(true)
}
}
function start() {
if (interval) {
stop()
}
interval = setInterval(decrement, 1000)
if (tempHookStart) {
tempHookStart()
}
}
function stop() {
clearInterval(interval)
if (tempHookStop) {
tempHookStop()
}
}
function onFinish(hook: () => any) {
tempHook = hook
}
function setTime(_time = '00:00:30') {
startTime = moment(_time, 'HH:mm:ss')
time.value = startTime.format(format)
stop()
start()
}
function reset() {
startTime = moment(_startTime, 'HH:mm:ss')
time.value = startTime.format(format)
stop()
start()
if (tempHookRestart) {
tempHookRestart()
}
}
function onStop(hook: () => any) {
tempHookStop = hook
}
function onStart(hook: () => any) {
tempHookStart = hook
}
function onRestart(hook: () => any) {
tempHookRestart = hook
}
onMounted(() => {
time.value = startTime.format(format)
})
return {
time,
stop,
start,
onFinish,
reset,
setTime,
onRestart,
onStart,
onStop,
}
}
export function diffdateBySeconds(date:string){
const fromTime= moment(date)
// console.log(fromTime)
const now=moment()
// console.log(now)
const seconds =fromTime.diff(now ,"seconds")
// console.log(seconds)
//
// console.log(moment.duration(seconds ,"seconds" ))
const time ={
years:moment.duration(seconds ,"seconds" ).years(),
months:moment.duration(seconds ,"seconds" ).months(),
days:moment.duration(seconds ,"seconds" ).days(),
hours:moment.duration(seconds ,"seconds" ).hours(),
minutes:moment.duration(seconds ,"seconds" ).minutes(),
seconds:moment.duration(seconds ,"seconds" ).seconds(),
}
return {
years:time.years>=10?time.years:`0${time.years}`,
months:time.months>=10?time.months:`0${time.months}`,
days:time.days>=10?time.days:`0${time.days}`,
hours:time.hours>=10?time.hours:`0${time.hours}`,
minutes:time.minutes>=10?time.minutes:`0${time.minutes}`,
seconds:time.seconds>=10?time.seconds:`0${time.seconds}`,
diffSeconds:seconds,
}
}

View File

@ -0,0 +1,8 @@
import { prefixCls } from '@/themeConfig'
export function usePrefix(name = '') {
return {
prefixVar: prefixCls,
prefixCls: `${prefixCls}-${name}`
}
}

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