feat: merge develop into main (#26)

* feat/login/uiedit (#21)

* feat: login ui edit

* feat: login page add dark theme

* feat: login page over

* feat: ui Update

* feat: update start script

Co-authored-by: pengyue970715@gmail.com

* docs: readme start script update

* fix/UI/dark (#22)

* style: ui edit。

* feat: ui edit complete

* style: ui edit

* chore: update style import path

* feat: update import path

* chore: lint fix

* feat: add package.lok

* chore: update node version

* chore: update actions

* chore: remove lock file

* fix: node version update

* fix: revert preview actiion

* fix: tdesign-wrapper classname fix

* chore: complete type pkg

* chore: action complete

* feat: update redirect url

* feat: rename layouts to layout

* chore: revert layout to layouts

Co-authored-by: Uyarn <uyarnchen@gmail.com>

* enhance/router (#23)

* style: ui edit。

* feat: ui edit complete

* style: ui edit

* chore: update style import path

* feat: update import path

* chore: lint fix

* feat: add package.lok

* chore: update node version

* chore: update actions

* chore: remove lock file

* fix: node version update

* fix: revert preview actiion

* fix: tdesign-wrapper classname fix

* feat: add router permission

* feat: enhance router

* feat: optimize header icon

Co-authored-by: pengYYY <pengyue970715@gmail.com>

* fix: layouts name fix (#24)

Co-authored-by: Uyarn <uyarnchen@gmail.com>
This commit is contained in:
PY 2021-12-13 10:38:26 +08:00 committed by GitHub
parent 63ab08dbe3
commit dcf3f1d73c
91 changed files with 3948 additions and 2537 deletions

View File

@ -46,6 +46,20 @@
"import/prefer-default-export": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off"
}
"@typescript-eslint/explicit-module-boundary-types": "off",
"vue/first-attribute-linebreak": 0
},
"overrides": [
{
"files": ["*.vue"],
"rules": {
"vue/component-name-in-template-casing": [2, "kebab-case"],
"vue/require-default-prop": 0,
"vue/multi-word-component-names": 0,
"vue/no-reserved-props": 0,
"vue/no-v-html": 0,
}
}
]
}

View File

@ -11,7 +11,18 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
node-version: 14
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: npm install
shell: bash
- run: npm run lint
- run: npm run lint

View File

@ -9,7 +9,18 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
node-version: 14
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: npm install
shell: bash
- run: echo '${{ github.ref }} ... ${{ github.sha }}'
@ -24,4 +35,4 @@ jobs:
npx surge --project ./dist --domain $DEPLOY_DOMAIN --token ${{ secrets.SURGE_TOKEN }}
echo the preview URL is $DEPLOY_DOMAIN
if: ${{ success() }}
- run: echo "🚀 This job's status is ${{ job.status }}."
- run: echo "🚀 This job's status is ${{ job.status }}."

View File

@ -45,7 +45,7 @@ TDesign Starter 基于 TDesign UI 组件,旨在提供项目开箱即用的、
npm install
// 启动项目
npm run start
npm run dev
// 项目构建 - 体验环境
npm run build:test

View File

@ -2,8 +2,8 @@
"name": "tdesign-vue-next-starter",
"version": "0.0.1",
"scripts": {
"start:mock": "vite --open --mode mock",
"start": "vite --open --mode development",
"dev:mock": "vite --open --mode mock",
"dev": "vite --open --mode development",
"build:test": "vite build --mode test",
"build": "vue-tsc --noEmit && vite build --mode release",
"preview": "vite preview",
@ -14,10 +14,10 @@
},
"dependencies": {
"dayjs": "^1.10.6",
"echarts": "^5.2.1",
"echarts": "~5.1.1",
"nprogress": "^0.2.0",
"qrcode.vue": "^3.2.2",
"tdesign-vue-next": "^0.4.1",
"tdesign-vue-next": "^0.5.0",
"vue": "^3.1.5",
"vue-router": "^4.0.11",
"vue3-clipboard": "^1.0.0",
@ -27,6 +27,7 @@
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^13.1.0",
"@types/echarts": "^4.9.10",
"@types/ws": "^8.2.2",
"@typescript-eslint/eslint-plugin": "^4.29.3",
"@typescript-eslint/parser": "^4.29.3",
"@vitejs/plugin-vue": "^1.3.0",
@ -52,7 +53,7 @@
"stylelint-order": "^4.1.0",
"stylelint-scss": "^4.0.0",
"typescript": "^4.4.3",
"vite": "^2.4.4",
"vite": "^2.7.1",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-svg-sprite-component": "^1.0.10",
"vite-svg-loader": "^3.1.0",

View File

@ -1,4 +1,28 @@
<template>
<router-view />
<router-view :class="[mode]" />
</template>
<script></script>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useStore } from 'vuex';
export default defineComponent({
setup() {
const store = useStore();
const mode = computed(() => {
return store.getters['setting/mode'];
});
return {
mode,
};
},
});
</script>
<style lang="less">
@import '@/style/variables.less';
#nprogress .bar {
background: @brand-color !important;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 KiB

View File

@ -0,0 +1,39 @@
<svg width="188" height="26" viewBox="0 0 188 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M77.7301 8.34426H80.6699L78.3234 21.7922H75.3799L77.7301 8.34426Z" fill="currentcolor"/>
<path d="M78.5992 3.09961H81.6221L81.0742 6.12246H78.0513L78.5992 3.09961Z" fill="currentcolor"/>
<path d="M32.5765 6.46921H36.937L37.4131 3.82422H25.4615L24.9854 6.46921H29.3723L26.6706 21.7913H29.8559L32.5765 6.46921Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.4051 11.4275C51.5998 10.4187 51.7035 9.39434 51.7149 8.3669C51.7149 4.97375 50.007 3.8364 45.4765 3.84774H39.2381L36.083 21.8035H43.3908C48.1329 21.8035 49.8181 20.5906 50.8836 14.4504L51.4051 11.4275ZM45.0004 6.47006C47.5623 6.47006 48.5372 6.80258 48.5372 8.83922C48.5177 9.70444 48.4254 10.5665 48.2613 11.4162L47.7399 14.4391C47.0522 18.4481 46.3191 19.1585 42.7597 19.1585H39.7369L41.9473 6.47006H45.0004Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M63.863 10.8329C63.8629 11.1826 63.8313 11.5317 63.7685 11.8758L63.436 13.765C63.0582 15.8735 62.276 16.6103 60.3074 16.6103H55.0816L54.9607 17.3471C54.8956 17.6913 54.8564 18.0399 54.8436 18.39C54.8436 19.2175 55.2705 19.4102 56.6459 19.4102H61.4145L60.4396 21.8058H56.0754C52.9958 21.8058 51.9189 21.0199 51.9189 19.1004C51.9308 18.5439 51.9864 17.9893 52.0852 17.4416L53.011 12.1289C53.5777 8.85291 54.9531 8.19166 58.4407 8.19166H60.0542C62.5292 8.17654 63.863 8.83779 63.863 10.8329ZM60.9006 11.2599C60.9006 10.6893 60.5454 10.5457 59.4534 10.5457H57.7682C56.6762 10.5457 56.1547 10.6893 55.8789 12.1138L55.501 14.294H59.5327C59.6593 14.3151 59.7889 14.3081 59.9124 14.2735C60.036 14.239 60.1504 14.1778 60.2477 14.0942C60.345 14.0106 60.4228 13.9066 60.4755 13.7897C60.5283 13.6728 60.5547 13.5457 60.553 13.4174L60.8137 11.8758C60.8561 11.6728 60.8826 11.4669 60.893 11.2599H60.9006Z" fill="currentcolor"/>
<path d="M69.644 19.3964H64.0215L64.4485 21.8185H69.644C72.4477 21.8185 73.1543 21.486 73.5813 19.0186L73.842 17.5072C73.9744 16.8585 74.0527 16.2001 74.0762 15.5385C74.0762 14.1896 73.3885 13.7853 71.3028 13.7853H68.4575C67.9096 13.7853 67.7471 13.7361 67.7471 13.4301C67.754 13.2471 67.7768 13.065 67.8152 12.886L68.0079 11.8166C68.1741 10.8909 68.3366 10.7511 69.2624 10.7511H74.269L75.2627 8.35547H69.2624C66.5834 8.35547 65.873 8.6502 65.3969 11.3783L65.208 12.4477C65.073 13.0947 64.9933 13.752 64.9699 14.4125C64.9699 15.7652 65.6576 16.1695 67.7471 16.1695H70.4715C71.0194 16.1695 71.1856 16.2187 71.1856 16.5247C71.1769 16.7078 71.1529 16.8898 71.1139 17.0688L70.8985 18.3271C70.7322 19.2529 70.566 19.3964 69.644 19.3964Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M93.2642 13.8225C93.4341 12.9856 93.5377 12.1366 93.5741 11.2833C93.5741 9.00486 92.3158 8.17735 89.3194 8.16602H87.6833C83.7196 8.16602 82.843 9.03509 82.1553 12.8137L81.8001 14.7823C81.6298 15.6192 81.5262 16.4682 81.4902 17.3215C81.4902 19.5962 82.7372 20.4275 85.76 20.4275H89.1607L88.9945 21.3532C88.6393 23.439 88.2841 23.5826 86.4326 23.5826H81.5469L81.9739 26.0008H87.0976C89.8258 26.0008 91.2238 25.3585 91.7944 22.1354L93.2642 13.8225ZM90.3132 13.7961L89.5877 18.0432L86.4931 18.0318C84.9287 18.0318 84.43 17.8429 84.43 16.8227C84.475 16.1366 84.5697 15.4547 84.7133 14.7823L85.0723 12.8137C85.3784 11.0944 85.6618 10.5465 87.4641 10.5465H88.5335C90.0751 10.5465 90.5966 10.7392 90.5966 11.7557C90.5573 12.4423 90.4625 13.1247 90.3132 13.7961Z" fill="currentcolor"/>
<path d="M107.112 10.7615C107.059 11.7344 106.933 12.7019 106.735 13.6559L105.31 21.7911H102.37L103.818 13.6559C103.946 13.0334 104.026 12.4016 104.056 11.7666C104.056 10.9353 103.7 10.7691 102.586 10.7691H99.1208L97.2013 21.776H94.2578L96.6081 8.32812H103.13C106.183 8.34324 107.112 9.06872 107.112 10.7615Z" fill="currentcolor"/>
<path d="M112.333 21.9715H117.879C121.093 21.9715 122.473 21.5668 123.045 18.3532L123.473 15.9727C123.568 15.4728 123.783 14.0683 123.783 13.4017C123.783 11.6164 122.497 11.3307 120.474 11.3307H117.427C116.76 11.3307 116.427 11.2117 116.427 10.807C116.427 10.5928 116.498 10.1167 116.617 9.49773L116.903 8.02182C117.141 6.80777 117.332 6.56972 118.427 6.56972H124.211L125.306 3.95117H118.26C115.284 3.95117 114.356 4.61771 113.856 7.47431L113.404 9.99763C113.285 10.688 113.19 11.3069 113.19 11.8306C113.19 13.3303 113.832 14.0683 115.76 14.0683H119.307C120.164 14.0683 120.498 14.1159 120.498 14.6396C120.498 14.9015 120.45 15.4252 120.283 16.2583L120.045 17.4962C119.736 19.1625 119.498 19.3292 117.927 19.3292H111.856L112.333 21.9715Z" fill="currentcolor"/>
<path d="M130.124 10.8784H133.909L134.337 8.47411H130.552L131.266 4.47488H128.314L127.6 8.47411H125.815L125.41 10.8784H127.172L126.101 16.9487C125.958 17.7104 125.839 18.5436 125.839 19.1625C125.839 21.2574 127.172 21.9715 129.838 21.9715H131.576L132.599 19.5672H130.981C129.433 19.5672 128.838 19.5434 128.838 18.5912C128.838 18.2103 128.933 17.5914 129.052 16.9487L130.124 10.8784Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M136.155 8.47411H142.344C145.034 8.47411 146.081 9.21207 146.081 10.926C146.081 11.4973 145.915 12.4495 145.772 13.2589L144.248 21.9715H137.107C134.798 21.9715 134.084 21.1384 134.084 19.5672C134.084 18.9959 134.179 18.377 134.298 17.6628L134.441 16.8535C135.012 13.6636 136.059 13.4732 139.297 13.4732H142.868L142.939 13.0447C143.034 12.5448 143.106 12.1639 143.106 11.8306C143.106 11.1165 142.701 10.8784 141.344 10.8784H136.559L136.155 8.47411ZM137.059 18.8531C137.059 19.4244 137.297 19.5672 137.94 19.5672H141.796L142.463 15.7584H138.749C137.75 15.7584 137.512 15.9727 137.345 16.9487L137.154 18.0199C137.083 18.4008 137.059 18.6626 137.059 18.8531Z" fill="currentcolor"/>
<path d="M146.66 21.9715H149.636L151.231 12.9971C151.54 11.2117 151.897 10.926 153.611 10.926H155.778L156.801 8.47411H153.516C149.779 8.47411 148.922 9.14065 148.279 12.8304L146.66 21.9715Z" fill="currentcolor"/>
<path d="M165.098 10.8784H161.313L160.242 16.9487C160.123 17.5914 160.028 18.2103 160.028 18.5912C160.028 19.5434 160.623 19.5672 162.17 19.5672H163.789L162.765 21.9715H161.027C158.361 21.9715 157.028 21.2574 157.028 19.1625C157.028 18.5436 157.147 17.7104 157.29 16.9487L158.361 10.8784H156.6L157.004 8.47411H158.79L159.504 4.47488H162.456L161.742 8.47411H165.527L165.098 10.8784Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M173.343 8.30748H171.724C168.225 8.30748 166.844 8.97402 166.273 12.2591L165.345 17.5914C165.249 18.1627 165.178 18.7579 165.178 19.2578C165.178 21.186 166.273 21.9715 169.368 21.9715H173.748L174.724 19.5672H169.939C168.558 19.5672 168.13 19.3768 168.13 18.5436C168.13 18.2818 168.177 17.9009 168.249 17.4962L168.368 16.7582H173.605C175.581 16.7582 176.366 16.0203 176.747 13.9016L177.08 12.0211C177.152 11.6402 177.176 11.2593 177.176 10.9736C177.176 8.97402 175.843 8.30748 173.343 8.30748ZM174.129 12.0211L173.867 13.5684C173.772 14.1873 173.51 14.4492 172.843 14.4492H168.796L169.177 12.2591C169.439 10.8308 169.963 10.688 171.058 10.688H172.748C173.843 10.688 174.2 10.8308 174.2 11.4021C174.2 11.5688 174.176 11.7354 174.129 12.0211Z" fill="currentcolor"/>
<path d="M179.919 21.9715H176.943L178.562 12.8304C179.205 9.14065 180.062 8.47411 183.799 8.47411H187.084L186.06 10.926H183.894C182.18 10.926 181.823 11.2117 181.514 12.9971L179.919 21.9715Z" fill="currentcolor"/>
<path d="M5.21231 5.28999H0.481548C0.412498 5.29078 0.344103 5.27654 0.281107 5.24826C0.21811 5.21998 0.162019 5.17833 0.116725 5.12621C0.0714314 5.07408 0.0380182 5.01273 0.0188032 4.9464C-0.000411771 4.88008 -0.00496858 4.81036 0.00544886 4.7421L0.783833 0.377856C0.80328 0.271783 0.8593 0.175885 0.942146 0.106846C1.02499 0.0378082 1.12942 0 1.23726 0L6.14939 0L5.21231 5.28999Z" fill="#0064FF"/>
<path d="M8.52984 16.5117H3.22852L5.21226 5.29688H10.5136L8.52984 16.5117Z" fill="url(#paint0_linear_18110_34031)"/>
<path d="M7.20647 21.8093H2.85735C2.78839 21.8085 2.72041 21.7929 2.65803 21.7635C2.59565 21.7341 2.54035 21.6915 2.49589 21.6388C2.45142 21.5861 2.41884 21.5244 2.40036 21.458C2.38188 21.3916 2.37794 21.3219 2.38881 21.2538L3.23142 16.5117H8.52142L7.65235 21.4239C7.63472 21.53 7.58048 21.6267 7.49905 21.6971C7.41761 21.7675 7.3141 21.8072 7.20647 21.8093V21.8093Z" fill="#009BFF"/>
<path d="M20.0919 5.28999H5.21191L6.149 0H20.8476C20.9168 0.00013044 20.9851 0.0153276 21.0478 0.0445343C21.1105 0.0737409 21.166 0.116256 21.2106 0.169121C21.2552 0.221986 21.2878 0.283932 21.306 0.35065C21.3242 0.417368 21.3277 0.487255 21.3162 0.555449L20.5416 4.91213C20.5236 5.01823 20.4685 5.11448 20.3862 5.1837C20.3038 5.25293 20.1995 5.2906 20.0919 5.28999V5.28999Z" fill="url(#paint1_linear_18110_34031)"/>
<defs>
<linearGradient id="paint0_linear_18110_34031" x1="7.81543" y1="4.88815" x2="7.92589" y2="15.4398" gradientUnits="userSpaceOnUse">
<stop stop-color="#009BFF"/>
<stop offset="0.35" stop-color="#0081FE"/>
<stop offset="0.75" stop-color="#006AFD"/>
<stop offset="1" stop-color="#0062FD"/>
</linearGradient>
<linearGradient id="paint1_linear_18110_34031" x1="5.68577" y1="2.65136" x2="20.3785" y2="5.41299" gradientUnits="userSpaceOnUse">
<stop offset="0.03" stop-color="#E9FFFF"/>
<stop offset="0.17" stop-color="#C4FAC9"/>
<stop offset="0.33" stop-color="#A0F694"/>
<stop offset="0.48" stop-color="#82F269"/>
<stop offset="0.63" stop-color="#6AEF47"/>
<stop offset="0.76" stop-color="#5AED2F"/>
<stop offset="0.89" stop-color="#4FEB20"/>
<stop offset="1" stop-color="#4CEB1B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,10 +1,11 @@
<template>
<div :class="containerCls">
<div :class="titleCls">
<span :class="titleTextCls">
<div v-if="title || $slots.title || $slots.option || subtitle || describe" :class="titleCls">
<div :class="titleTextCls">
{{ title }}
<span v-if="describe" class="card-describe">{{ describe }}</span>
</span>
<span v-if="subtitle" class="card-subtitle">{{ subtitle }}</span>
</div>
<span class="card-option">
<slot name="option" />
</span>
@ -25,6 +26,10 @@ export default defineComponent({
type: String as PropType<string>,
default: '',
},
subtitle: {
type: String as PropType<string>,
default: '',
},
compact: {
type: Boolean as PropType<boolean>,
default: false,
@ -37,11 +42,15 @@ export default defineComponent({
type: String as PropType<string>,
default: 'default',
},
border: {
type: Boolean,
default: false,
},
},
setup(props) {
const containerCls = computed(() => {
const { compact } = props;
return ['card-container', { 'card-container-compact': compact }];
const { compact, border } = props;
return ['card-container', { 'card-container-compact': compact, 'card-container--border': border }];
});
const titleCls = computed(() => {
@ -49,8 +58,8 @@ export default defineComponent({
return [
'card-title',
{
'card-title-small': size === 'small',
'card-title-default': size !== 'small',
'card-title--small': size === 'small',
'card-title--default': size !== 'small',
},
];
});
@ -59,8 +68,8 @@ export default defineComponent({
const { size } = props;
return [
{
'card-title-text-small': size === 'small',
'card-title-text-default': size !== 'small',
'card-title__text--small': size === 'small',
'card-title__text--default': size !== 'small',
},
];
});
@ -74,12 +83,29 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
@import url('@/style/index.less');
@import '@/style/variables';
.t-col > .card-container {
margin: 0;
.main-color {
background: @brand-color;
color: @text-color-primary;
.card-subtitle {
color: @text-color-anti;
}
.dashboard-item-top span {
color: @text-color-anti;
}
.dashboard-item-block {
color: @text-color-anti;
opacity: 0.6;
}
.dashboard-item-bottom {
color: @text-color-anti;
}
}
.card {
&-option {
display: flex;
@ -89,7 +115,6 @@ export default defineComponent({
&-container {
padding: 24px 32px;
margin: 16px 0;
background: @bg-color-container;
border-radius: @border-radius;
width: 100%;
@ -101,44 +126,56 @@ export default defineComponent({
margin-top: 24px;
margin-bottom: 16px;
}
&--border {
border: solid 1px @component-border;
}
}
&-title {
display: flex;
justify-content: space-between;
font-size: 20px;
line-height: 22px;
line-height: 24px;
font-family: PingFangSC-Regular;
font-weight: 500;
color: @text-color-primary;
&-small {
&--small {
margin-bottom: 8px;
}
&-default {
margin-bottom: 16px;
&--default {
margin-bottom: 24px;
}
&-text {
&__text {
display: inline-flex;
&-default {
&--default {
margin: @spacer 0;
}
}
&-text-small {
display: inline-block;
width: 100%;
&--small {
display: inline-block;
width: 100%;
}
}
}
&-describe {
font-size: 14px;
color: @brand-color;
color: @text-color-primary;
color: @text-color-placeholder;
line-height: 22px;
margin-left: 4px;
}
&-subtitle {
font-size: 14px;
color: @brand-color;
color: @text-color-secondary;
line-height: 22px;
margin-left: 4px;
}
&-content {

View File

@ -1,12 +1,8 @@
<template>
<div class="result-container">
<img class="result-bg-img" :src="bgUrl" />
<div class="result-title">
{{ title }}
</div>
<div class="result-tip">
{{ tip }}
</div>
<div class="result-title">{{ title }}</div>
<div class="result-tip">{{ tip }}</div>
<slot />
</div>
</template>
@ -32,7 +28,7 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
@import url('@/style/index.less');
@import '@/style/variables';
.result {
&-link {
@ -68,15 +64,23 @@ export default defineComponent({
}
&-bg-img {
width: 256px;
height: 256px;
width: 200px;
}
&-title {
font-style: normal;
font-weight: 500;
margin-top: 8px;
color: @text-color-primary;
font-size: @font-size-xl;
line-height: @text-line-height-xl;
}
&-tip {
margin: 8px 0 32px;
font-size: @font-size-base;
color: @text-color-secondary;
line-height: 22px;
line-height: @text-line-height-base;
}
}
</style>

View File

@ -67,38 +67,38 @@ export default defineComponent({
.trend {
&-container {
&__up {
color: #e34d59 !important;
color: @error-color;
display: inline-flex;
align-items: center;
justify-content: center;
.trend-icon-container {
background: #f9d7d9;
margin-right: 4px;
background: @error-color-2;
margin-right: 8px;
}
}
&__down {
color: #00a870 !important;
color: @success-color;
display: inline-flex;
align-items: center;
justify-content: center;
.trend-icon-container {
background: #bcebdc;
margin-right: 4px;
background: @success-color-2;
margin-right: 8px;
}
}
&__reverse {
color: #ffffff !important;
color: #ffffff;
display: inline-flex;
align-items: center;
justify-content: center;
.trend-icon-container {
background: @brand-color-5;
margin-right: 4px;
margin-right: 8px;
}
}

View File

@ -1,10 +1,4 @@
export const PREFIX = 'tdesign-starter';
export const THEME = 'light';
// customize(自定义登录,外网域名,统一重定向到登录页面)
// export const authenticationMethod = 'smartProxy';
export const AUTHENTICATION_METHOD = 'customize';
export default {
PREFIX,
THEME,
AUTHENTICATION_METHOD,
};
export const TOKEN_NAME = 'tdesign-starter';

View File

@ -9,7 +9,9 @@ export interface MenuRoute {
path: string;
title?: string;
icon?: string;
redirect?: string;
children: MenuRoute[];
meta: any;
}
export type ModeType = 'dark' | 'light';
@ -21,3 +23,13 @@ export type ClassName = { [className: string]: any } | ClassName[] | string;
export type CommonObjType = {
[key: string]: string | number;
};
export interface NotificationItem {
id: string;
content: string;
type: string;
status: boolean;
collected: boolean;
date: string;
quality: 'high' | 'low' | 'middle';
}

19
src/layouts/blank.vue Normal file
View File

@ -0,0 +1,19 @@
<template>
<div class="tdesign-wrapper">
<router-view />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
components: {},
});
</script>
<style lang="less">
.tdesign-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<t-breadcrumb :max-item-width="'150'" class="tdesign-breadcrumb">
<t-breadcrumbItem v-for="item in crumbs" :key="item.to" :to="item.to">
{{ item.title }}
</t-breadcrumbItem>
</t-breadcrumb>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useRoute } from 'vue-router';
export default defineComponent({
name: 'TdesignStarterBreadcrumb',
props: {
isVisible: Boolean,
},
setup() {
const crumbs = computed(() => {
const route = useRoute();
const pathArray = route.path.split('/');
pathArray.shift();
const breadcrumbs = pathArray.reduce((breadcrumbArray, path, idx) => {
breadcrumbArray.push({
path,
to: breadcrumbArray[idx - 1] ? `/${breadcrumbArray[idx - 1].path}/${path}` : `/${path}`,
title: route.matched[idx].meta.title || path,
});
return breadcrumbArray;
}, []);
return breadcrumbs;
});
return {
crumbs,
};
},
});
</script>
<style scoped>
.tdesign-breadcrumb {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({});
</script>
<style lang="less" scoped>
@import '@/style/variables';
.fade-leave-active,
.fade-enter-active {
transition: opacity @anim-duration-slow @anim-time-fn-easing;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<div :class="PREFIX + '-footer'">Copyright @ 2021-{{ new Date().getFullYear() }} Tencent. All Rights Reserved</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { PREFIX } from '@/config/global';
export default defineComponent({
name: `${PREFIX}-footer`,
setup() {
return {
PREFIX,
};
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables';
.@{prefix}-footer {
color: @text-color-placeholder;
line-height: 20px;
text-align: center;
}
</style>

View File

@ -0,0 +1,351 @@
<template>
<div :class="layoutCls">
<t-head-menu :class="menuCls" :theme="theme" expand-type="popup" :value="active">
<template #logo>
<span v-if="showLogo" class="header-logo-container" @click="goHome">
<tLogoFull class="t-logo" />
</span>
<div v-else class="header-operate-left">
<t-button theme="default" shape="square" variant="text" @click="changeCollapsed">
<t-icon v-show="isSidebarCompact" class="collapsed-icon" name="menu-fold" />
<t-icon v-show="!isSidebarCompact" class="collapsed-icon" name="menu-unfold" />
</t-button>
<search :layout="layout" />
</div>
</template>
<sub-menu v-show="layout !== 'side'" class="header-menu" :nav-data="menu" />
<template #operations>
<div class="operations-container">
<!-- 搜索框 -->
<search v-if="layout !== 'side'" :layout="layout" />
<t-dropdown :min-column-width="135" trigger="click">
<template #dropdown>
<t-dropdown-menu>
<t-dropdown-item class="operations-dropdown-container-item" @click="handleNav('/user/index')">
<t-icon name="user-circle"></t-icon>
</t-dropdown-item>
<t-dropdown-item class="operations-dropdown-container-item" @click="handleLogout">
<t-icon name="poweroff"></t-icon>退
</t-dropdown-item>
</t-dropdown-menu>
</template>
<t-button class="header-user-btn" theme="default" variant="text">
<template #icon>
<t-icon class="header-user-avatar" name="user-circle" />
</template>
<div class="header-user-account">
Tencent
<t-icon name="chevron-down" />
</div>
</t-button>
</t-dropdown>
<!-- 全局通知 -->
<notice />
<t-tooltip placement="bottom" content="代码仓库">
<t-button theme="default" shape="square" variant="text" @click="navToGitHub">
<t-icon name="logo-github" />
</t-button>
</t-tooltip>
<t-tooltip placement="bottom" content="帮助文档">
<t-button theme="default" shape="square" variant="text" @click="navToHelper">
<t-icon name="help-circle" />
</t-button>
</t-tooltip>
<t-tooltip placement="bottom" content="系统设置">
<t-button theme="default" shape="square" variant="text">
<t-icon name="setting" @click="toggleSettingPanel" />
</t-button>
</t-tooltip>
</div>
</template>
</t-head-menu>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, ref } from 'vue';
import { useStore } from 'vuex';
import { useRouter, useRoute } from 'vue-router';
import { PREFIX } from '@/config/global';
import tLogoFull from '@/assets/assets-logo-full.svg?component';
import { MenuRoute } from '@/interface';
import Notice from './Notice.vue';
import Search from './Search.vue';
import SubMenu from './SubMenu';
export default defineComponent({
components: {
tLogoFull,
Notice,
Search,
SubMenu,
},
props: {
theme: {
type: String as PropType<string>,
default: '',
},
layout: {
type: String as PropType<string>,
default: 'top',
},
showLogo: {
type: Boolean as PropType<boolean>,
default: true,
},
menu: {
type: Array as PropType<MenuRoute[]>,
default: () => [],
},
isFixed: {
type: Boolean as PropType<boolean>,
default: false,
},
isCompact: {
type: Boolean as PropType<boolean>,
default: false,
},
maxLevel: {
type: Number as PropType<number>,
default: 3,
},
},
setup(props) {
const store = useStore();
const router = useRouter();
const toggleSettingPanel = () => {
store.commit('setting/toggleSettingPanel', true);
};
const goHome = () => {
router.push('/dashboard/base');
};
const active = computed(() => {
const route = useRoute();
if (!route.path) {
return '';
}
return route.path
.split('/')
.filter((item, index) => index <= props.maxLevel && index > 0)
.map((item) => `/${item}`)
.join('');
});
const showMenu = computed(() => !(props.layout === 'mix' && props.showLogo));
const layoutCls = computed(() => [`${PREFIX}-header-layout`]);
const menuCls = computed(() => {
const { isFixed, layout, isCompact } = props;
return [
{
[`${PREFIX}-header-menu`]: !isFixed,
[`${PREFIX}-header-menu-fixed`]: isFixed,
[`${PREFIX}-header-menu-fixed-side`]: layout === 'side' && isFixed,
[`${PREFIX}-header-menu-fixed-side-compact`]: layout === 'side' && isFixed && isCompact,
},
];
});
const userVisible = ref(false);
const userVisibleChange = (value: boolean) => {
userVisible.value = value;
};
const changeCollapsed = () => {
store.commit('setting/toggleSidebarCompact');
};
const isSidebarCompact = computed(() => store.state.setting.isSidebarCompact);
const handleNav = (url) => {
router.push(url);
};
const handleLogout = () => {
router.push(`/login?redirect=${router.currentRoute.value.fullPath}`);
};
const navToGitHub = () => {
window.open('https://github.com/TDesignOteam/tdesign-vue-next-starter');
};
const navToHelper = () => {
window.open('http://tdesign.tencent.com/starter/get-started.html');
};
return {
isSidebarCompact,
goHome,
toggleSettingPanel,
active,
showMenu,
layoutCls,
userVisible,
userVisibleChange,
menuCls,
changeCollapsed,
handleNav,
handleLogout,
navToGitHub,
navToHelper,
};
},
});
</script>
<style lang="less">
@import '@/style/variables.less';
.@{prefix}-header {
&-layout {
height: 64px;
}
&-menu-fixed {
position: fixed;
top: 0;
z-index: 10;
&-side {
left: 232px;
right: 0;
z-index: 10;
width: auto;
transition: all 0.3s;
&-compact {
left: 64px;
}
}
}
&-logo-container {
cursor: pointer;
display: inline-flex;
height: 64px;
}
.t-logo {
width: 32px;
&:hover {
cursor: pointer;
}
}
}
.header-menu {
flex: 1 1 1;
display: inline-flex;
}
.operations-container {
display: flex;
align-items: center;
margin-right: 12px;
.t-popup-reference {
display: flex;
align-items: center;
justify-content: center;
}
.t-button {
margin: 0 8px;
&.header-user-btn {
margin: 0;
}
}
.t-icon {
font-size: 20px;
&.general {
margin-right: 16px;
}
}
}
.header-operate-left {
display: flex;
margin-left: 20px;
align-items: center;
.collapsed-icon {
font-size: 20px;
}
}
.header-logo-container {
display: flex;
margin-left: 16px;
&:hover {
cursor: pointer;
}
}
.header-user-account {
display: inline-flex;
align-items: center;
color: @text-color-primary;
.t-icon {
margin-left: 4px;
font-size: 16px;
}
}
.t-menu--light {
.header-user-account {
color: @text-color-primary;
}
}
.t-menu--dark {
.header-user-account {
color: rgba(255, 255, 255, 0.55);
}
.t-button {
--ripple-color: var(--td-gray-color-10) !important;
&:hover {
background: var(--td-gray-color-12) !important;
}
}
}
.operations-dropdown-container-item {
width: 100%;
display: flex;
align-items: center;
.t-icon {
margin-right: 8px;
}
.t-dropdown__item {
.t-dropdown__item__content {
display: flex;
justify-content: center;
}
.t-dropdown__item__content__text {
display: flex;
align-items: center;
font-size: 14px;
}
}
.t-dropdown__item {
width: 100%;
margin-bottom: 0px;
}
&:last-child {
.t-dropdown__item {
margin-bottom: 8px;
}
}
}
</style>

View File

@ -0,0 +1,207 @@
<template>
<t-popup expand-animation placement="bottom-right" trigger="click">
<template #content>
<div class="header-msg">
<div class="header-msg-top">
<p>通知</p>
<t-button v-if="unreadMsg.length > 0" class="clear-btn" variant="text" theme="primary" @click="setRead('all')"
>清空</t-button
>
</div>
<t-list v-if="unreadMsg.length > 0" class="narrow-scrollbar" :split="true">
<t-list-item v-for="(item, index) in unreadMsg" :key="index">
<div>
<p class="msg-content">{{ item.content }}</p>
<p class="msg-type">{{ item.type }}</p>
</div>
<p class="msg-time">{{ item.date }}</p>
<template #action>
<t-button size="small" variant="outline" @click="setRead('radio', item)"> 设为已读 </t-button>
</template>
</t-list-item>
</t-list>
<div v-else class="empty-list">
<img src="https://tdesign.gtimg.com/pro-template/personal/nothing.png" alt="空" />
<p>暂无通知</p>
</div>
<div class="header-msg-bottom">
<t-button
v-if="unreadMsg.length > 0"
class="header-msg-bottom-link"
variant="text"
theme="primary"
@click="goDetail"
>查看全部</t-button
>
</div>
</div>
</template>
<t-badge :count="unreadMsg.length" :offset="[15, 21]">
<t-button theme="default" shape="square" variant="text">
<t-icon name="mail" />
</t-button>
</t-badge>
</t-popup>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { NotificationItem } from '@/interface';
export default defineComponent({
setup() {
const store = useStore();
const { msgData } = store.state.notification;
const unreadMsg = computed(() => store.getters['notification/unreadMsg']);
const setRead = (type: string, item?: NotificationItem) => {
const changeMsg = msgData;
if (type === 'all') {
changeMsg.forEach((e: NotificationItem) => {
e.status = false;
});
} else {
changeMsg.forEach((e: NotificationItem) => {
if (e.id === item?.id) {
e.status = false;
}
});
}
store.commit('notification/setMsgData', changeMsg);
};
const goDetail = () => {
const router = useRouter();
router.push('/detail/secondary');
};
return {
goDetail,
unreadMsg,
setRead,
};
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.header-msg {
width: 400px;
height: 500px;
.empty-list {
height: calc(100% - 104px);
text-align: center;
padding-top: 135px;
font-size: 14px;
color: @text-color-secondary;
img {
width: 63px;
}
p {
margin-top: 30px;
}
}
&-top {
position: relative;
height: 56px;
font-size: 16px;
color: @text-color-primary;
text-align: center;
line-height: 56px;
border-bottom: 1px solid @component-border;
.clear-btn {
position: absolute;
top: 12px;
right: 24px;
}
}
&-bottom {
height: 48px;
align-items: center;
display: flex;
justify-content: center;
&-link {
text-decoration: none;
font-size: 14px;
color: @brand-color;
line-height: 48px;
cursor: pointer;
}
}
.t-list {
height: calc(100% - 104px);
}
.t-list-item {
overflow: hidden;
width: 100%;
padding: 16px 24px;
border-radius: @border-radius;
font-size: 14px;
color: @text-color-primary;
line-height: 22px;
cursor: pointer;
&:hover {
transition: background 0.2s ease;
background: @bg-color-container-hover;
.msg-content {
color: @brand-color-8;
}
.t-list-item__action {
button {
bottom: 16px;
opacity: 1;
}
}
.msg-time {
bottom: -6px;
opacity: 0;
}
}
.msg-content {
margin-bottom: 16px;
}
.msg-type {
color: @text-color-secondary;
}
.t-list-item__action {
button {
opacity: 0;
position: absolute;
right: 24px;
bottom: -6px;
}
}
.msg-time {
transition: all 0.2s ease;
opacity: 1;
position: absolute;
right: 24px;
bottom: 16px;
color: @text-color-secondary;
}
}
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<div v-if="layout === 'side'" class="header-menu-search">
<t-input
:class="{ 'hover-active': isSearchFocus }"
placeholder="请输入搜索内容"
@blur="changeSearchFocus(false)"
@focus="changeSearchFocus(true)"
>
<template #prefix-icon>
<t-icon class="icon" name="search" size="16" />
</template>
</t-input>
</div>
<div v-else>
<t-button
:class="{ 'search-icon-hide': isSearchFocus }"
theme="default"
shape="square"
variant="text"
@click="changeSearchFocus(true)"
>
<t-icon name="search" />
</t-button>
<t-input
v-model="searchData"
:class="['header-search', { 'width-zero': !isSearchFocus }]"
placeholder="输入要搜索内容"
:autofocus="isSearchFocus"
@blur="changeSearchFocus(false)"
>
<template #prefix-icon>
<t-icon name="search" size="16" />
</template>
</t-input>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, PropType } from 'vue';
export default defineComponent({
props: {
layout: {
type: String as PropType<string>,
},
},
setup() {
const isSearchFocus = ref(false);
const searchData = ref('');
const changeSearchFocus = (value: boolean) => {
if (!value) {
searchData.value = '';
}
isSearchFocus.value = value;
};
return {
isSearchFocus,
searchData,
changeSearchFocus,
};
},
});
</script>
<style lang="less">
@import '@/style/variables.less';
.header-menu-search {
display: flex;
margin-left: 16px;
.hover-active {
.t-input__inner {
background: @bg-color-secondarycontainer;
}
}
.t-icon {
font-size: 20px !important;
color: @text-color-primary !important;
}
.t-input__inner {
border: none;
outline: none;
box-shadow: none;
transform: background @anim-duration-base linear;
&:hover {
background: @bg-color-secondarycontainer;
}
}
}
.header-search {
width: 200px;
transition: width @anim-duration-base @anim-time-fn-easing;
.t-input__inner {
border: 0;
padding-left: 40px;
&:focus {
box-shadow: none;
}
}
&.width-zero {
width: 0;
opacity: 0;
}
}
.t-button {
transition: opacity @anim-duration-base @anim-time-fn-easing;
}
.search-icon-hide {
opacity: 0;
}
</style>

View File

@ -0,0 +1,173 @@
import { defineComponent, PropType, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { PREFIX } from '@/config/global';
import pgk from '../../../package.json';
import SubMenu from './SubMenu';
import tLogoUrl from '@/assets/assets-t-logo.svg?url';
import tLogoFull from '@/assets/assets-logo-full.svg?component';
const MIN_POINT = 992 - 1;
const useComputed = (props) => {
const store = useStore();
const collapsed = computed(() => store.state.setting.isSidebarCompact);
const sideNavCls = computed(() => {
const { isCompact } = props;
return [
`${PREFIX}-sidebar-layout`,
{
[`${PREFIX}-sidebar-compact`]: isCompact,
},
];
});
const menuCls = computed(() => {
const { showLogo, isFixed, layout } = props;
return [
`${PREFIX}-side-nav`,
{
[`${PREFIX}-side-nav-no-logo`]: !showLogo,
[`${PREFIX}-side-nav-no-fixed`]: !isFixed,
[`${PREFIX}-side-nav-mix-fixed`]: layout === 'mix' && isFixed,
},
];
});
const layoutCls = computed(() => {
const { layout } = props;
return [`${PREFIX}-side-nav-${layout}`, `${PREFIX}-sidebar-layout`];
});
return {
collapsed,
sideNavCls,
menuCls,
layoutCls,
};
};
export default defineComponent({
name: 'SideNav',
components: {
SubMenu,
tLogoFull,
},
props: {
menu: {
type: Array as PropType<string[]>,
default: () => [],
},
showLogo: {
type: Boolean as PropType<boolean>,
default: true,
},
isFixed: {
type: Boolean as PropType<boolean>,
default: true,
},
layout: {
type: String as PropType<string>,
default: '',
},
headerHeight: {
type: String as PropType<string>,
default: '64px',
},
theme: {
type: String as PropType<string>,
default: 'light',
},
isCompact: {
type: Boolean as PropType<boolean>,
default: false,
},
},
setup(props) {
const store = useStore();
const router = useRouter();
const changeCollapsed = () => {
store.commit('setting/toggleSidebarCompact');
};
const autoCollapsed = () => {
const isCompact = window.innerWidth <= MIN_POINT;
store.commit('setting/showSidebarCompact', isCompact);
};
onMounted(() => {
autoCollapsed();
window.onresize = () => {
autoCollapsed();
};
});
const getActiveName = (maxLevel = 2) => {
const route = useRoute();
if (!route.path) {
return '';
}
return route.path
.split('/')
.filter((_item: string, index: number) => index <= maxLevel && index > 0)
.map((item: string) => `/${item}`)
.join('');
};
const routerChange = (path: string) => {
router.push({
path,
});
};
const goHome = () => {
router.push('/dashboard/base');
};
return {
PREFIX,
...useComputed(props),
autoCollapsed,
changeCollapsed,
getActiveName,
routerChange,
goHome,
};
},
render() {
const active = this.getActiveName();
return (
<div class={this.sideNavCls}>
<t-menu
class={this.menuCls}
theme={this.theme}
value={active}
collapsed={this.collapsed}
v-slots={{
logo: () =>
this.showLogo && (
<span class={`${PREFIX}-side-nav-logo-wrapper`} onClick={this.goHome}>
{this.collapsed ? (
<img class={`${PREFIX}-side-nav-logo-t-logo`} src={tLogoUrl as string} />
) : (
<t-logo-full class={`${PREFIX}-side-nav-logo-tdesign-logo`} />
)}
</span>
),
operations: () => (
<span class="version-container">
{!this.collapsed && 'TDesign Starter'} {pgk.version}
</span>
),
}}
>
<sub-menu navData={this.menu} />
</t-menu>
<div class={`${PREFIX}-side-nav-placeholder${this.collapsed ? '-hidden' : ''}`}></div>
</div>
);
},
});

View File

@ -0,0 +1,72 @@
import { defineComponent, PropType, computed } from 'vue';
import { PREFIX as prefix } from '@/config/global';
import { MenuRoute } from '@/interface';
const getMenuList = (list: MenuRoute[], basePath?: string): MenuRoute[] => {
if (!list) {
return [];
}
return list.map((item) => {
const path = basePath ? `${basePath}/${item.path}` : item.path;
return {
path,
title: item.meta?.title,
icon: item.meta?.icon || '',
children: getMenuList(item.children, path),
meta: item.meta,
redirect: item.redirect,
};
});
};
const useRenderNav = (list: Array<MenuRoute>) => {
return list.map((item) => {
if (!item.children || !item.children.length || item.meta?.single) {
return (
<t-menu-item
name={item.path}
value={item.meta?.single ? item.redirect : item.path}
to={item.path}
icon={() => item.icon && <t-icon name={item.icon} />}
>
{item.title}
</t-menu-item>
);
}
return (
<t-submenu
name={item.path}
value={item.path}
title={item.title}
icon={() => item.icon && <t-icon name={item.icon} />}
>
{item.children && useRenderNav(item.children)}
</t-submenu>
);
});
};
export default defineComponent({
name: 'SubMenu',
props: {
navData: {
type: Array as PropType<MenuRoute[]>,
default: () => [],
},
},
setup(props) {
const list = computed(() => {
const { navData } = props;
return getMenuList(navData);
});
return {
prefix,
list,
useRenderNav,
};
},
render() {
return <div>{this.useRenderNav(this.list)}</div>;
},
});

166
src/layouts/index.tsx Normal file
View File

@ -0,0 +1,166 @@
import { defineComponent } from 'vue';
import { mapGetters } from 'vuex';
import TdesignHeader from './components/Header.vue';
import TdesignBreadcrumb from './components/Breadcrumb.vue';
import TdesignFooter from './components/Footer.vue';
import TdesignSideNav from './components/SideNav';
import TdesignContent from './components/Content.vue';
import { PREFIX } from '@/config/global';
import TdesignSetting from './setting.vue';
import { ModeType, SettingType, ClassName } from '@/interface';
import '@/style/layout.less';
const name = `${PREFIX}-base-layout`;
export default defineComponent({
name,
components: {
TdesignHeader,
TdesignFooter,
TdesignSideNav,
TdesignSetting,
TdesignBreadcrumb,
TdesignContent,
},
computed: {
...mapGetters({
showSidebar: 'setting/showSidebar',
showHeader: 'setting/showHeader',
showHeaderLogo: 'setting/showHeaderLogo',
showSidebarLogo: 'setting/showSidebarLogo',
showFooter: 'setting/showFooter',
mode: 'setting/mode',
menuRouters: 'permission/routers',
}),
setting(): SettingType {
return this.$store.state.setting;
},
mainLayoutCls(): ClassName {
return [
{
't-layout-has-sider': this.showSidebar,
},
];
},
headerMenu() {
const { layout, splitMenu } = this.$store.state.setting;
const { menuRouters } = this;
if (layout === 'mix') {
if (splitMenu) {
return menuRouters.map((menu) => ({
...menu,
children: [],
}));
}
return [];
}
return menuRouters;
},
sideMenu() {
const { layout, splitMenu } = this.$store.state.setting;
const { menuRouters } = this;
if (layout === 'mix' && splitMenu) {
let index;
for (index = 0; index < menuRouters.length; index++) {
const item = menuRouters[index];
if (item.children && item.children.length > 0) {
return item.children.map((menuRouter) => ({ ...menuRouter, path: `${item.path}/${menuRouter.path}` }));
}
}
}
return menuRouters;
},
},
methods: {
getNavTheme(mode: ModeType, layout: string, type: string): string {
if (mode === 'dark') {
return 'dark';
}
if (type.includes(layout)) {
return 'dark';
}
return this.mode;
},
renderSidebar() {
// const theme =
// this.setting.mode === 'dark' ? 'dark' : this.setting.layout === 'mix' ? 'light' : this.setting.theme;
// menu 组件最多支持 3级菜单
const theme = this.getNavTheme(this.setting.mode, this.setting.layout, ['side']);
return (
this.showSidebar && (
<tdesign-side-nav
showLogo={this.showSidebarLogo}
layout={this.setting.layout}
isFixed={this.setting.isSidebarFixed}
menu={this.sideMenu}
theme={theme}
isCompact={this.setting.isSidebarCompact}
/>
)
);
},
renderHeader() {
const theme = this.getNavTheme(this.setting.mode, this.setting.layout, ['mix', 'top']);
return (
this.showHeader && (
<tdesign-header
showLogo={this.showHeaderLogo}
theme={theme}
layout={this.setting.layout}
isFixed={this.setting.isHeaderFixed}
menu={this.headerMenu}
isCompact={this.setting.isSidebarCompact}
/>
)
);
},
renderContent() {
const { showBreadcrumb } = this.setting;
const { showFooter } = this;
return (
<t-layout class={[`${PREFIX}-layout`, 'narrow-scrollbar']}>
<t-content class={`${PREFIX}-content-layout`}>
{showBreadcrumb && <tdesign-breadcrumb />}
<TdesignContent />
</t-content>
{showFooter && this.renderFooter()}
</t-layout>
);
},
renderFooter() {
return (
<t-footer class={`${PREFIX}-footer-layout`}>
<tdesign-footer />
</t-footer>
);
},
},
render() {
const { layout } = this.setting;
const header = this.renderHeader();
const sidebar = this.renderSidebar();
const content = this.renderContent();
const footer = this.renderFooter();
return (
<div class={`${PREFIX}-wrapper`}>
{layout === 'side' ? (
<t-layout class={this.mainLayoutCls} key="side">
<t-aside>{sidebar}</t-aside>
<t-layout>{[header, content]}</t-layout>
</t-layout>
) : (
<t-layout key="no-side">
{header}
<t-layout class={this.mainLayoutCls}>{[sidebar, content]}</t-layout>
{this.showFooter && footer}
</t-layout>
)}
<tdesign-setting />
</div>
);
},
});

269
src/layouts/setting.vue Normal file
View File

@ -0,0 +1,269 @@
<template>
<t-drawer
v-model:visible="showSettingPanel"
size="458px"
:footer="false"
value="medium"
header="页面配置"
:close-btn="true"
class="setting-drawer-container"
>
<div class="setting-container">
<t-form ref="form" :data="formData" label-align="left">
<div class="setting-group-title">主题模式</div>
<t-radio-group v-model="formData.mode" default-vaule="dark">
<div v-for="(item, index) in MODE_OPTIONS" :key="index" class="setting-layout-drawer">
<t-radio-button :key="index" :value="item">
<thumbnail :src="getThumbnailUrl(item)" />
</t-radio-button>
</div>
</t-radio-group>
<div class="setting-group-title">主题色</div>
<t-radio-group v-model="formData.brandTheme" default-vaule="default">
<div v-for="(item, index) in COLOR_OPTIONS" :key="index" class="setting-layout-drawer">
<t-radio-button :key="index" :value="item" class="setting-layout-color-group">
<color-container :value="item" />
</t-radio-button>
</div>
</t-radio-group>
<div class="setting-group-title">导航布局</div>
<t-radio-group v-model="formData.layout" default-vaule="top">
<div v-for="(item, index) in LAYOUT_OPTION" :key="index" class="setting-layout-drawer">
<t-radio-button :key="index" :value="item">
<thumbnail :src="getThumbnailUrl(item)" />
</t-radio-button>
</div>
</t-radio-group>
<t-form-item v-show="formData.layout === 'mix'" label="分割菜单(混合模式下有效)" name="splitMenu">
<t-switch v-model="formData.splitMenu" />
</t-form-item>
<t-form-item v-show="formData.layout !== 'side'" label="固定 Header" name="isHeaderFixed">
<t-switch v-model="formData.isHeaderFixed" />
</t-form-item>
<t-form-item v-show="formData.layout !== 'top'" label="固定 Sidebar" name="isSidebarFixed">
<t-switch v-model="formData.isSidebarFixed" />
</t-form-item>
<div class="setting-group-title">元素开关</div>
<t-form-item v-show="formData.layout === 'side'" label="显示 Header" name="showHeader">
<t-switch v-model="formData.showHeader" />
</t-form-item>
<t-form-item label="显示 Breadcrumbs" name="showBreadcrumb">
<t-switch v-model="formData.showBreadcrumb" />
</t-form-item>
<t-form-item label="显示 Footer" name="showFooter">
<t-switch v-model="formData.showFooter" />
</t-form-item>
<t-form-item v-show="formData.showFooter && !formData.isSidebarFixed" label="footer 内收" name="footerPosition">
<t-switch v-model="formData.isFooterAside" />
</t-form-item>
</t-form>
<div class="setting-info">
<p>请复制后手动修改配置文件: /src/config/style.js</p>
<t-button theme="primary" variant="text" @click="handleCopy"> 复制配置项 </t-button>
</div>
</div>
</t-drawer>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
import { useStore } from 'vuex';
import { MessagePlugin } from 'tdesign-vue-next';
import STYLE_CONFIG from '@/config/style';
import Thumbnail from '@/components/thumbnail/index.vue';
import ColorContainer from '@/components/color/index.vue';
const LAYOUT_OPTION = ['side', 'top', 'mix'];
const COLOR_OPTIONS = ['default', 'purple', 'cyan', 'green', 'yellow', 'orange', 'red', 'pink'];
const MODE_OPTIONS = ['light', 'dark', 'auto'];
export default defineComponent({
name: 'DefaultLayoutSetting',
components: { Thumbnail, ColorContainer },
setup() {
const formData = ref({ ...STYLE_CONFIG });
const store = useStore();
const showSettingPanel = computed({
get() {
return store.state.setting.showSettingPanel;
},
set(newVal) {
store.commit('setting/toggleSettingPanel', newVal);
},
});
const handleCopy = () => {
MessagePlugin.closeAll();
MessagePlugin.success('复制成功');
};
return {
MODE_OPTIONS,
LAYOUT_OPTION,
COLOR_OPTIONS,
formData,
showSettingPanel,
handleCopy,
getThumbnailUrl(name: string): string {
return `https://tdesign.gtimg.com/tdesign-pro/setting/${name}.png`;
},
};
},
watch: {
formData: {
handler(newVal) {
this.$store.dispatch('setting/changeTheme', newVal);
},
deep: true,
},
},
});
</script>
<style lang="less">
@import '@/style/variables';
.tdesign-setting {
z-index: 100;
position: fixed;
bottom: 200px;
right: 0;
transition: transform 0.3s cubic-bezier(0.7, 0.3, 0.1, 1), visibility 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
height: 40px;
width: 40px;
border-radius: 20px 0 0 20px;
transition: all 0.3s;
.t-icon {
margin-left: 8px;
}
.tdesign-setting-text {
font-size: 12px;
display: none;
}
&:hover {
width: 96px;
.tdesign-setting-text {
display: inline-block;
}
}
}
.setting-layout-color-group {
display: inline-flex;
width: 42px;
height: 42px;
justify-content: center;
align-items: center;
border-radius: 50% !important;
border: 2px solid transparent !important;
.t-radio-button__label {
display: inline-flex;
}
}
.tdesign-setting-close {
position: fixed;
bottom: 200px;
right: 300px;
}
.setting-group-title {
font-size: 14px;
line-height: 22px;
margin: 32px 0 24px 0;
text-align: left;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
color: @text-color-primary;
}
.setting-link {
cursor: pointer;
color: @brand-color;
margin-bottom: 8px;
}
.setting-info {
position: absolute;
padding: 24px;
bottom: 0;
left: 0;
line-height: 20px;
font-size: 12px;
text-align: center;
color: @text-color-placeholder;
width: 100%;
background: @bg-color-container;
}
.setting-drawer-container {
.setting-container {
padding-bottom: 100px;
}
.t-radio-group.t-radio-group-medium {
min-height: 32px;
width: 100%;
height: auto;
justify-content: space-between;
align-items: center;
}
.setting-layout-drawer {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 16px;
.t-radio-button {
display: inline-flex;
max-height: 78px;
padding: 6px !important;
border-radius: @border-radius;
border: 2px solid #e3e6eb;
> .t-radio-button__label {
display: inline-flex;
}
}
.t-is-checked {
border: 2px solid @brand-color !important;
}
.t-form__controls--content {
justify-content: end;
}
}
.t-form__controls--content {
justify-content: end;
}
}
.setting-route-theme {
.t-form__label {
min-width: 310px !important;
color: @text-color-secondary;
}
}
.setting-color-theme {
.setting-layout-drawer {
.t-radio-button {
height: 32px;
}
&:last-child {
margin-right: 0;
}
}
}
</style>

View File

@ -7,6 +7,7 @@ import { store } from './store';
import router from './router';
import '@/style/index.less';
import './permission';
const app = createApp(App);

View File

@ -1,6 +1,6 @@
import { TdBaseTableProps } from 'tdesign-vue-next';
interface DashboardPannel {
interface DashboardPanel {
title: string;
number: string | number;
leftType: string;
@ -15,7 +15,7 @@ interface TendItem {
date: string;
}
export const PANE_LIST: Array<DashboardPannel> = [
export const PANE_LIST: Array<DashboardPanel> = [
{
title: '总收入',
number: '¥ 28,425.00',
@ -31,7 +31,7 @@ export const PANE_LIST: Array<DashboardPannel> = [
{
title: '活跃用户(个)',
number: '1126',
downTrend: '20.5%',
upTrend: '20.5%',
leftType: 'icon-usergroup',
},
{
@ -125,14 +125,15 @@ export const SALE_COLUMNS: TdBaseTableProps['columns'] = [
align: 'center',
colKey: 'index',
title: '排名',
width: 64,
width: 80,
fixed: 'left',
},
{
align: 'left',
ellipsis: true,
colKey: 'productName',
title: '客户名称',
minWidth: '200',
minWidth: 200,
},
{
align: 'center',
@ -149,14 +150,15 @@ export const SALE_COLUMNS: TdBaseTableProps['columns'] = [
{
align: 'center',
colKey: 'date',
width: 132,
width: 140,
title: '合同签订日期',
},
{
align: 'center',
colKey: 'operation',
title: '操作',
width: 76,
width: 80,
fixed: 'right',
},
];
@ -165,7 +167,8 @@ export const BUY_COLUMNS: TdBaseTableProps['columns'] = [
align: 'center',
colKey: 'index',
title: '排名',
width: 64,
width: 80,
fixed: 'left',
},
{
align: 'left',
@ -184,18 +187,19 @@ export const BUY_COLUMNS: TdBaseTableProps['columns'] = [
align: 'center',
colKey: 'count',
title: '订单量',
width: '100',
width: 100,
},
{
align: 'center',
colKey: 'date',
width: 132,
width: 140,
title: '合同签订日期',
},
{
align: 'center',
colKey: 'operation',
title: '操作',
width: 76,
width: 80,
fixed: 'right',
},
];

View File

@ -1,63 +1,4 @@
@import '@/style/index';
.t-tabs__content {
padding-top: @spacer-3;
}
.card-title-text-default {
display: inline-block;
white-space: nowrap;
}
.card-option {
width: 100%;
justify-content: right !important;
.dashboard-chart-title-left {
display: flex;
justify-content: left;
align-self: left;
width: 100%;
color: rgb(0 0 0 / 40%);
font-size: 15px;
padding-left: 8px;
}
}
.dashboard-panel {
padding: 0;
border-radius: @border-radius;
>div {
padding: 4px;
}
.card-container {
.card-content {
&-label-title {
color: @text-color-primary;
padding-bottom: 4px;
}
.t-table-content {
overflow: auto;
}
}
.t-table th,
.t-table td {
padding: 10px 8px;
}
.t-table tr {
color: @text-color-primary;
}
}
}
.dashboard-item-perfix {
margin-right: 0 !important;
}
@import '@/style/variables.less';
.dashboard-item {
display: flex;
@ -72,11 +13,15 @@
> span {
display: inline-block;
color: black;
color: @text-color-primary;
font-size: 36px;
line-height: 44px;
}
}
&:hover {
cursor: pointer;
}
}
.dashboard-item-bottom {
@ -87,167 +32,9 @@
> .t-icon {
cursor: pointer;
color: inherit;
}
}
.dashboard-bottom {
margin-top: 16px;
display: flex;
flex-direction: row;
/* 左边图表 */
.dashboard-bottom-left {
width: 100%;
height: 100%;
.dashboard-bottom-left-top {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
/* 计量统计 */
.dashboard-bottom-left-top-count-chart {
padding: 20px 24px;
background-color: white;
border-radius: @border-radius;
width: 100%;
min-width: 300px;
height: 320px;
}
/* 动态监测 */
.dashboard-bottom-left-top-monitor-chart {
padding: 20px 24px;
background-color: white;
border-radius: @border-radius;
margin-left: 16px;
width: 100%;
height: 320px;
}
}
/* 出入库概览() */
.dashboard-bottom-left-bottom {
background-color: white;
padding: 20px 24px;
border-radius: @border-radius;
margin-top: 16px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
min-height: 366px;
}
}
/* 右边列表-实时热度趋势 */
.dashboard-bottom-right {
padding: 20px 24px;
background-color: white;
border-radius: @border-radius;
margin-left: 16px;
width: 426px;
min-height: 690px;
ul {
padding-top: 16px;
li {
.dashboard-chart-tend-item-title {
color: grey !important;
}
.dashboard-chart-tend-sep {
padding: 0 0 17px !important;
}
.dashboard-chart-tend-item-line {
border-bottom: solid 1px #dedede;
padding: 9px 0;
}
.dashboard-chart-tend-item {
display: flex;
flex-direction: row;
justify-content: space-between;
.dashboard-chart-tend-left {
display: flex;
flex-direction: row;
.dashboard-chart-tend-num {
width: 48px;
}
.dashboard-chart-tend-rante {
width: 48px;
}
}
}
}
}
}
}
/* 标题内容 */
.dashboard-chart-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
font-weight: bold;
&-describe {
font-size: 14px !important;
color: rgb(0 0 0 / 60%) !important;
text-align: left;
line-height: 22px;
}
&-container {
.t-date-picker {
width: 240px;
}
}
span {
font-size: 16px;
color: rgb(0 0 0 / 90%);
overflow: hidden;
text-overflow: ellipsis;
}
.dashboard-chart-title-search {
display: flex;
flex-direction: row;
border: 1px solid #ddd;
border-radius: @border-radius;
padding: 0;
align-items: center;
justify-content: center;
font-weight: normal;
input {
border: solid 0 #fff !important;
}
}
label {
font-weight: normal !important;
padding: 5px 15px !important;
.t-radio-button__label {
font-size: 10px !important;
}
}
}
.dashboard-chart-container {
margin: 0 auto;
}
.dashboard-item-block {
display: flex;
align-items: center;
@ -257,10 +44,10 @@
}
.dashboard-item-trend {
margin-left: 4px;
margin-left: 8px;
}
.dashbord-item-left {
.dashboard-item-left {
position: absolute;
top: 32px;
right: 32px;
@ -281,7 +68,7 @@
}
}
.dashbord-rank {
.dashboard-rank {
display: inline-flex;
width: 24px;
height: 24px;
@ -298,32 +85,15 @@
}
}
.row-container {
margin-bottom: 8px !important;
}
.overview-pannel {
.overview-panel {
background-color: @bg-color-container;
border-radius: 3px;
}
.card-container.main-color {
background: @brand-color;
color: @text-color-primary;
.card-describe {
color: white;
}
.dashboard-item-top span {
color: white;
}
.dashboard-item-block {
color: rgb(255 255 255 / 55%);
}
.dashboard-item-bottom {
color: white;
}
.row-container {
margin-top: 16px;
}
.card-date-picker-container {
width: 240px;
}

View File

@ -32,7 +32,7 @@ export function getColorFromTheme(theme: string) {
/** 图表颜色 */
function chartListColor(): Array<string> {
const colorList: Array<string> = ['#0052D9', '#BCC4D0', '#7D46BD', '#0594FA', '#ED7B2F'];
const { setting } = state;
const { setting } = state as any;
return getColorFromTheme(setting.brandTheme) || colorList;
}
@ -1020,13 +1020,16 @@ export function getPieChartDataSet(radius = 42) {
return {
color: chartListColor(),
tooltip: {
trigger: 'item',
show: false,
trigger: 'axis',
position: null,
},
grid: {
top: '0',
right: '0',
},
legend: {
selectedMode: false,
itemWidth: 12,
itemHeight: 4,
textStyle: {
@ -1042,7 +1045,10 @@ export function getPieChartDataSet(radius = 42) {
name: '销售渠道',
type: 'pie',
radius: ['48%', '60%'],
avoidLabelOverlap: false,
avoidLabelOverlap: true,
selectedMode: true,
hoverAnimation: true,
silent: true,
label: {
show: true,
position: 'center',
@ -1050,13 +1056,13 @@ export function getPieChartDataSet(radius = 42) {
rich: {
value: {
color: '#303133',
fontSize: 36,
fontSize: 28,
fontWeight: 'normal',
lineHeight: 46,
},
name: {
color: '#909399',
fontSize: 14,
fontSize: 12,
lineHeight: 14,
},
},
@ -1068,7 +1074,7 @@ export function getPieChartDataSet(radius = 42) {
rich: {
value: {
color: '#303133',
fontSize: 36,
fontSize: 28,
fontWeight: 'normal',
lineHeight: 46,
},

View File

@ -1,14 +1,13 @@
<template>
<div class="dashboard-panel">
<!-- 顶部 card -->
<t-row :gutter="16" class="row-container">
<div>
<t-row :gutter="[16, 16]">
<t-col v-for="(item, index) in PANE_LIST" :key="item.title" :xs="6" :xl="3">
<card :describe="item.title" :style="{ height: '168px' }" :class="{ 'main-color': index == 0 }" size="small">
<card :subtitle="item.title" :style="{ height: '168px' }" :class="{ 'main-color': index == 0 }" size="small">
<div class="dashboard-item">
<div class="dashboard-item-top">
<span>{{ item.number }}</span>
</div>
<div class="dashbord-item-left">
<div class="dashboard-item-left">
<div v-if="index === 0">
<div
id="moneyContainer"
@ -51,12 +50,11 @@
<!-- 中部图表 -->
<t-row :gutter="16" class="row-container">
<t-col :xs="12" :xl="9">
<card title="统计数据">
<card title="统计数据" :describe="`(万元)${currentMonth}`">
<template #option>
<div class="dashboard-chart-title-left">(万元) {{ currentMonth }}</div>
<div class="dashboard-chart-title-container">
<t-date-picker
class="card-date-picker-slig"
class="card-date-picker-container"
theme="primary"
mode="date"
range
@ -74,10 +72,7 @@
</card>
</t-col>
<t-col :xs="12" :xl="3">
<card title="销售渠道">
<template #option>
<div class="dashboard-chart-title-left">{{ currentMonth }}</div>
</template>
<card title="销售渠道" :describe="currentMonth">
<div
id="countContainer"
ref="countContainer"
@ -110,7 +105,7 @@
</span>
</template>
<template #operation="slotProps">
<a class="link" @click="rehandleClickOp(slotProps)">操作</a>
<a class="t-button-link" @click="rehandleClickOp(slotProps)">操作</a>
</template>
</t-table>
</card>
@ -132,9 +127,8 @@
<template #growUp="{ row }">
<trend :type="row.growUp > 0 ? 'up' : 'down'" :describe="Math.abs(row.growUp)" />
</template>
<template #operation="slotProps">
<a class="link" @click="rehandleClickOp(slotProps)">操作</a>
<a class="t-button-link" @click="rehandleClickOp(slotProps)">操作</a>
</template>
</t-table>
</card>
@ -142,21 +136,19 @@
</t-row>
<!-- 出入库概览 -->
<div class="overview-pannel">
<div class="row-container overview-panel">
<t-row>
<t-col :xs="12" :xl="9">
<card title="出入库概览">
<card title="出入库概览" describe="(件)">
<template #option>
<div class="dashboard-chart-title-left">()</div>
<div class="dashboard-chart-title-container">
<t-date-picker
theme="primary"
mode="date"
range
:default-value="LAST_7_DAYS"
@change="onWharehouseChange"
/>
</div>
<t-date-picker
class="card-date-picker-container"
theme="primary"
mode="date"
range
:default-value="LAST_7_DAYS"
@change="onWharehouseChange"
/>
</template>
<div
id="stokeContainer"
@ -167,15 +159,13 @@
</card>
</t-col>
<t-col :xs="12" :xl="3">
<div>
<card :style="{ margin: '0 0 -40px 0' }">
<template #option>
<t-button>导出数据</t-button>
</template>
</card>
<card>
<template #option>
<t-button>导出数据</t-button>
</template>
<t-row>
<t-col :xs="6" :xl="12">
<card describe="本月出库总计(件)" :style="{ height: '168px' }" size="small">
<card describe="本月出库总计(件)" class="inner-card" size="small">
<div class="dashboard-item">
<div class="dashboard-item-top">
<span>1726</span>
@ -191,7 +181,7 @@
</t-col>
<t-col :xs="6" :xl="12">
<card describe="本月入库总计(件)" :style="{ height: '168px' }" size="small">
<card describe="本月入库总计(件)" class="inner-card" size="small">
<div class="dashboard-item">
<div class="dashboard-item-top">
<span>226</span>
@ -206,7 +196,7 @@
</card>
</t-col>
</t-row>
</div>
</card>
</t-col>
</t-row>
</div>
@ -309,12 +299,37 @@ export default defineComponent({
console.log(val);
},
getRankClass(index: number) {
return ['dashbord-rank', { 'dashbord-rank__top': index < 3 }];
return ['dashboard-rank', { 'dashboard-rank__top': index < 3 }];
},
};
},
});
</script>
<style lang="less">
@import url('./index.less');
<style lang="less" scoped>
@import './index.less';
</style>
<style lang="less">
@import '@/style/variables.less';
.card-container.main-color {
background: @brand-color !important;
color: @text-color-primary !important;
.card-describe {
color: @text-color-anti !important;
}
.dashboard-item-top span {
color: @text-color-anti !important;
}
.dashboard-item-block {
color: @text-color-anti !important;
opacity: 0.6;
}
.dashboard-item-bottom {
color: @text-color-anti !important;
}
}
</style>

View File

@ -1,33 +1,34 @@
export const PANE_LIST_DATA = [
{
title: '总申请数(次)',
count: 1126,
percent: 10,
number: '1126',
upTrend: '10%',
},
{
title: '供应商数量(个)',
count: 1126,
percent: -13,
number: '13',
downTrend: '13%',
},
{
title: '采购商品品类(类)',
count: 1126,
percent: 10,
number: '4',
upTrend: '10%',
},
{
title: '申请人数量(人)',
count: 1126,
percent: 44,
number: 90,
downTrend: '44%',
leftType: 'icon-file-paste',
},
{
title: '申请完成率(%',
count: 1126,
percent: 70,
number: 80.5,
upTrend: '70%',
},
{
title: '到货及时率(%',
count: 78,
percent: 16,
number: 78,
upTrend: '16%',
},
];

View File

@ -1,18 +1,10 @@
@import '@/style/index';
@import '@/style/variables.less';
.t-tabs__content {
padding-top: @spacer-3;
}
.list-card-item + .list-card-item {
.card-container-margin {
margin-top: 16px;
}
.dashboard-panel-detail {
margin-top: -16px;
}
.card-date-picker {
.card-date-picker-container {
width: 240px;
}
@ -20,30 +12,39 @@
margin-left: 8px;
}
.dashboard-detail-container-item {
height: 168px;
border: solid 1px #ebebeb;
padding: 24px 32px;
box-sizing: border-box;
border-radius: @border-radius;
.dark {
.dashboard-detail-container-item {
&:hover {
background: @gray-color-14;
cursor: pointer;
}
}
}
&:hover {
background: @gray-color-1;
cursor: pointer;
.light {
.dashboard-detail-container-item {
&:hover {
background: @gray-color-1;
cursor: pointer;
}
}
}
.dashboard-detail-container-item {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
height: 170px;
.title {
color: @text-color-secondary;
}
h1 {
.number {
font-size: 36px;
color: rgba(0,0,0,.90);
font-weight: 500;
padding-top: 8px;
padding-bottom: 24px;
span {
font-size: 14px;
color: rgba(0,0,0,.30);
padding-left: 8px;
}
line-height: 44px;
color: @text-color-primary;
}
.dashboard-detail-container-item-text {
@ -52,27 +53,25 @@
align-items: center;
justify-content: space-between;
font-size: 14px;
color: rgba(0,0,0,.40);
color: @text-color-placeholder;
text-align: left;
line-height: 22px;
> span {
line-height: 18px;
&-left {
display: flex;
.icon {
margin: 0 8px;
}
}
}
}
.dashboard-detail-bottom-container {
padding: 20px 24px;
background-color: white;
border-radius: @border-radius;
margin-top: 16px;
min-height: 400px;
.dashboard-chart-title-left {
display: flex;
width: 100%;
color: @text-color-placeholder;
justify-content: left;
font-size: 15px;
padding-left: 8px;
}
.trend-container {
margin-left: 4px;
}

View File

@ -1,46 +1,50 @@
<template>
<div class="dashboard-panel-detail">
<card title="本月采购申请情况">
<t-row :gutter="16">
<t-row :gutter="[16, 16]">
<t-col v-for="(item, index) in PANE_LIST_DATA" :key="index" :xs="6" :xl="3">
<div class="dashboard-detail-container-item">
<span>{{ item.title }}</span>
<h1>{{ item.count }}</h1>
<card border class="dashboard-detail-container-item" size="small" :subtitle="item.title">
<div class="number">{{ item.number }}</div>
<div class="dashboard-detail-container-item-text">
<span
>环比<trend :type="item.percent > 0 ? 'up' : 'down'" :describe="`${Math.abs(item.percent)}%`"
/></span>
<div class="dashboard-detail-container-item-text-left">
环比
<trend class="icon" :type="item.upTrend ? 'up' : 'down'" :describe="item.upTrend || item.downTrend" />
</div>
<t-icon name="chevron-right" />
</div>
</div>
</card>
</t-col>
</t-row>
</card>
<t-row :gutter="16">
<t-row :gutter="16" class="card-container-margin">
<t-col :xs="12" :xl="9">
<card title="采购商品申请趋势">
<card title="采购商品申请趋势" describe="(件)">
<template #option>
<div class="card-date-picker">
<t-date-picker
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"
range
@change="onMaterialChange"
/>
</div>
<t-date-picker
class="card-date-picker-container"
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"
range
@change="onMaterialChange"
/>
</template>
<div id="lineContainer" style="width: 100%; height: 418px" />
<div id="lineContainer" style="width: 100%; height: 406px" />
</card>
</t-col>
<t-col :xs="12" :xl="3">
<product-card v-for="(item, index) in PRODUCT_LIST" :key="index" :product="item" />
<product-card
v-for="(item, index) in PRODUCT_LIST"
:key="index"
:product="item"
:class="{ 'card-container-margin': index !== 0 }"
/>
</t-col>
</t-row>
<card title="采购商品满意度分布">
<card title="采购商品满意度分布" class="card-container-margin">
<template #option>
<t-date-picker
class="card-date-picker"
class="card-date-picker-container"
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"

View File

@ -54,5 +54,109 @@ export default defineComponent({
</script>
<style lang="less" scoped>
@import url('../index.less');
@import '@/style/variables.less';
.operater-gap {
margin-left: 20px;
}
.operater-block {
position: relative;
background-color: @bg-color-container;
border: 1px solid #e3e6eb;
border-radius: 3px;
.operater-content {
padding: 20px 32px 24px 32px;
height: 256px;
.operater-title-icon {
background: #ecf2fe;
color: @brand-color;
font-size: 56px;
padding: 14px;
border-radius: 100%;
}
.operater-title {
margin-bottom: 25px;
position: relative;
h1 {
display: inline-block;
font-weight: 500;
font-size: 24px;
color: @text-color-primary;
}
&-subtitle {
display: block;
font-weight: 400;
font-size: 14px;
width: 60%;
color: @text-color-placeholder;
}
&-tag {
margin-right: 4px;
margin-top: 8px;
margin-left: unset;
border: unset;
}
&-icon {
position: absolute;
top: 0px;
right: 0px;
}
svg {
circle {
fill: @brand-color-2;
}
path {
fill: @brand-color;
}
}
}
.operater-item {
position: relative;
padding-top: 8px;
padding-bottom: 8px;
&-info {
display: inline-block;
width: 60%;
text-align: left;
font-size: 14px;
color: @text-color-placeholder;
}
&-icon {
position: absolute;
bottom: 8px;
right: 0;
}
}
}
.operater-footer {
position: absolute;
width: 100%;
bottom: 0px;
left: 0;
.t-progress--thin {
display: unset;
}
&-percentage {
position: absolute;
bottom: 15px;
right: 32px;
color: @text-color-placeholder;
}
}
}
</style>

View File

@ -1,10 +1,3 @@
interface TableRowType {
index: number;
pdName: string;
purchaseNum: number;
updateTime: string;
}
export const BASE_INFO_DATA = [
{
name: '合同名称',
@ -86,52 +79,50 @@ export const BASE_INFO_DATA = [
export const TABLE_COLUMNS_DATA = [
{
align: 'center',
minWidth: '250',
width: 300,
ellipsis: true,
colKey: 'index',
title: '申请号',
sorter: (a: TableRowType, b: TableRowType) => a.index - b.index,
sorter: (a, b) => a.index.substr(3) - b.index.substr(3),
},
{
minWidth: '100',
width: 200,
ellipsis: true,
colKey: 'pdName',
title: '产品名称',
sorter: (a: TableRowType, b: TableRowType) => a.pdName.length - b.pdName.length,
sorter: (a, b) => a.pdName.length - b.pdName.length,
},
{
minWidth: '250',
width: 200,
ellipsis: true,
colKey: 'pdNum',
title: '产品编号',
},
{
minWidth: '100',
width: 200,
ellipsis: true,
colKey: 'purchaseNum',
title: '采购数量',
sorter: (a: TableRowType, b: TableRowType) => a.purchaseNum - b.purchaseNum,
sorter: (a, b) => a.purchaseNum - b.purchaseNum,
},
{
minWidth: '100',
width: 200,
ellipsis: true,
colKey: 'adminName',
title: '申请部门',
},
{
minWidth: '250',
className: 'test',
width: 200,
ellipsis: true,
colKey: 'updateTime',
title: '创建时间',
sorter: (a: TableRowType, b: TableRowType) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
},
{
align: 'left',
fixed: 'right',
width: 200,
className: 'test2',
ellipsis: true,
colKey: 'op',
title: '操作',
},
@ -139,8 +130,8 @@ export const TABLE_COLUMNS_DATA = [
export const PRODUCT_LIST = [
{
name: 'Macbook Pro 2021',
subTitle: 'Macbook Pro 2021',
name: 'MacBook Pro 2021',
subTitle: 'MacBook Pro 2021',
size: '13.3 英寸',
cpu: 'Apple M1',
memory: 'RAM 16GB',

View File

@ -1,29 +1,7 @@
@import '@/style/index';
@import '@/style/variables.less';
@import '../base//index.less';
.t-tabs__content {
padding-top: @spacer-3;
}
.t-layout--content-replace {
color: rgba(0, 0, 0, .6) !important;
}
.t-table.t-size-l th,
.t-table.t-size-l td {
padding: 13px 13px 12px 24px;
line-height: 22px;
}
.row-padding {
padding-top: 30px;
padding-left: 20px;
}
.t-tag.t-size-s {
margin-left: 8px;
}
.operater-add {
.product-add {
width: 100%;
height: 256px;
display: flex;
@ -33,7 +11,7 @@
border: dashed 1px #dedede;
border-radius: 3px;
.operater-sub-icon {
.product-sub-icon {
background: #ecf2fe;
color: @brand-color;
font-size: 33px;
@ -41,7 +19,7 @@
border-radius: 100%;
}
.operater-sub {
.product-sub {
font-size: 14px;
color: @text-color-secondary;
margin: 0 auto;
@ -53,7 +31,6 @@
cursor: pointer;
svg {
rect {
fill: @brand-color-1;
}
@ -68,233 +45,3 @@
padding-top: 12px;
}
}
.operater-gap {
margin-left: 20px;
}
.operater-block {
position: relative;
background-color: #fff;
border: 1px solid #e3e6eb;
border-radius: 3px;
.operater-content {
padding: 20px 32px 24px 32px;
height: 256px;
.operater-title-icon {
background: #ecf2fe;
color: @brand-color;
font-size: 56px;
padding: 14px;
border-radius: 100%;
}
.operater-title {
margin-bottom: 25px;
position: relative;
h1 {
display: inline-block;
font-weight: 500;
font-size: 24px;
}
&-subtitle {
display: block;
font-weight: 400;
font-size: 14px;
width: 60%;
color: rgba(0, 0, 0, .4);
}
&-tag {
margin-right: 4px;
margin-top: 8px;
margin-left: unset;
border: unset
}
&-icon {
position: absolute;
top: 0px;
right: 0px;
}
svg {
circle {
fill: @brand-color-2;
}
path {
fill: @brand-color;
}
}
}
.operater-item {
position: relative;
padding-top: 8px;
padding-bottom: 8px;
&-info {
display: inline-block;
width: 60%;
text-align: left;
font-size: 14px;
color: rgba(0, 0, 0, .4);
}
&-icon {
position: absolute;
bottom: 8px;
right: 0;
}
}
}
.operater-footer {
position: absolute;
width: 100%;
bottom: 0px;
left: 0;
.t-progress--thin {
display: unset;
}
&-percentage {
position: absolute;
bottom: 15px;
right: 32px;
color: rgba(0, 0, 0, .4);
}
}
}
.@{prefix} {
&-panel {
background-color: white;
padding: @spacer-3 @spacer-4 @spacer-4 @spacer-4;
border-radius: @border-radius;
}
&-search-input {
width: 360px;
margin-right: 8px;
}
&-operater-row {
margin-bottom: 32px;
}
&-operater-title {
font-size: 20px;
color: rgba(0, 0, 0, .9);
}
&-operater-label {
color: #000;
padding-left: 112px;
}
}
.advanced-card {
margin-top: 0 !important;
.card-title-default {
margin-bottom: 12px;
}
}
.info-block {
column-count: 2;
margin-bottom: 12px;
.info-item {
padding: 12px 0;
display: flex;
@media (max-width: @screen-sm-max) {
h1 {
width: 80px;
}
}
@media (min-width: @screen-md-min) {
h1 {
width: 200px;
}
}
h1 {
font-family: PingFangSC-Regular;
font-size: 14px;
color: @text-color-secondary;
text-align: left;
line-height: 22px;
}
span {
margin-left: 24px;
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: @border-radius-50;
background: @success-color-5;
}
.inProgress {
color: @success-color-5;
}
.pdf {
color: @brand-color-8;
}
}
}
.dialog-info-block {
.info-item {
padding: 12px 0;
display: flex;
h1 {
width: 84px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: @text-color-secondary;
text-align: left;
line-height: 22px;
}
span {
margin-left: 24px;
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: @border-radius-50;
background: @success-color-5;
}
.green {
color: @success-color-5;
}
.blue {
color: @brand-color-8;
}
}
}

View File

@ -1,6 +1,6 @@
<template>
<div>
<card title="基本信息" class="advanced-card">
<card title="基本信息">
<div class="info-block">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item">
<h1>{{ item.name }}</h1>
@ -18,30 +18,30 @@
</card>
<!-- 发票进度 -->
<card title="发票进度">
<t-row :class="PREFIX + '-operater-row row-padding'" justify="space-between">
<t-steps theme="dot" :current="updateCurrent">
<card title="发票进度" class="container-base-margin-top">
<t-row justify="space-between">
<t-steps :current="updateCurrent">
<t-step-item title="申请提交" content="已于12月21日提交" />
<t-step-item title="电子发票" content="预计13个工作日" />
<t-step-item title="发票已邮寄" content="电子发票开出后7个工作日联系" />
<t-step-item title="发票已邮寄" content="电子发票开出后7个工作日联系" />
<t-step-item title="完成" content />
</t-steps>
</t-row>
</card>
<!-- 产品目录 -->
<card title="产品目录">
<card title="产品目录" class="container-base-margin-top">
<template #option>
<t-radio-group default-value="dateVal">
<t-radio-button value="dateVal"> 季度 </t-radio-button>
<t-radio-button value="monthVal"> 月份 </t-radio-button>
</t-radio-group>
</template>
<t-row class="operater-block-container">
<t-row class="product-block-container">
<t-col :flex="1">
<div class="operater-add">
<div class="operater-sub">
<t-icon name="add" class="operater-sub-icon" />
<div class="product-add">
<div class="product-sub">
<t-icon name="add" class="product-sub-icon" />
<span>新增产品</span>
</div>
</div>
@ -53,7 +53,7 @@
</card>
<!-- 产品采购明细 -->
<card title="产品采购明细">
<card title="产品采购明细" class="container-base-margin-top">
<t-table
:columns="columns"
:data="data"

View File

@ -1,44 +1,4 @@
@import '@/style/index';
.t-tabs__content {
padding-top: @spacer-3;
}
.t-steps .t-steps-item-title {
font-size: @font-size-base;
}
.@{prefix} {
&-operater-row {
margin-bottom: 12px;
}
}
.detail-base-panel {
background-color: @bg-color-container;
padding: @spacer-3 @spacer-4 @spacer-4 @spacer-4;
border-radius: @border-radius;
margin-top: 0;
&-row {
margin-bottom: 12px;
}
}
.detail-base-info {
margin-bottom: 12px;
}
.detail-base-info-title {
margin: @spacer-1 0;
font-size: @font-size-xl;
font-weight: 500;
line-height: 28px;
color: @text-color-primary;
font-family: PingFangSC-Regular;
}
@import '@/style/variables.less';
.detail-base-info-steps {
padding-top: 12px;
@ -50,16 +10,15 @@
.info-item {
padding: 12px 0;
display: flex;
color: @text-color-primary;
@media (max-width: @screen-sm-max) {
h1 {
width: 80px;
}
}
@media (min-width: @screen-md-min) {
h1 {
width: 200px;
}
@ -67,7 +26,6 @@
h1 {
font-weight: normal;
font-family: PingFangSC-Regular;
font-size: @font-size-base;
color: @text-color-secondary;
text-align: left;
@ -91,6 +49,46 @@
}
.pdf {
color: @brand-color;
&:hover {
cursor: pointer;
}
}
}
}
.dialog-info-block {
.info-item {
padding: 12px 0;
display: flex;
h1 {
width: 84px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: @text-color-secondary;
text-align: left;
line-height: 22px;
}
span {
margin-left: 24px;
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: @border-radius-50;
background: @success-color-5;
}
.green {
color: @success-color-5;
}
.blue {
color: @brand-color-8;
}
}

View File

@ -1,9 +1,6 @@
<template>
<div>
<div class="detail-base-panel">
<t-row class="detail-base-info" justify="space-between">
<p class="detail-base-info-title">基本信息</p>
</t-row>
<card title="基本信息">
<div class="info-block">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item">
<h1>{{ item.name }}</h1>
@ -18,26 +15,103 @@
</span>
</div>
</div>
<t-divider style="margin-top: 24px; margin-bottom: 23px; border-top-color: rgba(255, 255, 255, 0)" />
<div class="update-history">
<t-row class="detail-base-info" justify="space-between">
<p class="detail-base-info-title">变更记录</p>
</t-row>
<t-steps class="detail-base-info-steps" direction="vertical" theme="dot" :current="1">
<t-step-item title="上传合同附件" content="这里是提示文字" />
<t-step-item title="修改合同金额" content="这里是提示文字" />
<t-step-item title="新建合同" content="2020-12-01 15:00:00 管理员-李川操作" />
</t-steps>
</div>
</div>
</card>
<card title="变更记录" class="container-base-margin-top">
<t-steps class="detail-base-info-steps" layout="vertical" theme="dot" :current="1">
<t-step-item title="上传合同附件" content="这里是提示文字" />
<t-step-item title="修改合同金额" content="这里是提示文字" />
<t-step-item title="新建合同" content="2020-12-01 15:00:00 管理员-李川操作" />
</t-steps>
</card>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BASE_INFO_DATA } from './constants';
import Card from '@/components/card/index.vue';
const BASE_INFO_DATA = [
{
name: '合同名称',
value: '总部办公用品采购项目',
type: null,
},
{
name: '合同状态',
value: '履行中',
type: {
key: 'contractStatus',
value: 'inProgress',
},
},
{
name: '合同编号',
value: 'BH00010',
type: null,
},
{
name: '合同类型',
value: '主合同',
type: null,
},
{
name: '合同收付类型',
value: '付款',
type: null,
},
{
name: '合同金额',
value: '5,000,000元',
type: null,
},
{
name: '甲方',
value: '腾讯科技(深圳)有限公司',
type: null,
},
{
name: '乙方',
value: '欧尚',
type: null,
},
{
name: '合同签订日期',
value: '2020-12-20',
type: null,
},
{
name: '合同生效日期',
value: '2021-01-20',
type: null,
},
{
name: '合同结束日期',
value: '2022-12-20',
type: null,
},
{
name: '合同附件',
value: '总部办公用品采购项目合同.pdf',
type: {
key: 'contractAnnex',
value: 'pdf',
},
},
{
name: '备注',
value: '--',
type: null,
},
{
name: '创建时间',
value: '2020-12-22 10:00:00',
type: null,
},
];
export default defineComponent({
name: 'ListBase',
components: { Card },
data() {
return {
BASE_INFO_DATA,

View File

@ -1,8 +1,3 @@
interface TableRowType {
name: string;
updateTime: string;
}
export const BASE_INFO_DATA = [
{
name: '集群名',
@ -88,32 +83,33 @@ export const BASE_INFO_DATA = [
export const TABLE_COLUMNS = [
{
minWidth: '250',
minWidth: '448',
ellipsis: true,
colKey: 'name',
title: '项目名称',
sorter: (a: TableRowType, b: TableRowType) => a.name.length - b.name.length,
sorter: (a, b) => a.name.substr(10) - b.name.substr(10),
},
{
minWidth: '200',
width: '448',
ellipsis: true,
colKey: 'adminName',
title: '管理员',
},
{
minWidth: '100',
width: '448',
className: 'test',
ellipsis: true,
colKey: 'updateTime',
title: '创建时间',
sorter: (a: TableRowType, b: TableRowType) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
},
{
align: 'left',
width: 200,
width: '200',
className: 'test2',
ellipsis: true,
colKey: 'op',
fixed: 'right',
title: '操作',
},
];

View File

@ -1,70 +1,72 @@
<template>
<t-row :gutter="16">
<t-col :span="6">
<card title="部署趋势">
<div class="deploy-panel-left">
<div id="monitorContainer" style="width: 100%; height: 265px" />
</div>
</card>
</t-col>
<t-col :span="6">
<card title="告警情况">
<template #option>
<t-radio-group default-value="dateVal" @change="onAlertChange">
<t-radio-button value="dateVal"> 本周 </t-radio-button>
<t-radio-button value="monthVal"> 本月 </t-radio-button>
</t-radio-group>
<div>
<t-row :gutter="16">
<t-col :span="6">
<card title="部署趋势">
<div class="deploy-panel-left">
<div id="monitorContainer" style="width: 100%; height: 265px" />
</div>
</card>
</t-col>
<t-col :span="6">
<card title="告警情况">
<template #option>
<t-radio-group default-value="dateVal" @change="onAlertChange">
<t-radio-button value="dateVal"> 本周 </t-radio-button>
<t-radio-button value="monthVal"> 本月 </t-radio-button>
</t-radio-group>
</template>
<div id="dataContainer" style="width: 100%; height: 265px" />
</card>
</t-col>
</t-row>
<!-- 项目列表 -->
<card title="项目列表" class="container-base-margin-top">
<t-table
:columns="columns"
:data="data"
:pagination="pagination"
:hover="true"
row-key="index"
@sort-change="sortChange"
@change="rehandleChange"
>
<template #adminName="{ row }">
<span>
{{ row.adminName }}
<t-tag v-if="row.adminPhone" size="small">{{ row.adminPhone }}</t-tag>
</span>
</template>
<div id="dataContainer" style="width: 100%; height: 265px" />
</card>
</t-col>
</t-row>
<template #op="slotProps">
<a :class="PREFIX + '-link'" @click="listClick()">管理</a>
<a :class="PREFIX + '-link'" @click="deleteClickOp(slotProps)">删除</a>
</template>
<template #op-column>
<t-icon name="descending-order" />
</template>
</t-table>
</card>
<!-- 项目列表 -->
<card title="项目列表">
<t-table
:columns="columns"
:data="data"
:pagination="pagination"
:hover="true"
row-key="index"
@sort-change="sortChange"
@change="rehandleChange"
>
<template #adminName="{ row }">
<span>
{{ row.adminName }}
<t-tag v-if="row.adminPhone" size="small" style="color: rgba(0, 0, 0, 0.4)">{{ row.adminPhone }}</t-tag>
</span>
</template>
<template #op="slotProps">
<a :class="PREFIX + '-link'" @click="listClick()">管理</a>
<a :class="PREFIX + '-link'" @click="deleteClickOp(slotProps)">删除</a>
</template>
<template #op-column>
<t-icon name="descending-order" />
</template>
</t-table>
</card>
<t-dialog v-model:visible="visible" header="基本信息" @confirm="onConfirm">
<template #body>
<div class="dialog-info-block">
<t-dialog v-model:visible="visible" header="基本信息" @confirm="onConfirm">
<template #body>
<div class="dialog-info-block">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item">
<h1>{{ item.name }}</h1>
<span
:class="{
['green']: item.type && item.type.value === 'green',
['blue']: item.type && item.type.value === 'blue',
}"
>{{ item.value }}</span
>
<div class="dialog-info-block">
<div v-for="(item, index) in BASE_INFO_DATA" :key="index" class="info-item">
<h1>{{ item.name }}</h1>
<span
:class="{
['green']: item.type && item.type.value === 'green',
['blue']: item.type && item.type.value === 'blue',
}"
>{{ item.value }}</span
>
</div>
</div>
</div>
</div>
</template>
</t-dialog>
</template>
</t-dialog>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
@ -182,5 +184,5 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
@import url('./index.less');
@import url('../base/index.less');
</style>

View File

@ -50,10 +50,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
span {
font-family: PingFangSC-Regular;
}
}
.unread {
@ -72,5 +68,6 @@
min-height: 443px;
padding-top: 170px;
text-align: center;
color: @text-color-primary;
}
}

View File

@ -1,58 +1,59 @@
<template>
<div class="secondary-notification">
<t-tabs v-model="tabValue">
<t-tab-panel v-for="(tab, tabIndex) in TAB_LIST" :key="tabIndex" :value="tab.value" :label="tab.label">
<t-list v-if="msgDataList.length > 0" class="secondary-msg-list" :split="true">
<t-list-item v-for="(item, index) in msgDataList" :key="index">
<p :class="['content', { unread: item.status }]" @click="setReadStatus(item)">
<t-tag class="operater-title-tag" size="small" :theme="NOTIFICATION_TYPES[item.priorty]" variant="light">
{{ item.type }}
</t-tag>
{{ item.content }}
</p>
<template #action>
<span class="msg-date">{{ item.date }}</span>
<div>
<div class="secondary-notification">
<t-tabs v-model="tabValue">
<t-tab-panel v-for="(tab, tabIndex) in TAB_LIST" :key="tabIndex" :value="tab.value" :label="tab.label">
<t-list v-if="msgDataList.length > 0" class="secondary-msg-list" :split="true">
<t-list-item v-for="(item, index) in msgDataList" :key="index">
<p :class="['content', { unread: item.status }]" @click="setReadStatus(item)">
<t-tag size="small" :theme="NOTIFICATION_TYPES[item.quality]" variant="light">
{{ item.type }}
</t-tag>
{{ item.content }}
</p>
<template #action>
<span class="msg-date">{{ item.date }}</span>
<div class="msg-action">
<t-tooltip
class="set-read-icon"
:overlay-style="{ margin: '6px' }"
:content="item.status ? '设为已读' : '设为未读'"
>
<span class="msg-action-icon" @click="setReadStatus(item)">
<t-icon v-if="!!item.status" name="queue" size="16px" />
<read-icon v-else />
</span>
</t-tooltip>
<t-tooltip content="删除通知" :overlay-style="{ margin: '6px' }">
<span @click="handleClickDeleteBtn(item)">
<t-icon name="delete" size="16px" />
</span>
</t-tooltip>
</div>
</template>
</t-list-item>
</t-list>
<div v-else class="secondary-msg-list__empty-list">
<img src="https://tdesign.gtimg.com/pro-template/personal/nothing.png" alt="空" />
<p>暂无通知</p>
</div>
</t-tab-panel>
</t-tabs>
<div class="msg-action">
<t-tooltip
class="set-read-icon"
:overlay-style="{ margin: '6px' }"
:content="item.status ? '设为已读' : '设为未读'"
>
<span class="msg-action-icon" @click="setReadStatus(item)">
<t-icon v-if="!!item.status" name="queue" size="16px" />
<t-icon v-else name="chat" />
</span>
</t-tooltip>
<t-tooltip content="删除通知" :overlay-style="{ margin: '6px' }">
<span @click="handleClickDeleteBtn(item)">
<t-icon name="delete" size="16px" />
</span>
</t-tooltip>
</div>
</template>
</t-list-item>
</t-list>
<div v-else class="secondary-msg-list__empty-list">
<img src="https://tdesign.gtimg.com/pro-template/personal/nothing.png" alt="空" />
<p>暂无通知</p>
</div>
</t-tab-panel>
</t-tabs>
</div>
<t-dialog
v-model:visible="visible"
header="删除通知"
:body="`确认删除通知:${selectedItem && selectedItem.content}吗?`"
:on-confirm="deleteMsg"
/>
</div>
<t-dialog
v-model:visible="visible"
header="删除通知"
:body="`确认删除通知:${selectedItem && selectedItem.content}吗?`"
:on-confirm="deleteMsg"
/>
</template>
<script lang="ts">
import { defineComponent, ref, computed, ComputedRef } from 'vue';
import { useStore } from 'vuex';
import { NOTIFICATION_TYPES } from '@/constants';
import ReadIcon from '@/assets/read.svg?component';
import { NotificationItem } from '@/store/interface';
import { NotificationItem } from '@/interface';
const TAB_LIST = [
{
@ -71,7 +72,6 @@ const TAB_LIST = [
export default defineComponent({
name: 'DetailSecondary',
components: { ReadIcon },
setup() {
const tabValue = ref('msgData');

View File

@ -1,125 +1,20 @@
@import '@/style/index';
.t-tabs__content {
padding-top: @spacer-3;
}
.@{prefix} {
&-panel {
background-color: @bg-color-container;
padding: @spacer-3;
border-radius: @border-radius;
}
&-form {
padding-right: 50px;
padding-top: 30px;
border-top: solid 1px #eee;
}
}
.upload-tips {
margin-left: 16px;
font-size: 12px;
color: @text-color-secondary;
line-height: 20px;
}
.form-item-container {
display: flex;
// align-items: center;
justify-content: center;
}
@import '@/style/variables.less';
.form-basic-container {
display: flex;
align-items: center;
justify-content: center;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
.span-item {
width: 12px;
}
.form-gap {
padding-left: 32px;
}
.row-gap {
padding-bottom: 24px;
}
background-color: @bg-color-container;
padding-bottom: 134px;
.form-basic-item {
// padding: 0 60px 0 160px;
width: 676px;
.form-basic-container-title {
font-style: normal;
font-weight: normal;
font-size: 20px;
line-height: 22px;
color: rgba(0, 0, 0, .9);
padding: 64px 0 32px 0;
}
// .t-size-m {
// width: 100% !important;
// min-width: 280px;
// }
// .t-form-item__type {
// // width: 100% !important;
// min-width: 280px;
// }
}
.tdesign-pro-panel {
margin-top: 0px !important;
}
.t-textarea__inner {
height: 124px !important;
}
.form-cretifier {
font-size: 14px;
color: #000000;
padding-top: 24px;
padding-bottom: 130px;
.form-cretifier-container {
padding-top: 12px;
.form-cretifier-circle {
background: #0052d9;
border: 1px solid #a7a4a4;
border-radius: 40px;
width: 32px;
height: 32px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
text-align: center;
line-height: 22px;
font-weight: 500;
position: absolute;
}
.form-cretifier-gap1 {
margin-left: 25px;
}
.form-cretifier-gap2 {
margin-left: 50px;
}
.form-cretifier-blure {
background: #d4e3fc;
color: #0052d9;
}
line-height: 24px;
color: @text-color-primary;
margin: 64px 0 32px;
}
}
}
@ -131,48 +26,24 @@
justify-content: center;
padding-top: 30px;
padding-bottom: 28px;
background-color: #e3e6eb;
border-bottom: solid 1px #d8dadf;
background-color: @bg-color-component;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
.form-submint-sub {
.form-submit-sub {
width: 676px;
display: flex;
align-items: center;
justify-content: space-between;
.form-submit-left {
.form-submit-upload-span {
font-size: 14px;
line-height: 22px;
color: rgba(0, 0, 0, .4);
color: @text-color-placeholder;
padding-top: 8px;
display: inline-block;
}
}
.form-submit-upload-btn {
width: 144px;
height: 40px;
}
.form-submit-right {
.form-submit-cancel {
font-size: 16px !important;
color: #0052d9 !important;
background-color: #ebedf100 !important;
}
.form-submit-confirm {
font-size: 16px !important;
background: #0052d9 !important;
border-radius: 3px !important;
width: 80px !important;
height: 40px !important;
}
}
}
}

View File

@ -1,156 +1,174 @@
<template>
<div :class="`${PREFIX}-panel form-basic-container`">
<div class="form-basic-item">
<div class="form-basic-container-title">合同信息</div>
<!-- 表单内容 -->
<t-form
ref="form"
class="base-form"
:data="formData"
:rules="FORM_RULES"
label-align="top"
:label-width="100"
@reset="onReset"
@submit="onSubmit"
>
<!-- 合同名称,合同类型 -->
<t-row class="row-gap">
<t-col :flex="1">
<t-form-item label="合同名称" name="name">
<t-input v-model="formData.name" :style="{ width: '322px' }" placeholder="请输入内容" />
</t-form-item>
</t-col>
<t-col :flex="1">
<t-form-item label="合同类型" name="type" class="form-gap">
<t-select
v-model="formData.type"
:style="{ width: '322px' }"
placeholder="请选择类型"
class="demo-select-base"
clearable
>
<t-option v-for="(item, index) in TYPE_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
</t-row>
<div>
<div class="form-basic-container">
<div class="form-basic-item">
<div class="form-basic-container-title">合同信息</div>
<!-- 表单内容 -->
<t-form
ref="form"
class="base-form"
:data="formData"
:rules="FORM_RULES"
label-align="top"
:label-width="100"
@reset="onReset"
@submit="onSubmit"
>
<!-- 合同名称,合同类型 -->
<t-row class="row-gap" :gutter="[16, 24]">
<t-col :span="6">
<t-form-item label="合同名称" name="name">
<t-input v-model="formData.name" :style="{ width: '322px' }" placeholder="请输入内容" />
</t-form-item>
</t-col>
<t-col :span="6">
<t-form-item label="合同类型" name="type">
<t-select
v-model="formData.type"
:style="{ width: '322px' }"
placeholder="请选择类型"
class="demo-select-base"
clearable
>
<t-option v-for="(item, index) in TYPE_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
<!-- 合同收付类型 -->
<t-row class="row-gap">
<t-col :flex="1">
<t-form-item label="合同收付类型" name="payment">
<t-radio-group v-model="formData.payment">
<t-radio value="1"> 收款 </t-radio>
<t-radio value="2"> 付款 </t-radio>
</t-radio-group>
<span class="span-item" />
<t-input placeholder="请输入金额" :style="{ width: '160px' }" />
</t-form-item>
</t-col>
</t-row>
<!-- 合同收付类型 -->
<t-col :span="8">
<t-form-item label="合同收付类型" name="payment">
<t-radio-group v-model="formData.payment">
<t-radio value="1"> 收款 </t-radio>
<t-radio value="2"> 付款 </t-radio>
</t-radio-group>
<span class="space-item" />
<t-input placeholder="请输入金额" :style="{ width: '160px' }" />
</t-form-item>
</t-col>
<!-- 甲方 / 乙方 -->
<t-row class="row-gap">
<t-col :flex="1">
<t-form-item label="甲方" name="partyA">
<t-select v-model="formData.partyA" :style="{ width: '322px' }" class="demo-select-base" clearable>
<t-option v-for="(item, index) in PARTY_A_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
<t-col :flex="1">
<t-form-item label="乙方" name="partyB" class="form-gap">
<t-select v-model="formData.partyB" :style="{ width: '322px' }" class="demo-select-base" clearable>
<t-option v-for="(item, index) in PARTY_B_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
</t-row>
<t-col :span="6">
<t-form-item label="甲方" name="partyA">
<t-select
v-model="formData.partyA"
:style="{ width: '322px' }"
class="demo-select-base"
placeholder="请选择类型"
clearable
>
<t-option
v-for="(item, index) in PARTY_A_OPTIONS"
:key="index"
:value="item.value"
:label="item.label"
>
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
<t-col :span="6">
<t-form-item label="乙方" name="partyB">
<t-select
v-model="formData.partyB"
:style="{ width: '322px' }"
placeholder="请选择类型"
class="demo-select-base"
clearable
>
<t-option
v-for="(item, index) in PARTY_B_OPTIONS"
:key="index"
:value="item.value"
:label="item.label"
>
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
</t-col>
<t-col :span="6">
<t-form-item label="合同签订日期" name="signDate">
<t-date-picker
v-model="formData.signDate"
:style="{ width: '322px' }"
theme="primary"
mode="date"
separator="/"
/>
</t-form-item>
</t-col>
<t-col :span="6">
<t-form-item label="合同生效日期" name="startDate">
<t-date-picker
v-model="formData.startDate"
:style="{ width: '322px' }"
theme="primary"
mode="date"
separator="/"
/>
</t-form-item>
</t-col>
<t-col :span="6">
<t-form-item label="合同结束日期" name="endDate">
<t-date-picker
v-model="formData.endDate"
:style="{ width: '322px' }"
theme="primary"
mode="date"
separator="/"
/>
</t-form-item>
</t-col>
</t-row>
</t-form>
<t-row class="row-gap">
<t-col :flex="1">
<t-form-item label="合同签订日期" name="signDate">
<t-date-picker
v-model="formData.signDate"
:style="{ width: '322px' }"
theme="primary"
mode="date"
separator="/"
/>
</t-form-item>
</t-col>
<t-col :flex="1">
<t-form-item label="合同生效日期" name="startDate" class="form-gap">
<t-date-picker
v-model="formData.startDate"
:style="{ width: '322px' }"
theme="primary"
mode="date"
separator="/"
/>
</t-form-item>
</t-col>
</t-row>
<t-form-item label="合同结束日期" name="endDate">
<t-date-picker
v-model="formData.endDate"
:style="{ width: '322px' }"
theme="primary"
mode="date"
separator="/"
/>
</t-form-item>
</t-form>
<div class="form-basic-container-title form-title-gap">其它信息</div>
<t-form
ref="form"
class="base-form"
:data="formData"
:rules="FORM_RULES"
label-align="top"
:label-width="100"
@reset="onReset"
@submit="onSubmit"
>
<t-form-item label="备注" name="comment">
<t-textarea v-model="formData.comment" placeholder="请输入备注" />
</t-form-item>
</t-form>
<div class="form-cretifier">
<span class="form-cretifier-span">公证人</span>
<div class="form-cretifier-container">
<div class="form-cretifier-circle">D</div>
<div class="form-cretifier-circle form-cretifier-gap1">S</div>
<div class="form-cretifier-circle form-cretifier-gap2 form-cretifier-blure">+</div>
</div>
<div class="form-basic-container-title form-title-gap">其它信息</div>
<t-form
ref="form"
class="base-form"
:data="formData"
:rules="FORM_RULES"
label-align="top"
:label-width="100"
@reset="onReset"
@submit="onSubmit"
>
<t-form-item label="备注" name="comment">
<t-textarea v-model="formData.comment" :height="124" placeholder="请输入备注" />
</t-form-item>
<t-form-item label="公证人">
<t-avatar-group>
<t-avatar>D</t-avatar>
<t-avatar>S</t-avatar>
<t-avatar>+</t-avatar>
</t-avatar-group>
</t-form-item>
</t-form>
</div>
</div>
</div>
<div class="form-submit-container">
<div class="form-submint-sub">
<div class="form-submit-left">
<t-upload
v-model="files"
action=""
placeholder="支持批量上传文件,文 件格式不限,最多只能上传 10 份文件"
:format-response="formatResponse"
:before-upload="beforeUpload"
@fail="handleFail"
>
<t-button class="form-submit-upload-btn" variant="outline"> 上传合同文件 </t-button>
</t-upload>
<span class="form-submit-upload-span">请上传pdf文件大小在60M以内</span>
</div>
<div class="form-submit-right">
<t-button type="reset" class="form-submit-cancel" theme="default" variant="base"> 取消 </t-button>
<t-button theme="primary" class="form-submit-confirm" type="submit"> 提交 </t-button>
<div class="form-submit-container">
<div class="form-submit-sub">
<div class="form-submit-left">
<t-upload
v-model="files"
action=""
placeholder="支持批量上传文件,文 件格式不限,最多只能上传 10 份文件"
:format-response="formatResponse"
:before-upload="beforeUpload"
@fail="handleFail"
>
<t-button class="form-submit-upload-btn" variant="outline"> 上传合同文件 </t-button>
</t-upload>
<span class="form-submit-upload-span">请上传pdf文件大小在60M以内</span>
</div>
<div class="form-submit-right">
<t-button type="reset" class="form-submit-cancel" theme="default" variant="base"> 取消 </t-button>
<t-button theme="primary" class="form-submit-confirm" type="submit"> 提交 </t-button>
</div>
</div>
</div>
</div>
@ -158,7 +176,6 @@
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { PREFIX } from '@/config/global';
import { FORM_RULES, INITIAL_DATA, TYPE_OPTIONS, PARTY_A_OPTIONS, PARTY_B_OPTIONS } from './constants';
export default defineComponent({
@ -168,7 +185,6 @@ export default defineComponent({
const files = ref([]);
return {
PREFIX,
TYPE_OPTIONS,
PARTY_A_OPTIONS,
PARTY_B_OPTIONS,

View File

@ -1,68 +1,17 @@
@import '@/style/index';
@import '@/style/variables';
.t-tabs__content {
padding-top: @spacer-3;
}
.@{prefix} {
&-panel {
background-color: @bg-color-container;
&.@{prefix}-step-panel {
padding: 0;
}
}
&-step {
padding: 14px 0 24px 0;
}
&-form {
padding-right: 50px;
padding-top: 30px;
border-top: solid 1px #eee;
}
.form-step-container {
background-color: @bg-color-container;
}
.rule-tips {
padding: 24px 24px 0 24px;
margin: 8px 24px 0;
}
.step-top {
padding: 24px;
border-bottom: #eee solid 1px;
.step-container {
padding: 8px 24px 0;
}
.step-form {
padding: 24px;
.t-form__label {
min-width: 108px;
}
}
.step-form-4 {
height: 365px;
margin-top: 72px;
text-align: center;
.t-icon {
margin: 6px;
}
.text {
margin: 16px 0 8px 0;
font-weight: bold;
font-size: 20px;
color: @text-color-primary;
line-height: 30px;
}
.tips {
margin-bottom: 24px;
font-size: 14px;
color: @text-color-secondary;
line-height: 22px;
}
}

View File

@ -1,138 +1,140 @@
<template>
<div :class="`${PREFIX}-panel ${PREFIX}-step-panel`">
<!-- 简单步骤条 -->
<div :class="`${PREFIX}-step step-top`">
<t-steps :current="activeForm" status="process">
<t-step-item title="提交开票申请" />
<t-step-item title="填写发票信息" />
<t-step-item title="确认邮寄地址" />
<t-step-item title="完成" />
</t-steps>
</div>
<div>
<div class="form-step-container">
<!-- 简单步骤条 -->
<card title="基本信息">
<t-steps class="step-container" :current="activeForm" status="process">
<t-step-item title="提交开票申请" />
<t-step-item title="填写发票信息" />
<t-step-item title="确认邮寄地址" />
<t-step-item title="完成" />
</t-steps>
</card>
<!-- 分步表单1 -->
<div v-show="activeForm === 0" class="rule-tips">
<t-alert theme="info" title="发票开具规则:" :close="true">
<template #message>
<p>
1申请开票后电子发票在13个工作日内开具增值税专用发票纸质如资质审核通过将在电子发票开具后10个工作日内为您寄出
</p>
<p>2开票金额为您实际支付金额</p>
<p>3如有疑问请直接联系13300001111</p>
</template>
</t-alert>
</div>
<t-form
v-show="activeForm === 0"
class="step-form"
:data="formData1"
:rules="FORM_RULES"
label-align="right"
@submit="(result) => onSubmit(result, 1)"
>
<t-form-item label="合同名称" name="name">
<t-select v-model="formData1.name" :style="{ width: '480px' }" class="demo-select-base" clearable>
<t-option v-for="(item, index) in NAME_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
<t-form-item label="发票类型" name="type">
<t-select v-model="formData1.type" :style="{ width: '480px' }" class="demo-select-base" clearable>
<t-option v-for="(item, index) in TYPE_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
<t-form-item label="开票金额"> {{ amount }} </t-form-item>
<t-form-item>
<t-button theme="primary" type="submit"> 提交申请 </t-button>
</t-form-item>
</t-form>
<!-- 分步表单1 -->
<div v-show="activeForm === 0" class="rule-tips">
<t-alert theme="info" title="发票开具规则:" :close="true">
<template #message>
<p>
1申请开票后电子发票在13个工作日内开具增值税专用发票纸质如资质审核通过将在电子发票开具后10个工作日内为您寄出
</p>
<p>2开票金额为您实际支付金额</p>
<p>3如有疑问请直接联系13300001111</p>
</template>
</t-alert>
</div>
<t-form
v-show="activeForm === 0"
class="step-form"
:data="formData1"
:rules="FORM_RULES"
label-align="right"
@submit="(result) => onSubmit(result, 1)"
>
<t-form-item label="合同名称" name="name">
<t-select v-model="formData1.name" :style="{ width: '480px' }" class="demo-select-base" clearable>
<t-option v-for="(item, index) in NAME_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
<t-form-item label="发票类型" name="type">
<t-select v-model="formData1.type" :style="{ width: '480px' }" class="demo-select-base" clearable>
<t-option v-for="(item, index) in TYPE_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
<t-form-item label="开票金额"> {{ amount }} </t-form-item>
<t-form-item>
<t-button theme="primary" type="submit"> 提交申请 </t-button>
</t-form-item>
</t-form>
<!-- 分步表单2 -->
<t-form
v-show="activeForm === 1"
class="step-form"
:data="formData2"
:rules="FORM_RULES"
label-align="left"
@reset="onReset(0)"
@submit="(result) => onSubmit(result, 2)"
>
<t-form-item label="发票抬头" name="title">
<t-input v-model="formData2.title" :style="{ width: '480px' }" placeholder="请输入发票抬头" />
</t-form-item>
<t-form-item label="纳税人识别号" name="taxNum">
<t-input v-model="formData2.taxNum" :style="{ width: '480px' }" placeholder="请输入纳税人识别号" />
</t-form-item>
<t-form-item label="单位地址" name="address">
<t-input v-model="formData2.address" :style="{ width: '480px' }" placeholder="请输入单位地址" />
</t-form-item>
<t-form-item label="开户行" name="bank">
<t-input v-model="formData2.bank" :style="{ width: '480px' }" placeholder="请输入开户行" />
</t-form-item>
<t-form-item label="银行账号" name="bankAccount">
<t-input v-model="formData2.bankAccount" :style="{ width: '480px' }" placeholder="请输入银行账号" />
</t-form-item>
<t-form-item label="通知邮箱" name="email">
<t-input v-model="formData2.email" :style="{ width: '480px' }" placeholder="请输入通知邮箱" />
</t-form-item>
<t-form-item label="通知手机" name="tel">
<t-input v-model="formData2.tel" :style="{ width: '480px' }" placeholder="请输入通知手机" />
</t-form-item>
<t-form-item>
<t-button type="reset" theme="default" variant="base"> 上一步 </t-button>
<t-button theme="primary" type="submit"> 下一步 </t-button>
</t-form-item>
</t-form>
<!-- 分步表单2 -->
<t-form
v-show="activeForm === 1"
class="step-form"
:data="formData2"
:rules="FORM_RULES"
label-align="left"
@reset="onReset(0)"
@submit="(result) => onSubmit(result, 2)"
>
<t-form-item label="发票抬头" name="title">
<t-input v-model="formData2.title" :style="{ width: '480px' }" placeholder="请输入发票抬头" />
</t-form-item>
<t-form-item label="纳税人识别号" name="taxNum">
<t-input v-model="formData2.taxNum" :style="{ width: '480px' }" placeholder="请输入纳税人识别号" />
</t-form-item>
<t-form-item label="单位地址" name="address">
<t-input v-model="formData2.address" :style="{ width: '480px' }" placeholder="请输入单位地址" />
</t-form-item>
<t-form-item label="开户行" name="bank">
<t-input v-model="formData2.bank" :style="{ width: '480px' }" placeholder="请输入开户行" />
</t-form-item>
<t-form-item label="银行账号" name="bankAccount">
<t-input v-model="formData2.bankAccount" :style="{ width: '480px' }" placeholder="请输入银行账号" />
</t-form-item>
<t-form-item label="通知邮箱" name="email">
<t-input v-model="formData2.email" :style="{ width: '480px' }" placeholder="请输入通知邮箱" />
</t-form-item>
<t-form-item label="通知手机" name="tel">
<t-input v-model="formData2.tel" :style="{ width: '480px' }" placeholder="请输入通知手机" />
</t-form-item>
<t-form-item>
<t-button type="reset" theme="default" variant="base"> 上一步 </t-button>
<t-button theme="primary" type="submit"> 下一步 </t-button>
</t-form-item>
</t-form>
<!-- 分步表单3 -->
<t-form
v-show="activeForm === 2"
class="step-form"
:data="formData3"
:rules="FORM_RULES"
label-align="left"
@reset="onReset(1)"
@submit="(result) => onSubmit(result, 6)"
>
<t-form-item label="收货人" name="consignee">
<t-input v-model="formData3.consignee" :style="{ width: '480px' }" placeholder="请输入收货人" />
</t-form-item>
<t-form-item label="手机号码" name="mobileNum">
<t-input v-model="formData3.mobileNum" :style="{ width: '480px' }" placeholder="请输入手机号码" />
</t-form-item>
<t-form-item label="收货地址" name="deliveryAddress">
<t-select
v-model="formData3.deliveryAddress"
:style="{ width: '480px' }"
placeholder="请选择收货地址"
class="demo-select-base"
clearable
>
<t-option v-for="(item, index) in ADDRESS_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
<t-form-item label="详细地址" name="fullAddress">
<t-textarea v-model="formData3.fullAddress" :style="{ width: '480px' }" placeholder="请输入详细地址" />
</t-form-item>
<t-form-item>
<t-button type="reset" theme="default" variant="base"> 上一步 </t-button>
<t-button theme="primary" type="submit"> 下一步 </t-button>
</t-form-item>
</t-form>
<!-- 分步表单3 -->
<t-form
v-show="activeForm === 2"
class="step-form"
:data="formData3"
:rules="FORM_RULES"
label-align="left"
@reset="onReset(1)"
@submit="(result) => onSubmit(result, 6)"
>
<t-form-item label="收货人" name="consignee">
<t-input v-model="formData3.consignee" :style="{ width: '480px' }" placeholder="请输入收货人" />
</t-form-item>
<t-form-item label="手机号码" name="mobileNum">
<t-input v-model="formData3.mobileNum" :style="{ width: '480px' }" placeholder="请输入手机号码" />
</t-form-item>
<t-form-item label="收货地址" name="deliveryAddress">
<t-select
v-model="formData3.deliveryAddress"
:style="{ width: '480px' }"
placeholder="请选择收货地址"
class="demo-select-base"
clearable
>
<t-option v-for="(item, index) in ADDRESS_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
<t-form-item label="详细地址" name="fullAddress">
<t-textarea v-model="formData3.fullAddress" :style="{ width: '480px' }" placeholder="请输入详细地址" />
</t-form-item>
<t-form-item>
<t-button type="reset" theme="default" variant="base"> 上一步 </t-button>
<t-button theme="primary" type="submit"> 下一步 </t-button>
</t-form-item>
</t-form>
<!-- 分步表单4 -->
<div v-show="activeForm === 6" class="step-form-4">
<t-icon name="check-circle-filled" style="color: green" size="52px" />
<p class="text">完成开票申请</p>
<p class="tips">预计13个工作日会将电子发票发至邮箱发票邮寄请耐心等待</p>
<div class="button-group">
<t-button theme="primary" @click="onReset(0)"> 再次申请 </t-button>
<t-button variant="base" theme="default" @click="complete"> 查看进度 </t-button>
<!-- 分步表单4 -->
<div v-show="activeForm === 6" class="step-form-4">
<t-icon name="check-circle-filled" style="color: green" size="52px" />
<p class="text">完成开票申请</p>
<p class="tips">预计13个工作日会将电子发票发至邮箱发票邮寄请耐心等待</p>
<div class="button-group">
<t-button theme="primary" @click="onReset(0)"> 再次申请 </t-button>
<t-button variant="base" theme="default" @click="complete"> 查看进度 </t-button>
</div>
</div>
</div>
</div>
@ -141,7 +143,7 @@
import { defineComponent, ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import { ValidateResultContext } from 'tdesign-vue-next';
import { PREFIX } from '@/config/global';
import Card from '@/components/card/index.vue';
import {
FORM_RULES,
@ -155,6 +157,7 @@ import {
export default defineComponent({
name: 'FormStep',
components: { Card },
setup() {
const formData1 = ref({ ...INITIAL_DATA1 });
const formData2 = ref({ ...INITIAL_DATA2 });
@ -175,7 +178,6 @@ export default defineComponent({
});
return {
PREFIX,
NAME_OPTIONS,
TYPE_OPTIONS,
ADDRESS_OPTIONS,

View File

@ -1,44 +1,34 @@
export const COLUMNS = [
{ colKey: 'row-select', type: 'multiple', width: '50' },
{ colKey: 'row-select', type: 'multiple', width: 64, fixed: 'left' },
{
title: '合同名称',
minWidth: '200',
width: 200,
align: 'left',
ellipsis: true,
width: 300,
colKey: 'name',
},
{
title: '合同状态',
colKey: 'status',
width: 150,
cell: { col: 'status' },
},
{ title: '合同状态', colKey: 'status', width: 200, cell: { col: 'status' } },
{
title: '合同编号',
minWidth: '100',
width: 100,
width: 200,
ellipsis: true,
colKey: 'no',
},
{
title: '合同类型',
width: 150,
minWidth: '150',
width: 200,
ellipsis: true,
colKey: 'contractType',
},
{
title: '合同收付类型',
width: 200,
minWidth: '200',
ellipsis: true,
colKey: 'paymentType',
},
{
title: '合同金额 (元)',
width: 300,
minWidth: '300',
width: 200,
ellipsis: true,
colKey: 'amount',
},
@ -46,7 +36,6 @@ export const COLUMNS = [
align: 'left',
fixed: 'right',
width: 200,
ellipsis: true,
colKey: 'op',
title: '操作',
},

View File

@ -1,20 +1,19 @@
<template>
<div :class="`${PREFIX}-panel`">
<t-row :class="`${PREFIX}-operater-row`" justify="space-between">
<div>
<t-button @click="handleSetupContract"> 新建合同 </t-button>
<t-button variant="base" theme="default" :disabled="!selectedRowKeys.length"> 导出合同 </t-button>
<p v-if="!!selectedRowKeys.length" :class="`${PREFIX}-selected-count`">已选{{ selectedRowKeys.length }}</p>
</div>
<div :class="`${PREFIX}-search-input`">
<t-input v-model="searchValue" placeholder="请输入你需要搜索的内容" clearable>
<div>
<card class="list-card-container">
<t-row justify="space-between">
<div class="left-operation-container">
<t-button @click="handleSetupContract"> 新建合同 </t-button>
<t-button variant="base" theme="default" :disabled="!selectedRowKeys.length"> 导出合同 </t-button>
<p v-if="!!selectedRowKeys.length" class="selected-count">已选{{ selectedRowKeys.length }}</p>
</div>
<t-input v-model="searchValue" class="search-input" placeholder="请输入你需要搜索的内容" clearable>
<template #suffix-icon>
<t-icon-search size="20px" />
<search-icon size="20px" />
</template>
</t-input>
</div>
</t-row>
<div class="table-container">
</t-row>
<t-table
:data="data"
:columns="COLUMNS"
@ -41,38 +40,39 @@
<p v-if="row.contractType === CONTRACT_TYPES.SUPPLEMENT">待履行</p>
</template>
<template #paymentType="{ row }">
<p v-if="row.paymentType === CONTRACT_PAYMENT_TYPES.PAYMENT" class="payment-col">
<div v-if="row.paymentType === CONTRACT_PAYMENT_TYPES.PAYMENT" class="payment-col">
付款<trend class="dashboard-item-trend" type="up" />
</p>
<p v-if="row.paymentType === CONTRACT_PAYMENT_TYPES.RECIPT" class="payment-col">
</div>
<div v-if="row.paymentType === CONTRACT_PAYMENT_TYPES.RECIPT" class="payment-col">
收款<trend class="dashboard-item-trend" type="down" />
</p>
</div>
</template>
<template #op="slotProps">
<a :class="`${PREFIX}-link`" @click="handleClickDetail()">详情</a>
<a :class="`${PREFIX}-link`" @click="handleClickDelete(slotProps)">删除</a>
<a class="t-button-link" @click="handleClickDetail()">详情</a>
<a class="t-button-link" @click="handleClickDelete(slotProps)">删除</a>
</template>
</t-table>
</div>
</card>
<t-dialog
v-model:visible="confirmVisible"
header="是否确认删除"
:body="confirmBody"
:on-cancel="onCancel"
@confirm="onConfirmDelete"
/>
</div>
<t-dialog
v-model:visible="confirmVisible"
header="是否确认删除"
:body="confirmBody"
:on-cancel="onCancel"
@confirm="onConfirmDelete"
/>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import TIconSearch from 'tdesign-vue-next/lib/icon/search';
import { SearchIcon } from 'tdesign-icons-vue-next';
import { MessagePlugin } from 'tdesign-vue-next';
import { PREFIX } from '@/config/global';
import { CONTRACT_STATUS, CONTRACT_TYPES, CONTRACT_PAYMENT_TYPES } from '@/constants';
import Trend from '@/components/trend/index.vue';
import Card from '@/components/card/index.vue';
import { ResDataType } from '@/interface';
import request from '@/utils/request';
@ -81,7 +81,8 @@ import { COLUMNS } from './constants';
export default defineComponent({
name: 'ListBaseCard',
components: {
TIconSearch,
Card,
SearchIcon,
Trend,
},
setup() {
@ -159,7 +160,6 @@ export default defineComponent({
CONTRACT_TYPES,
CONTRACT_PAYMENT_TYPES,
COLUMNS,
PREFIX,
data,
searchValue,
dataLoading,
@ -195,5 +195,30 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
@import url('./index.less');
@import '@/style/variables';
.payment-col {
display: flex;
.trend-container {
display: flex;
align-items: center;
margin-left: 8px;
}
}
.left-operation-container {
padding: 6px 0;
margin-bottom: 16px;
.selected-count {
display: inline-block;
margin-left: 8px;
color: @text-color-secondary;
}
}
.search-input {
width: 360px;
}
</style>

View File

@ -3,15 +3,15 @@
<div class="list-card-item_detail">
<t-row justify="space-between">
<div :class="cardLogoClass">
<t-icon-shop v-if="product.type === 1" />
<t-icon-calendar v-if="product.type === 2" />
<t-icon-service v-if="product.type === 3" />
<t-icon-user-avatar v-if="product.type === 4" />
<t-icon-laptop v-if="product.type === 5" />
<shop-icon v-if="product.type === 1" />
<calendar-icon v-if="product.type === 2" />
<service-icon v-if="product.type === 3" />
<user-avatar-icon v-if="product.type === 4" />
<laptop-icon v-if="product.type === 5" />
</div>
<p :class="cardStatusClass">
{{ product.isSetup ? '已启用' : '已停用' }}
</p>
<t-tag :theme="product.isSetup ? 'success' : 'default'" :disabled="!product.isSetup">{{
product.isSetup ? '已启用' : '已停用'
}}</t-tag>
</t-row>
<p class="list-card-item_detail--name">
{{ product.name }}
@ -25,11 +25,12 @@
{{ typeMap[product.type - 1] }}
</t-button>
<t-button shape="circle" :disabled="!product.isSetup">
<t-icon-add />
<add-icon />
</t-button>
</div>
<t-dropdown
:disabled="!product.isSetup"
trigger="click"
:options="[
{
content: '管理',
@ -43,7 +44,9 @@
},
]"
>
<t-icon-more />
<t-button theme="default" :disabled="!product.isSetup" shape="square" variant="text">
<more-icon />
</t-button>
</t-dropdown>
</t-row>
</div>
@ -51,13 +54,15 @@
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import TIconShop from 'tdesign-vue-next/lib/icon/shop';
import TIconCalendar from 'tdesign-vue-next/lib/icon/calendar';
import TIconService from 'tdesign-vue-next/lib/icon/service';
import TIconUserAvatar from 'tdesign-vue-next/lib/icon/user-avatar';
import TIconLaptop from 'tdesign-vue-next/lib/icon/laptop';
import TIconMore from 'tdesign-vue-next/lib/icon/more';
import TIconAdd from 'tdesign-vue-next/lib/icon/add';
import {
ShopIcon,
CalendarIcon,
ServiceIcon,
UserAvatarIcon,
LaptopIcon,
MoreIcon,
AddIcon,
} from 'tdesign-icons-vue-next';
export interface CardProductType {
type: number;
@ -69,13 +74,13 @@ export interface CardProductType {
export default defineComponent({
name: 'ListCardComponent',
components: {
TIconShop,
TIconCalendar,
TIconService,
TIconUserAvatar,
TIconLaptop,
TIconMore,
TIconAdd,
ShopIcon,
CalendarIcon,
ServiceIcon,
UserAvatarIcon,
LaptopIcon,
MoreIcon,
AddIcon,
},
props: {
product: {
@ -102,14 +107,6 @@ export default defineComponent({
},
]);
const cardStatusClass = computed(() => [
'list-card-item_detail--status',
{
'list-card-item_detail--status__disabled': !props.product.isSetup,
'list-card-item_detail--status__setup': props.product.isSetup,
},
]);
const cardControlClass = computed(() => [
'list-card-item_detail--control',
{
@ -120,7 +117,6 @@ export default defineComponent({
return {
cardClass,
cardLogoClass,
cardStatusClass,
cardControlClass,
typeMap: ['A', 'B', 'C', 'D', 'E'],
handleClickManage(product) {
@ -135,7 +131,7 @@ export default defineComponent({
</script>
<style lang="less" scoped>
@import '@/style/index';
@import '@/style/variables';
.list-card-item {
display: flex;
@ -173,25 +169,6 @@ export default defineComponent({
font-weight: bold;
}
&--status {
border-radius: @border-radius;
width: 52px;
height: 24px;
line-height: 24px;
font-size: 12px;
text-align: center;
font-weight: 400;
&__disabled {
background: @gray-color-2;
}
&__setup {
background: @success-color-5;
color: @text-color-anti;
}
}
&--desc {
font-size: 14px;
line-height: 20px;

View File

@ -122,5 +122,3 @@ export default defineComponent({
},
});
</script>
<style></style>

View File

@ -0,0 +1,31 @@
@import '@/style/variables.less';
.list-card {
height: 100%;
&-operation {
display: flex;
justify-content: space-between;
.search-input {
width: 360px;
}
}
&-items {
margin-top: 14px;
margin-bottom: 24px;
}
&-pagination {
padding: 16px;
}
&-loading {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}

View File

@ -1,66 +1,67 @@
<template>
<div class="list-card-operation">
<t-button @click="formDialogVisible = true"> 新建产品 </t-button>
<div :class="PREFIX + '-search-input'">
<t-input v-model="searchValue" placeholder="请输入你需要搜索的内容" clearable>
<div>
<div class="list-card-operation">
<t-button @click="formDialogVisible = true"> 新建产品 </t-button>
<t-input v-model="searchValue" class="search-input" placeholder="请输入你需要搜索的内容" clearable>
<template #suffix-icon>
<t-icon-search v-if="searchValue === ''" size="20px" />
<search-icon v-if="searchValue === ''" size="20px" />
</template>
</t-input>
</div>
</div>
<dialog-form v-model:visible="formDialogVisible" :data="formData" />
<dialog-form v-model:visible="formDialogVisible" :data="formData" />
<template v-if="pagination.total > 0 && !dataLoading">
<div class="list-card-items">
<t-row :gutter="[16, 12]">
<t-col
v-for="product in productList.slice(
pagination.pageSize * (pagination.current - 1),
pagination.pageSize * pagination.current,
)"
:key="product.index"
:lg="4"
:xs="6"
:xl="3"
>
<card
class="list-card-item"
:product="product"
@delete-item="handleDeleteItem"
@manage-product="handleManageProduct"
/>
</t-col>
</t-row>
<template v-if="pagination.total > 0 && !dataLoading">
<div class="list-card-items">
<t-row :gutter="[16, 12]">
<t-col
v-for="product in productList.slice(
pagination.pageSize * (pagination.current - 1),
pagination.pageSize * pagination.current,
)"
:key="product.index"
:lg="4"
:xs="6"
:xl="3"
>
<card
class="list-card-item"
:product="product"
@delete-item="handleDeleteItem"
@manage-product="handleManageProduct"
/>
</t-col>
</t-row>
</div>
<div class="list-card-pagination">
<t-pagination
v-model="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-size-options="[12, 24, 36]"
@page-size-change="onPageSizeChange"
@current-change="onCurrentChange"
/>
</div>
</template>
<div v-else-if="dataLoading" class="list-card-loading">
<t-loading size="large" text="加载数据中..." />
</div>
<div class="list-card-pagination">
<t-pagination
v-model="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-size-options="[12, 24, 36]"
@page-size-change="onPageSizeChange"
@current-change="onCurrentChange"
/>
</div>
</template>
<div v-else-if="dataLoading" class="list-card-loading">
<t-loading size="large" text="加载数据中..." />
<t-dialog
v-model:visible="confirmVisible"
header="是否确认删除产品"
:body="confirmBody"
:on-cancel="onCancel"
@confirm="onConfirmDelete"
/>
</div>
<t-dialog
v-model:visible="confirmVisible"
header="是否确认删除产品"
:body="confirmBody"
:on-cancel="onCancel"
@confirm="onConfirmDelete"
/>
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted } from 'vue';
import TIconSearch from 'tdesign-vue-next/lib/icon/search';
import { SearchIcon } from 'tdesign-icons-vue-next';
import { MessagePlugin } from 'tdesign-vue-next';
import { PREFIX } from '@/config/global';
import Card from './components/Card.vue';
import DialogForm from './components/DialogForm.vue';
import request from '@/utils/request';
@ -78,7 +79,7 @@ const INITIAL_DATA = {
export default defineComponent({
name: 'ListBase',
components: {
TIconSearch,
SearchIcon,
Card,
DialogForm,
},
@ -121,7 +122,6 @@ export default defineComponent({
const formData = ref({ ...INITIAL_DATA });
return {
PREFIX,
pagination,
productList,
dataLoading,
@ -160,47 +160,5 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.list-card {
height: 100%;
&-operation {
display: flex;
justify-content: space-between;
}
&-items {
margin-top: 14px;
margin-bottom: 24px;
}
&-pagination {
padding: 16px;
}
&-loading {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
.@{prefix} {
&-panel {
background-color: @bg-color-container;
padding: @spacer-3;
border-radius: @border-radius;
}
&-search-input {
width: 360px;
}
&-operater-row {
margin-bottom: 16px;
}
}
@import url('./index.less');
</style>

View File

@ -1,56 +1,54 @@
<template>
<div :class="`${PREFIX}-list-table`">
<t-form
ref="form"
:data="formData"
scroll-to-first-error="smooth"
layout="inline"
:class="`${PREFIX}-list-table-form`"
:style="{ height: isExpand ? '' : '32px' }"
:label-width="39"
colon
@reset="onReset"
@submit="onSubmit"
>
<div>
<t-form-item label="合同名称" name="name">
<t-input
v-model="formData.name"
:class="`${PREFIX}-form-item-content`"
type="search"
placeholder="请输入合同名称"
/>
</t-form-item>
<t-form-item label="合同状态" name="status">
<t-select
v-model="formData.status"
:class="`${PREFIX}-form-item-content`"
:options="CONTRACT_STATUS_OPTIONS"
placeholder="请选择合同状态"
/>
</t-form-item>
<t-form-item label="合同编号" name="no">
<t-input v-model="formData.no" :class="`${PREFIX}-form-item-content`" placeholder="请输入合同编号" />
</t-form-item>
<t-form-item label="合同类型" name="type">
<t-select
v-model="formData.type"
:class="`${PREFIX}-form-item-content`"
:options="CONTRACT_TYPE_OPTIONS"
placeholder="请选择合同类型"
/>
</t-form-item>
</div>
<div :class="`${PREFIX}-list-table-form-operation`">
<t-form-item :style="operationStyle">
<div class="list-common-table">
<t-form ref="form" :data="formData" :label-width="80" colon @reset="onReset" @submit="onSubmit">
<t-row>
<t-col :span="8">
<t-row :gutter="[16, 16]">
<t-col :span="4">
<t-form-item label="合同名称" name="name">
<t-input v-model="formData.name" class="form-item-content" type="search" placeholder="请输入合同名称" />
</t-form-item>
</t-col>
<t-col :span="4">
<t-form-item label="合同状态" name="status">
<t-select
v-model="formData.status"
class="form-item-content`"
:options="CONTRACT_STATUS_OPTIONS"
placeholder="请选择合同状态"
/>
</t-form-item>
</t-col>
<t-col :span="4">
<t-form-item label="合同编号" name="no">
<t-input v-model="formData.no" class="form-item-content" placeholder="请输入合同编号" />
</t-form-item>
</t-col>
<template v-if="isExpand">
<t-col :span="4">
<t-form-item label="合同类型" name="type">
<t-select
v-model="formData.type"
class="form-item-content`"
:options="CONTRACT_TYPE_OPTIONS"
placeholder="请选择合同类型"
/>
</t-form-item>
</t-col>
</template>
</t-row>
</t-col>
<t-col :span="4" class="operation-container">
<t-button theme="primary" type="submit"> 查询 </t-button>
<t-button type="reset" variant="base" theme="default"> 重置 </t-button>
<div class="expand" @click="toggleExpand">
{{ isExpand ? '收起' : '展开'
}}<t-icon-chevron-down size="20" :style="{ transform: `rotate(${isExpand ? '180deg' : '0'}` }" />
</div>
</t-form-item>
</div>
<t-button theme="primary" variant="text" class="expand" @click="toggleExpand">
{{ isExpand ? '收起' : '展开' }}
<chevron-down-icon size="20" :style="{ transform: `rotate(${isExpand ? '180deg' : '0'}` }" />
</t-button>
</t-col>
</t-row>
</t-form>
<div class="table-container">
@ -86,8 +84,8 @@
</p>
</template>
<template #op="slotProps">
<a :class="PREFIX + '-link'" @click="rehandleClickOp(slotProps)">管理</a>
<a :class="PREFIX + '-link'" @click="handleClickDelete(slotProps)">删除</a>
<a class="t-button-link" @click="rehandleClickOp(slotProps)">管理</a>
<a class="t-button-link" @click="handleClickDelete(slotProps)">删除</a>
</template>
</t-table>
<t-dialog
@ -102,11 +100,9 @@
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted } from 'vue';
import TIconChevronDown from 'tdesign-vue-next/lib/icon/chevron-down';
import { MessagePlugin } from 'tdesign-vue-next';
import { PREFIX } from '@/config/global';
import { ChevronDownIcon } from 'tdesign-icons-vue-next';
import Trend from '@/components/trend/index.vue';
import { COLUMNS } from './constants';
import request from '@/utils/request';
import { ResDataType } from '@/interface';
@ -118,6 +114,49 @@ import {
CONTRACT_PAYMENT_TYPES,
} from '@/constants';
const COLUMNS = [
{
title: '合同名称',
fixed: 'left',
minWidth: '300',
align: 'left',
ellipsis: true,
colKey: 'name',
},
{ title: '合同状态', colKey: 'status', width: 200, cell: { col: 'status' } },
{
title: '合同编号',
width: 200,
ellipsis: true,
colKey: 'no',
},
{
title: '合同类型',
width: 200,
ellipsis: true,
colKey: 'contractType',
},
{
title: '合同收付类型',
width: 200,
ellipsis: true,
colKey: 'paymentType',
},
{
title: '合同金额 (元)',
width: 200,
ellipsis: true,
colKey: 'amount',
},
{
align: 'left',
fixed: 'right',
width: 200,
colKey: 'op',
title: '操作',
},
];
const searchForm = {
name: '',
no: undefined,
@ -129,7 +168,7 @@ export default defineComponent({
name: 'ListTable',
components: {
Trend,
TIconChevronDown,
ChevronDownIcon,
},
setup() {
const formData = ref({ ...searchForm });
@ -149,13 +188,6 @@ export default defineComponent({
isExpand.value = !isExpand.value;
};
const operationStyle = computed(() => {
const basisStyle = {
position: 'absolute',
};
return isExpand.value ? { ...basisStyle, bottom: '24px' } : { ...basisStyle, top: 0 };
});
const data = ref([]);
const dataLoading = ref(false);
@ -209,7 +241,6 @@ export default defineComponent({
});
return {
PREFIX,
data,
COLUMNS,
CONTRACT_STATUS,
@ -220,7 +251,6 @@ export default defineComponent({
formData,
pagination,
confirmVisible,
operationStyle,
confirmBody,
...tableConfig,
onConfirmDelete,
@ -252,60 +282,36 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
@import '@/style/index';
.@{prefix} {
&-list-table {
background-color: @bg-color-container;
padding: 30px 32px;
border-radius: @border-radius;
<style lang="less">
@import '@/style/variables.less';
.list-common-table {
background-color: @bg-color-container;
padding: 30px 32px;
border-radius: @border-radius;
&-form {
.table-container {
margin-top: 30px;
}
}
.form-item-content {
width: 100%;
}
.operation-container {
display: flex;
justify-content: flex-end;
align-items: center;
.expand {
.t-button__text {
display: flex;
position: relative;
overflow: hidden;
padding-left: 24px;
> div:first-child {
flex: 1;
.t-form__item {
display: inline-flex;
}
}
&-operation {
width: 280px;
position: relative;
> * {
margin: 0 8px;
}
.expand {
margin-left: 16px;
color: @brand-color;
line-height: 22px;
height: 22px;
cursor: pointer;
}
}
align-items: center;
}
.table-container {
margin-top: 30px;
.t-icon {
margin-left: 4px;
transition: transform 0.3s ease;
}
}
&-operater-row {
margin-bottom: 16px;
}
&-form-item-content {
width: 240px;
display: inline-block;
margin-right: 40px;
}
}
.payment-col {

View File

@ -3,7 +3,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import CommonTable from '../components/Table.vue';
import CommonTable from '../components/CommonTable.vue';
export default defineComponent({
name: 'ListBase',
@ -13,7 +13,7 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
@import '@/style/index';
@import '@/style/variables';
.@{prefix} {
&-panel {

View File

@ -1,33 +1,33 @@
<template>
<div :class="`${PREFIX}-tree-panel`">
<div class="table-tree-container">
<div class="list-tree-wrapper">
<div class="list-tree-operator">
<t-input v-model="filterText" placeholder="请输入关键词" @input="onInput">
<template #suffix-icon>
<t-icon-search size="20px" />
<search-icon size="20px" />
</template>
</t-input>
<t-tree :data="TREE_DATA" hover expand-on-click-node :default-expanded="expanded" :filter="filterByText" />
</div>
<div class="list-tree-content">
<list-common-table />
<common-table />
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import TIconSearch from 'tdesign-vue-next/lib/icon/search';
import { SearchIcon } from 'tdesign-icons-vue-next';
import { PREFIX } from '@/config/global';
import { TREE_DATA } from './constants';
import ListCommonTable from '../components/Table.vue';
import CommonTable from '../components/CommonTable.vue';
export default defineComponent({
name: 'ListTree',
components: {
TIconSearch,
ListCommonTable,
SearchIcon,
CommonTable,
},
setup() {
const filterByText = ref();
@ -50,23 +50,12 @@ export default defineComponent({
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.@{prefix} {
&-tree-panel {
background-color: @bg-color-container;
border-radius: @border-radius;
.table-tree-container {
background-color: @bg-color-container;
border-radius: @border-radius;
.t-tree {
margin-top: 36px;
}
}
&-search-input {
width: 360px;
margin-right: 8px;
}
&-operater-row {
margin-bottom: 16px;
.t-tree {
margin-top: 24px;
}
}
@ -84,8 +73,4 @@ export default defineComponent({
border-left: 1px solid @border-level-1-color;
overflow: auto;
}
.selet-base {
width: 88px;
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<header class="login-header">
<logo-full-icon class="logo" />
<div class="operations-container">
<t-button theme="default" shape="square" variant="text" @click="navToGitHub">
<t-icon name="logo-github" class="icon" />
</t-button>
<t-button theme="default" shape="square" variant="text" @click="navToHelper">
<t-icon name="help-circle" class="icon" />
</t-button>
<t-button theme="default" shape="square" variant="text" @click="toggleSettingPanel">
<t-icon name="setting" class="icon" />
</t-button>
</div>
</header>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useStore } from 'vuex';
import LogoFullIcon from '@/assets/assets-logo-full.svg?component';
export default defineComponent({
components: { LogoFullIcon },
setup() {
const store = useStore();
const toggleSettingPanel = () => {
store.commit('setting/toggleSettingPanel', true);
};
const navToGitHub = () => {
window.open('https://github.com/TDesignOteam/tdesign-vue-next-starter');
};
const navToHelper = () => {
window.open('http://tdesign.tencent.com/starter/get-started.html');
};
return {
toggleSettingPanel,
navToGitHub,
navToHelper,
};
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.login-header {
height: 64px;
padding: 0 24px;
display: flex;
justify-content: space-between;
align-items: center;
backdrop-filter: blur(5px);
color: @text-color-primary;
.operations-container {
display: flex;
align-items: center;
.t-button {
margin-left: 16px;
}
.icon {
height: 20px;
width: 20px;
padding: 6px;
box-sizing: content-box;
&:hover {
cursor: pointer;
}
}
}
}
</style>

View File

@ -1,5 +1,4 @@
<template>
<!-- 密码登陆 -->
<t-form
ref="form"
:class="['item-container', `login-${type}`]"
@ -10,7 +9,7 @@
>
<template v-if="type == 'password'">
<t-form-item name="account">
<t-input v-model="formData.account" size="large" placeholder="请输入您的邮箱/手机号">
<t-input v-model="formData.account" size="large" placeholder="请输入您的账号:td">
<template #prefix-icon>
<t-icon name="user" />
</template>
@ -23,7 +22,7 @@
size="large"
:type="showPsw ? 'text' : 'password'"
clearable
placeholder="请输入登录密码"
placeholder="请输入登录密码:main_/dev_"
>
<template #prefix-icon>
<t-icon name="lock-on" />
@ -43,22 +42,14 @@
<!-- 扫码登陆 -->
<template v-else-if="type == 'qrcode'">
<div class="tip-container">
<span class="tip1">请使用微信扫一扫登录</span>
<span class="tip2 refresh">刷新 <t-icon name="refresh" color="#0052D9" /> </span>
<span class="tip">请使用微信扫一扫登录</span>
<span class="refresh">刷新 <t-icon name="refresh" color="#0052D9" /> </span>
</div>
<qrcode-vue value="" :size="192" level="H" />
</template>
<!-- 手机号登陆 -->
<template v-else>
<t-form-item name="phone">
<t-input v-model="formData.phone" size="large" placeholder="请输入您的手机号">
<template #prefix-icon>
<t-icon name="user" />
</template>
</t-input>
</t-form-item>
<t-form-item class="verification-code" name="verifyCode">
<t-input v-model="formData.verifyCode" size="large" placeholder="请输入验证码" />
<t-button variant="outline" :disabled="countDown > 0" @click="handleCounter">
@ -67,7 +58,7 @@
</t-form-item>
</template>
<t-form-item v-if="type !== 'qrcode'">
<t-form-item v-if="type !== 'qrcode'" class="btn-container">
<t-button block size="large" type="submit"> 登录 </t-button>
</t-form-item>
@ -98,8 +89,11 @@ const INITIAL_DATA = {
};
const FORM_RULES = {
phone: [{ required: true, message: '手机号必填', type: 'error' }],
account: [{ required: true, message: '账号必填', type: 'error' }],
phone: [
{ required: true, message: '手机号必填', type: 'error' },
{ telnumber: true, message: '请输入正确的手机号', type: 'warning' },
],
password: [{ required: true, message: '密码必填', type: 'error' }, { validator: passwordValidator }],
verifyCode: [{ required: true, message: '验证码必填', type: 'error' }],
};
@ -121,15 +115,18 @@ export default defineComponent({
const router = useRouter();
const store = useStore();
const onSubmit = ({ validateResult }) => {
const onSubmit = async ({ validateResult }) => {
if (validateResult === true) {
store.commit('user/SET_USER_INFO', formData.value);
MessagePlugin.success('登录成功');
router.push({
path: '/',
});
try {
await store.dispatch('user/login', formData.value);
MessagePlugin.success('登陆成功');
router.push({
path: '/dashboard/base',
});
} catch (e) {
console.log(e);
MessagePlugin.error(e.message);
}
}
};

View File

@ -9,13 +9,7 @@
>
<template v-if="type == 'phone'">
<t-form-item name="phone">
<t-input
v-model="formData.phone"
:maxlength="11"
style="width: 400px"
size="large"
placeholder="请输入您的手机号"
>
<t-input v-model="formData.phone" :maxlength="11" size="large" placeholder="请输入您的手机号">
<template #prefix-icon>
<t-icon name="user" />
</template>
@ -25,19 +19,11 @@
<template v-if="type == 'email'">
<t-form-item name="email">
<t-select
v-model="formData.email"
placeholder="请输入您的邮箱"
filterable
size="large"
:empty="''"
:options="emailOptions"
:on-search="remoteMethod"
>
<template #t-input>
<t-icon name="lock-on" />
<t-input v-model="formData.email" type="text" size="large" placeholder="请输入您的邮箱">
<template #prefix-icon>
<t-icon name="mail" />
</template>
</t-select>
</t-input>
</t-form-item>
</template>
@ -88,7 +74,7 @@
import { defineComponent, ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { useCounter } from '@/utils/hooks';
import { passwordValidator, getEmails } from '../helper';
import { passwordValidator } from '../helper';
const INITIAL_DATA = {
phone: '',
@ -100,7 +86,10 @@ const INITIAL_DATA = {
const FORM_RULES = {
phone: [{ required: true, message: '手机号必填', type: 'error' }],
email: [{ required: true, email: true, message: '邮箱必填', type: 'error' }],
email: [
{ required: true, message: '邮箱必填', type: 'error' },
{ email: true, message: '请输入正确的邮箱', type: 'warning' },
],
password: [{ required: true, message: '密码必填', type: 'error' }, { validator: passwordValidator }],
verifyCode: [{ required: true, message: '验证码必填', type: 'error' }],
};
@ -109,11 +98,6 @@ export default defineComponent({
setup(props, ctx) {
const type = ref('phone');
const emailOptions = ref([]);
const remoteMethod = (search: string) => {
if (search && search.indexOf('@') === -1) {
emailOptions.value = getEmails(search);
}
};
const form = ref();
const formData = ref({ ...INITIAL_DATA });
@ -146,7 +130,6 @@ export default defineComponent({
form,
type,
emailOptions,
remoteMethod,
countDown,
handleCounter,
onSubmit,

View File

@ -8,34 +8,6 @@ export const passwordValidator = (val) => {
return { result: true };
};
export const getEmails = (search) => [
{
value: `${search}@qq.com`,
label: `${search}@qq.com`,
},
{
value: `${search}@gmail.com`,
label: `${search}@gmail.com`,
},
{
value: `${search}@126.com`,
label: `${search}@126.com`,
},
{
value: `${search}@163.com`,
label: `${search}@163.com`,
},
{
value: `${search}@21cn.com`,
label: `${search}@21cn.com`,
},
{
value: `${search}@yahoo.com`,
label: `${search}@yahoo.com`,
},
];
export default {
passwordValidator,
getEmails,
};

View File

@ -1,9 +1,23 @@
@import '@/style/index';
@import '@/style/variables.less';
.light {
&.login-wrapper {
background-color: white;
background-image: url('@/assets/assets-login-bg-white.png');
}
}
.dark {
&.login-wrapper {
background-color: @bg-color-page;
background-image: url('@/assets/assets-login-bg-black.png');
}
}
.login-wrapper {
width: 100%;
height: 100%;
background-image: url('https://tdesign.gtimg.com/pro-template/login-bg-img.jpg');
height: 100vh;
display: flex;
flex-direction: column;
background-size: cover;
background-position: 50%;
position: relative;
@ -11,53 +25,68 @@
.login-container {
position: absolute;
top: 28%;
left: 12%;
top: 22%;
left: 5%;
min-height: 500px;
line-height: 22px;
}
.title-container {
.icon {
width: 290px;
height: 40px;
.title {
font-size: 36px;
line-height: 44px;
color: @text-color-primary;
margin-top: 4px;
&.margin-no {
margin-top: 0;
}
}
.side-title {
margin-top: 24.9px;
.sub-title {
margin-top: 16px;
.tip1,
.tip2 {
.tip {
display: inline-block;
margin-right: 8px;
}
.tip1 {
font-size: 14px;
color: rgb(0 0 0 / 60%);
}
.tip2 {
font-size: 14px;
color: @brand-color-8;
cursor: pointer;
&:first-child {
color: @text-color-secondary;
}
&:last-child {
color: @text-color-primary;
cursor: pointer;
}
}
}
}
.item-container {
width: 400px;
margin-top: 64px;
margin-top: 48px;
&.login-qrcode {
margin-top: 34px;
.tip-container {
width: 192px;
margin-bottom: 16px;
font-size: 14px;
display: flex;
justify-content: space-between;
.tip {
color: @text-color-primary;
}
.refresh {
display: flex;
align-items: center;
color: @brand-color;
.t-icon {
font-size: 14px;
}
&:hover {
cursor: pointer;
@ -76,14 +105,6 @@
}
}
&.register-phone {
margin-top: 64px;
.t-select-popup-reference {
margin: 0;
}
}
.check-container {
display: flex;
align-items: center;
@ -98,7 +119,7 @@
}
span {
color: @brand-color-8;
color: @brand-color;
&:hover {
cursor: pointer;
@ -106,26 +127,6 @@
}
}
.tip-container {
margin-bottom: 16px;
.tip1 {
font-size: 14px;
color: rgb(0 0 0 / 60%);
}
.tip2 {
float: right;
font-size: 14px;
color: @brand-color-8;
.t-icon {
height: 20px;
vertical-align: text-bottom;
}
}
}
.verification-code {
display: flex;
align-items: center;
@ -141,10 +142,14 @@
}
}
}
.btn-container {
margin-top: 48px;
}
}
.switch-container {
margin-top: 66px;
margin-top: 24px;
.tip {
font-size: 14px;
@ -173,7 +178,7 @@
.check-container {
font-size: 14px;
color: rgb(0 0 0 / 60%);
color: @text-color-secondary;
.tip {
float: right;

View File

@ -1,27 +1,33 @@
<template>
<div class="login-wrapper">
<login-header />
<div class="login-container">
<div class="title-container">
<img class="icon" src="https://tdesign.gtimg.com/starter/logo%402x.png" />
<div class="side-title">
<p class="tip1">
{{ type == 'register' ? '没有账号吗?' : '已有账号?' }}
</p>
<p class="tip2" @click="switchType(type == 'register' ? 'login' : 'register')">
<h1 class="title margin-no">登录到</h1>
<h1 class="title">TDesign Starter</h1>
<div class="sub-title">
<p class="tip">{{ type == 'register' ? '已有账号?' : '没有账号吗?' }}</p>
<p class="tip" @click="switchType(type == 'register' ? 'login' : 'register')">
{{ type == 'register' ? '登录' : '注册新账号' }}
</p>
</div>
</div>
<login v-if="type === 'login'" />
<register v-else @registerSuccess="switchType('login')" />
<register v-else @register-success="switchType('login')" />
<tdesign-setting />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { defineComponent, ref, computed } from 'vue';
import { useStore } from 'vuex';
import Login from './components/Login.vue';
import Register from './components/Register.vue';
import LoginHeader from './components/Header.vue';
import TdesignSetting from '@/layouts/setting.vue';
/** 高级详情 */
export default defineComponent({
@ -29,17 +35,24 @@ export default defineComponent({
components: {
Login,
Register,
LoginHeader,
TdesignSetting,
},
setup() {
const type = ref('login');
const store = useStore();
const switchType = (val: string) => {
type.value = val;
};
const mode = computed(() => {
return store.state.setting.mode;
});
return {
type,
switchType,
mode,
};
},
});

View File

@ -2,7 +2,7 @@
<result
page-header="403"
tip="抱歉您无权限访问此页面企业微信联系创建者xiaolaoshi"
bg-url="https://tdesign.gtimg.com/pro-template/result-page/403.png"
bg-url="https://tdesign.gtimg.com/starter/result-page/403.png"
>
<t-button @click="() => $router.push('/')">返回首页</t-button>
</result>

View File

@ -2,7 +2,7 @@
<result
page-header="404"
tip="抱歉,您访问的页面不存在"
bg-url="https://tdesign.gtimg.com/pro-template/result-page/404.png"
bg-url="https://tdesign.gtimg.com/starter/result-page/404.png"
>
<t-button @click="() => $router.push('/')">返回首页</t-button>
</result>

View File

@ -1,9 +1,5 @@
<template>
<result
page-header="500"
tip="抱歉,服务器出错啦"
bg-url="https://tdesign.gtimg.com/pro-template/result-page/500.png"
>
<result page-header="500" tip="抱歉,服务器出错啦" bg-url="https://tdesign.gtimg.com/starter/result-page/500.png">
<t-button @click="() => $router.push('/')">返回首页</t-button>
</result>
</template>

View File

@ -2,7 +2,7 @@
<result
page-header="浏览器不兼容"
tip="抱歉,您正在使用的浏览器版本过低,无法打开当前网页"
bg-url="https://tdesign.gtimg.com/pro-template/result-page/browser-incompatible.png"
bg-url="https://tdesign.gtimg.com/starter/result-page/browser-incompatible.png"
>
<div class="result-slot-container">
<t-button class="result-button" @click="() => $router.push('/')">返回首页</t-button>

View File

@ -1,24 +1,20 @@
<template>
<card>
<div class="result-success">
<t-icon class="result-success-icon" name="error-circle" />
<div class="result-success-title">项目创建失败</div>
<div class="result-success-describe">企业微信联系检查创建者权限或返回修改</div>
<div>
<t-button theme="default" @click="() => $router.push('/form/base')">返回首页</t-button>
<t-button @click="() => $router.push('/form/base')"> 返回修改 </t-button>
</div>
<div class="result-success">
<t-icon class="result-success-icon" name="error-circle" />
<div class="result-success-title">项目创建失败</div>
<div class="result-success-describe">企业微信联系检查创建者权限或返回修改</div>
<div>
<t-button theme="default" @click="() => $router.push('/form/base')">返回首页</t-button>
<t-button @click="() => $router.push('/form/base')"> 返回修改 </t-button>
</div>
</card>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Card from '@/components/card/index.vue';
export default defineComponent({
name: 'ResultSuccess',
components: { Card },
});
</script>
@ -42,6 +38,7 @@ export default defineComponent({
color: @text-color-primary;
text-align: center;
line-height: 22px;
font-weight: 500;
}
&-describe {

View File

@ -2,7 +2,7 @@
<result
page-header="网络异常"
tip="网络异常,请稍后再试"
bg-url="https://tdesign.gtimg.com/pro-template/result-page/network-error.png"
bg-url="https://tdesign.gtimg.com/starter/result-page/network-error.png"
>
<div>
<t-button theme="default" @click="() => $router.push('/')">返回首页</t-button>

View File

@ -1,25 +1,20 @@
<template>
<card>
<div class="result-success">
<t-icon class="result-success-icon" name="check-circle-filled" />
<div class="result-success-title">项目已创建成功</div>
<div class="result-success-describe">可以联系负责人分发应用</div>
<div>
<t-button theme="default" @click="() => $router.push('/detail/advanced')"> 查看进度 </t-button>
<t-button @click="() => $router.push('/form/base')"> 再次创建 </t-button>
</div>
<div class="result-success">
<t-icon class="result-success-icon" name="check-circle" />
<div class="result-success-title">创建成功</div>
<div class="result-success-describe">可以联系负责人分发应用</div>
<div>
<t-button theme="default" @click="() => $router.push('/detail/advanced')"> 查看进度 </t-button>
<t-button @click="() => $router.push('/form/base')"> 再次创建 </t-button>
</div>
</card>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Card from '@/components/card/index.vue';
export default defineComponent({
name: 'ResultSuccess',
components: { Card },
});
</script>
@ -44,6 +39,7 @@ export default defineComponent({
color: @text-color-primary;
text-align: center;
line-height: 22px;
font-weight: 500;
}
&-describe {

View File

@ -40,23 +40,23 @@ export const USER_INFO_LIST = [
export const TEAM_MEMBERS = [
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar5.png',
title: 'Lovellzhang 张庆霖',
avatar: 'https://avatars.githubusercontent.com/Wen1kang',
title: 'Lovellzhong 钟某某',
description: '直客销售 港澳拓展组员工',
},
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar1.png',
title: 'Jiajingwang 王佳静',
avatar: 'https://avatars.githubusercontent.com/pengYYYYY',
title: 'Jiajingwang 彭某某',
description: '前端开发 前台研发组员工',
},
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar5.png',
title: 'cruisezhang 张超',
avatar: 'https://avatars.githubusercontent.com/u/24469546?s=96&v=4',
title: 'cruisezhang 林某某',
description: '技术产品 产品组员工',
},
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar3.png',
title: 'Lovellzhang 张庆霖',
avatar: 'https://avatars.githubusercontent.com/u/88708072?s=96&v=4',
title: 'Lovellzhang 商某某',
description: '产品运营 港澳拓展组员工',
},
];

View File

@ -1,279 +1,166 @@
@import '@/style/index';
@import '@/style/variables.less';
.user-panel {
@media (max-width: @screen-sm-max) {
.card-padding-no {
padding-left: 0;
padding-right: 0;
}
.user-right {
min-width: 100%;
}
.user-left-greeting {
padding: 28px 32px;
line-height: 28px;
font-size: 20px;
background: @bg-color-container;
color: @text-color-primary;
text-align: left;
border-radius: @border-radius;
display: flex;
justify-content: space-between;
align-items: center;
.regular {
margin-right: 15px;
font-size: 14px;
}
@media (min-width: @screen-md-min) {
.logo {
width: 180px;
}
}
.user-right {
min-width: 33.33%;
}
.user-info-list {
margin-top: 16px;
.content {
width: 90%;
}
.user-right {
transition: width .3s linear;
.contract {
width: 340px;
height: 88px;
border-radius: @border-radius;
margin: 8px 0;
&-greeting {
margin-bottom: 16px;
background: #fff;
border-radius: @border-radius;
}
&-trend {
margin-bottom: 16px;
background: #fff;
border-radius: @border-radius;
}
&-todo {
margin-bottom: 16px;
background: #fff;
border-radius: @border-radius;
}
}
.head-bar {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
.title {
&-title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 24px;
font-family: PingFangSC;
font-size: 20px;
color: @text-color-primary;
text-align: left;
margin: 20px 0 6px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: rgb(0 0 0 / 40%);
}
&-detail {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 40px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: rgb(0 0 0 / 90%);
}
}
.contract:last-child {
margin-bottom: 0;
}
}
.user-intro {
padding: 48px;
background: @brand-color;
border-radius: @border-radius;
color: @text-color-primary;
.name {
line-height: 37px;
font-size: 20px;
margin-top: 36px;
color: #fff;
}
.position {
line-height: 24px;
font-size: 14px;
margin-top: 8px;
color: #fff;
}
.user-info {
line-height: 24px;
font-size: 14px;
color: @text-color-primary;
.hiredate,
.del,
.mail {
display: flex;
}
.t-icon {
font-size: 16px;
color: rgba(0, 0, 0, .9);
height: 24px;
margin-right: 8px;
}
.more {
display: flex;
align-items: center;
justify-content: center;
line-height: 22px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: @text-color-primary;
cursor: pointer;
.t-icon {
vertical-align: bottom;
font-size: 20px;
}
&:hover {
color: @brand-color-8;
}
}
}
.user-right-panel {
.user-top {
padding: 48px;
margin: 0 0 16px 16px;
background: @brand-color;
border-radius: @border-radius;
.account {
text-align: left;
margin-bottom: 0;
color: @text-color-primary;
.img {
display: block;
width: 90px;
height: 90px;
border-radius: @border-radius-50;
border: 1px solid @brand-color;
background: #ebedf1;
}
.name {
line-height: 37px;
font-family: PingFangSC-Semibold;
font-size: 20px;
margin-top: 36px;
color: #fff;
}
.position {
line-height: 24px;
font-family: PingFangSC-Regular;
font-size: 14px;
margin-top: 8px;
color: #fff;
}
}
.user-info {
line-height: 24px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: @text-color-primary;
.hiredate,
.del,
.mail {
display: flex;
}
.t-icon {
height: 24px;
margin-right: 8px;
}
.del {
margin: 16px 0;
}
}
}
.user-middle {
padding: 28px 32px 5px 32px;
margin: 0 0 16px 16px;
background: #fff;
border-radius: @border-radius;
.t-list {
margin: 20px 0;
}
.t-list-item {
padding: 15px 0;
.t-list-item__meta-avatar {
height: 50px;
width: 50px;
margin: 0 24px 0 0;
}
.t-list-item__meta-description {
display: inline-block;
color: rgba(0, 0, 0, .4);
font-size: 14px;
}
}
}
.user-bottom {
padding: 24px 24px 5px 24px;
margin: 0 0 16px 16px;
background: #fff;
border-radius: @border-radius;
.content {
width: 70%;
margin: 24px 0 12px 0;
}
.user-right-logo {
width: 48px;
}
}
}
.user-left-panel {
.user-top {
.user-left-greeting {
padding: 28px 32px;
line-height: 28px;
font-family: PingFangSC-Semibold;
font-size: 20px;
color: @text-color-primary;
text-align: left;
background-color: #fff;
border-radius: @border-radius;
.regular {
margin: 0 0 0 15px;
vertical-align: bottom;
font-family: PingFangSC-Semibold;
font-size: 14px;
}
}
.user-left-logo {
float: right;
width: 180px;
}
.user-right-info {
margin: 16px 0;
padding: 32px;
background-color: #fff;
border-radius: @border-radius;
.content {
width: 90%;
}
.contract {
width: 340px;
height: 88px;
border-radius: @border-radius;
margin: 8px 0;
&-title {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 24px;
margin: 20px 0 6px 0;
font-family: PingFangSC-Regular;
font-size: 14px;
color: rgba(0, 0, 0, .4);
}
&-detail {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 40px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: rgba(0, 0, 0, .9);
}
}
.contract:last-child {
margin-bottom: 0;
}
}
}
.user-bottom {
margin: 0 0 16px 0;
padding: 28px 32px;
background: #fff;
border-radius: @border-radius;
.user-bottom-container {
margin-top: 15px;
.unit {
font-size: 14px;
font-family: PingFangSC-Regular;
color: rgba(0, 0, 0, .4);
}
.time-picker {
float: right;
width: 250px;
}
}
.del {
margin: 16px 0;
}
}
}
.product-container {
margin-top: 16px;
border-radius: @border-radius;
.content {
width: 70%;
margin: 24px 0 12px;
}
.logo {
width: 48px;
}
}
.content-container {
margin-top: 16px;
padding: 28px 32px;
background: #fff;
border-radius: @border-radius;
.user-bottom-container {
margin-top: 15px;
.unit {
font-size: 14px;
font-family: PingFangSC-Regular;
color: rgb(0 0 0 / 40%);
}
.time-picker {
float: right;
width: 250px;
}
}
}
.user-team {
margin-top: 16px;
.t-list-item {
padding: 15px 0;
.t-list-item__meta-avatar {
height: 50px;
width: 50px;
margin: 0 24px 0 0;
}
.t-list-item__meta-description {
display: inline-block;
color: rgb(0 0 0 / 40%);
font-size: 14px;
}
}
}

View File

@ -1,98 +1,93 @@
<template>
<div>
<div class="user-panel">
<t-row>
<t-col class="user-left-panel" :flex="3">
<div class="user-top">
<div class="user-left-greeting">
HiImage
<span class="regular"> 下午好今天是你加入鹅厂的第 100 </span>
<img src="../../assets/tencent-logo.png" class="user-left-logo" />
</div>
<div class="user-right-info">
<div class="head-bar">
<div class="title">个人信息</div>
<t-icon name="edit" size="18" />
</div>
<t-row class="content" justify="space-between">
<t-col v-for="(item, index) in USER_INFO_LIST" :key="index" class="contract" :span="item.span || 3">
<div class="contract-title">
{{ item.title }}
</div>
<div class="contract-detail">
{{ item.content }}
</div>
</t-col>
</t-row>
</div>
</div>
<t-row :gutter="16">
<t-col :flex="3">
<div class="user-left-greeting">
<div>
HiImage
<span class="regular"> 下午好今天是你加入鹅厂的第 100 </span>
</div>
<img src="@/assets/assets-tencent-logo.png" class="logo" />
</div>
<div class="user-bottom">
<div class="t-demo-tabs">
<t-tabs default-value="second">
<t-tab-panel value="first" label="内容列表">
<p style="padding: 25px">内容列表</p>
</t-tab-panel>
<t-tab-panel value="second" label="内容列表">
<div class="user-bottom-container">
<div class="head-bar">
<div class="title">主页访问数据<span class="unit"></span></div>
<t-date-picker
class="time-picker"
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"
range
@change="onLineChange"
/>
</div>
<div id="lineContainer" style="width: 100%; height: 330px" />
</div>
</t-tab-panel>
<t-tab-panel value="third" label="内容列表">
<p style="padding: 25px">内容列表</p>
</t-tab-panel>
</t-tabs>
<card class="user-info-list" size="small" title="个人信息">
<template #option>
<t-button theme="default" shape="square" variant="text">
<t-icon name="edit" size="18" />
</t-button>
</template>
<t-row class="content" justify="space-between">
<t-col v-for="(item, index) in USER_INFO_LIST" :key="index" class="contract" :span="item.span || 3">
<div class="contract-title">
{{ item.title }}
</div>
</div>
</t-col>
<div class="contract-detail">
{{ item.content }}
</div>
</t-col>
</t-row>
</card>
<t-col class="user-right-panel" :flex="1">
<div class="user-top">
<div class="account">
<img class="img" src="https://tdesign.gtimg.com/pro-template/personal/avatar4.png" />
<div class="name">My Account</div>
<div class="position">XXG 港澳业务拓展组员工 直客销售</div>
</div>
</div>
<card class="content-container">
<t-tabs default-value="second">
<t-tab-panel value="first" label="内容列表">
<p>内容列表</p>
</t-tab-panel>
<t-tab-panel value="second" label="内容列表">
<card class="card-padding-no" title="主页访问数据" describe="(次)">
<template #options>
<t-date-picker
class="time-picker"
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"
range
@change="onLineChange"
/>
</template>
<div id="lineContainer" style="width: 100%; height: 330px" />
</card>
</t-tab-panel>
<t-tab-panel value="third" label="内容列表">
<p>内容列表</p>
</t-tab-panel>
</t-tabs>
</card>
</t-col>
<div class="user-middle">
<div class="head-bar">
<div class="title">团队成员</div>
<t-icon name="edit" size="18" />
</div>
<t-list :split="false">
<t-list-item v-for="(item, index) in TEAM_MEMBERS" :key="index">
<t-list-item-meta :image="item.avatar" :title="item.title" :description="item.description" />
</t-list-item>
</t-list>
</div>
<t-col :flex="1">
<card class="user-intro">
<t-avatar size="90px">T</t-avatar>
<div class="name">My Account</div>
<div class="position">XXG 港澳业务拓展组员工 直客销售</div>
</card>
<div class="user-bottom">
<div class="head-bar">
<div class="title">服务产品</div>
<t-icon name="edit" size="18" />
</div>
<t-row class="content" justify="space-between">
<t-col v-for="(item, index) in PRODUCT_LIST" :key="index" class="contract" :span="4">
<img :src="item.logo" class="user-right-logo" />
</t-col>
</t-row>
</div>
</t-col>
</t-row>
</div>
</div>
<card title="团队成员" class="user-team" size="small">
<template #option>
<t-button theme="default" shape="square" variant="text">
<t-icon name="edit" size="18" />
</t-button>
</template>
<t-list :split="false">
<t-list-item v-for="(item, index) in TEAM_MEMBERS" :key="index">
<t-list-item-meta :image="item.avatar" :title="item.title" :description="item.description" />
</t-list-item>
</t-list>
</card>
<card title="服务产品" class="product-container" size="small">
<template #option>
<t-button theme="default" shape="square" variant="text">
<t-icon name="edit" size="18" />
</t-button>
</template>
<t-row class="content" :getters="16">
<t-col v-for="(item, index) in PRODUCT_LIST" :key="index" :span="4">
<img :src="item.logo" class="logo" />
</t-col>
</t-row>
</card>
</t-col>
</t-row>
</template>
<script lang="ts">
import { defineComponent, onMounted, watch } from 'vue';
@ -104,37 +99,41 @@ import { LineChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { LAST_7_DAYS } from '@/utils/date';
import { useChart } from '@/utils/hooks';
// import { useChart } from '@/utils/hooks';
import { USER_INFO_LIST, TEAM_MEMBERS, PRODUCT_LIST } from './constants';
import { changeChartsTheme, getFolderLineDataSet } from '@/pages/dashboard/base/index';
// import { changeChartsTheme, getFolderLineDataSet } from '@/pages/dashboard/base/index';
import Card from '@/components/card/index.vue';
echarts.use([GridComponent, TooltipComponent, LineChart, CanvasRenderer, LegendComponent]);
export default defineComponent({
components: {
Card,
},
setup() {
const lineChart = useChart('lineContainer');
// const lineChart = useChart('lineContainer');
const onLineChange = (value: string[]) => {
lineChart.value.setOption(getFolderLineDataSet(value));
const onLineChange = () => {
// lineChart.value.setOption(getFolderLineDataSet(value));
};
onMounted(() => {
lineChart.value.setOption({
grid: {
x: 30, // 80px
y: 30, // 60px
x2: 10, // 80px
y2: 30, // 60px
},
...getFolderLineDataSet(),
});
// lineChart.value.setOption({
// grid: {
// x: 30, // 80px
// y: 30, // 60px
// x2: 10, // 80px
// y2: 30, // 60px
// },
// ...getFolderLineDataSet(),
// });
});
const store = useStore();
watch(
() => store.state.setting.brandTheme,
() => {
changeChartsTheme([lineChart.value]);
// changeChartsTheme([lineChart.value]);
},
);

56
src/permission.ts Normal file
View File

@ -0,0 +1,56 @@
import { MessagePlugin } from 'tdesign-vue-next';
import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css'; // progress bar style
import store from '@/store';
import router from '@/router';
NProgress.configure({ showSpinner: false });
const whiteListRouters = store.getters['permission/whiteListRouters'];
router.beforeEach(async (to, from, next) => {
NProgress.start();
const token = store.getters['user/token'];
if (token) {
if (to.path === '/login') {
store.dispatch('user/logout');
store.dispatch('permission/restore');
next();
return;
}
const roles = store.getters['user/roles'];
if (roles && roles.length > 0) {
next();
} else {
try {
await store.dispatch('user/getUserInfo');
await store.dispatch('permission/initRoutes', store.getters['user/roles']);
next({ ...to });
} catch (error) {
console.log(error);
MessagePlugin.error(error);
await store.commit('user/removeToken');
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
} else {
/* white list router */
if (whiteListRouters.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
}
NProgress.done();
}
});
router.afterEach(() => {
NProgress.done();
});

View File

@ -1,38 +1,35 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import routeConfig from '@/config/routes';
const layoutModules = import.meta.glob('../layouts/*');
const pagesModules = import.meta.glob('../pages/**/*.vue');
const fristPagesModules = import.meta.glob('../pages/*.vue');
const modules = { ...layoutModules, ...fristPagesModules, ...pagesModules };
import baseRouters from './modules/base';
import componentsRouters from './modules/components';
import othersRouters from './modules/others';
const getMenuRoutes = (list) => {
if (!list) {
return [];
}
return list.map((item) => {
const { path = '', component, meta = { title: item.title }, redirect = '' } = item;
return {
path,
component: modules[component],
children: getMenuRoutes(item.children),
meta,
redirect,
};
});
};
// 存放动态路由
export const asyncRouterList: Array<RouteRecordRaw> = [...baseRouters, ...componentsRouters, ...othersRouters];
const routes: Array<RouteRecordRaw> = [
...getMenuRoutes(routeConfig),
// 存放固定的路由
const defaultRouterList: Array<RouteRecordRaw> = [
{
path: '',
redirect: '/dashboard/base',
path: '/login',
name: 'login',
component: () => import('@/pages/login/index.vue'),
},
{
path: '/',
redirect: '/login',
component: () => import('@/layouts/blank.vue'),
},
];
export const page404 = {
path: '/:w+',
name: '404Page',
redirect: '/result/404',
};
const router = createRouter({
history: createWebHashHistory(),
routes,
routes: defaultRouterList,
scrollBehavior() {
return {
el: '#app',
@ -41,4 +38,5 @@ const router = createRouter({
};
},
});
export default router;

View File

@ -0,0 +1,25 @@
import Layout from '@/layouts';
export default [
{
path: '/dashboard',
component: Layout,
redirect: '/dashboard/base',
name: 'dashboard',
meta: { title: '仪表盘', icon: 'dashboard' },
children: [
{
path: 'base',
name: 'dashboardBase',
component: () => import('@/pages/dashboard/base/index.vue'),
meta: { title: '概览仪表盘' },
},
{
path: 'detail',
name: 'dashboardDetail',
component: () => import('@/pages/dashboard/detail/index.vue'),
meta: { title: '统计报表' },
},
],
},
];

View File

@ -0,0 +1,151 @@
import Layout from '@/layouts';
export default [
{
path: '/list',
name: 'list',
component: Layout,
redirect: '/list/base',
meta: { title: '列表页', icon: 'view-module' },
children: [
{
path: 'base',
name: 'listBase',
component: () => import('@/pages/list/base/index.vue'),
meta: { title: '基础列表页' },
},
{
path: 'card',
name: 'listCard',
component: () => import('@/pages/list/card/index.vue'),
meta: { title: '卡片列表页' },
},
{
path: 'filter',
name: 'listFilter',
component: () => import('@/pages/list/filter/index.vue'),
meta: { title: '筛选列表页' },
},
{
path: 'tree',
name: 'listTree',
component: () => import('@/pages/list/tree/index.vue'),
meta: { title: '树状筛选列表页' },
},
],
},
{
path: '/form',
name: 'form',
component: Layout,
redirect: '/form/base',
meta: { title: '表单页', icon: 'queue' },
children: [
{
path: 'base',
name: 'formBase',
component: () => import('@/pages/form/base/index.vue'),
meta: { title: '基础表单页' },
},
{
path: 'step',
name: 'formStep',
component: () => import('@/pages/form/step/index.vue'),
meta: { title: '分步表单页' },
},
],
},
{
path: '/detail',
name: 'detail',
component: Layout,
redirect: '/detail/base',
meta: { title: '详情页', icon: 'layers' },
children: [
{
path: 'base',
name: 'detailBase',
component: () => import('@/pages/detail/base/index.vue'),
meta: { title: '基础详情页' },
},
{
path: 'advanced',
name: 'detailAdvanced',
component: () => import('@/pages/detail/advanced/index.vue'),
meta: { title: '多卡片详情页' },
},
{
path: 'deploy',
name: 'detailDeploy',
component: () => import('@/pages/detail/deploy/index.vue'),
meta: { title: '数据详情页' },
},
{
path: 'secondary',
name: 'detailDeploy',
component: () => import('@/pages/detail/secondary/index.vue'),
meta: { title: '二级详情页' },
},
],
},
{
path: '/result',
name: 'result',
component: Layout,
redirect: '/result/success',
meta: { title: '结果页', icon: 'check-circle' },
children: [
{
path: 'success',
name: 'resultSuccess',
component: () => import('@/pages/result/success/index.vue'),
meta: { title: '成功页' },
},
{
path: 'fail',
name: 'resultFail',
component: () => import('@/pages/result/fail/index.vue'),
meta: { title: '失败页' },
},
],
},
{
path: '/warning',
name: 'warning',
component: Layout,
redirect: '/warning/success',
meta: { title: '异常页', icon: 'error-circle' },
children: [
{
path: 'network-error',
name: 'warningNetworkError',
component: () => import('@/pages/result/network-error/index.vue'),
meta: { title: '网络异常' },
},
{
path: '403',
name: 'warning403',
component: () => import('@/pages/result/403/index.vue'),
meta: { title: '无权限' },
},
{
path: '404',
name: 'warning404',
component: () => import('@/pages/result/404/index.vue'),
meta: { title: '访问页面不存在页' },
},
{
path: '500',
name: 'warning500',
component: () => import('@/pages/result/500/index.vue'),
meta: { title: '服务器出错页' },
},
{
path: 'browser-incompatible',
name: 'warningBrowserIncompatible',
component: () => import('@/pages/result/browser-incompatible/index.vue'),
meta: { title: '浏览器不兼容页' },
},
],
},
];

View File

@ -0,0 +1,33 @@
import Layout from '@/layouts';
export default [
{
path: '/user',
name: 'user',
component: Layout,
redirect: '/user/index',
meta: { title: '个人页', icon: 'user-circle' },
children: [
{
path: 'index',
name: 'userIndex',
component: () => import('@/pages/user/index.vue'),
meta: { title: '个人中心' },
},
],
},
{
path: '/loginRedirect',
name: 'loginRedirect',
meta: { title: '登录页', icon: 'chevron-right-rectangle' },
component: () => import('@/layouts/blank.vue'),
children: [
{
path: 'index',
redirect: '/login',
component: () => import('@/layouts/blank.vue'),
meta: { title: '登陆页' },
},
],
},
];

View File

@ -2,12 +2,14 @@ import { createStore } from 'vuex';
import user from './modules/user';
import notification from './modules/notification';
import setting from './modules/setting';
import permission from './modules/permission';
export const store = createStore({
modules: {
user,
setting,
notification,
permission,
},
});

View File

@ -1,5 +1,5 @@
// 定义的state初始值
import { NotificationItem } from '../interface';
import { NotificationItem } from '@/interface';
const state = {
msgData: [
@ -10,7 +10,7 @@ const state = {
status: true,
collected: false,
date: '2021-01-01 08:00',
priorty: 'high',
quality: 'high',
},
{
id: '124',
@ -19,7 +19,7 @@ const state = {
status: true,
collected: false,
date: '2021-01-01 08:00',
priorty: 'low',
quality: 'low',
},
{
id: '125',
@ -28,7 +28,7 @@ const state = {
status: true,
collected: false,
date: '2021-01-01 08:00',
priorty: 'middle',
quality: 'middle',
},
{
id: '126',
@ -37,7 +37,7 @@ const state = {
status: true,
collected: false,
date: '2021-01-01 08:00',
priorty: 'low',
quality: 'low',
},
{
id: '127',
@ -46,7 +46,7 @@ const state = {
status: true,
collected: false,
date: '2021-01-01 08:00',
priorty: 'low',
quality: 'low',
},
{
id: '128',
@ -55,7 +55,7 @@ const state = {
status: true,
collected: false,
date: '2021-01-01 08:00',
priorty: 'low',
quality: 'low',
},
],
};

View File

@ -0,0 +1,74 @@
import router, { asyncRouterList, page404 } from '@/router';
function filterPermissionsRouters(routes, roles) {
const res = [];
routes.forEach((route) => {
const children = [];
route.children?.forEach((childRouter) => {
const roleCode = childRouter.meta?.roleCode || childRouter.name;
if (roles.indexOf(roleCode) !== -1) {
children.push(childRouter);
}
});
if (children.length > 0) {
route.children = children;
res.push(route);
}
});
return res;
}
const state = {
whiteListRouters: ['/login'],
routers: [],
};
const mutations = {
setRouters: (state, routers) => {
state.routers = routers;
},
};
const getters = {
routers: (state) => {
return state.routers;
},
whiteListRouters: (state) => {
return state.whiteListRouters;
},
};
const actions = {
async initRoutes({ commit }, roles) {
let accessedRouters;
// special token
if (roles.includes('ALL_ROUTERS')) {
accessedRouters = asyncRouterList;
} else {
accessedRouters = filterPermissionsRouters(asyncRouterList, roles);
}
commit('setRouters', accessedRouters);
// register routers
state.routers.concat(page404).forEach((item) => {
router.addRoute(item);
});
},
async restore({ commit, state }) {
// remove routers
state.routers.concat(page404).forEach((item) => {
router.removeRoute(item.name);
});
commit('setRouters', []);
},
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

View File

@ -1,5 +1,4 @@
import STYLE_CONFIG from '@/config/style';
import MENU_CONFIG from '@/config/routes';
// 定义的state初始值
const state = {
@ -37,46 +36,38 @@ const getters = {
showSidebar: (state) => state.layout !== 'top',
showSidebarLogo: (state) => state.layout === 'side',
showHeaderLogo: (state) => state.layout !== 'side',
headerMenu: (state) => {
if (state.layout === 'mix') {
if (state.splitMenu) {
return MENU_CONFIG.map((menu) => ({
...menu,
children: [],
}));
}
return [];
}
return MENU_CONFIG;
},
sideMenu: (state, getters, rootState) => {
if (state.layout === 'mix' && state.splitMenu) {
let index;
for (index = 0; index < MENU_CONFIG.length; index++) {
const item = MENU_CONFIG[index];
if (item.children && item.children.length > 0) {
if (rootState.route.path.indexOf(item.path) === 0) {
return item.children.map((menuRouter) => ({ ...menuRouter, path: `${item.path}/${menuRouter.path}` }));
}
}
}
}
return MENU_CONFIG;
},
showFooter: (state) => state.showFooter,
showSettingBtn: (state) => !state.showHeader,
mode: (state) => {
if (state.mode === 'auto') {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
return 'dark';
}
return 'light';
}
return state.mode;
},
};
const actions = {
async changeTheme({ commit, dispatch }, payload) {
console.log(payload);
dispatch('changeMode', payload);
dispatch('changeBrandTheme', payload);
commit('update', payload);
},
changeMode({ state }, payload) {
if (payload.mode !== state.mode) {
document.documentElement.setAttribute('theme-mode', payload.mode === 'dark' ? 'dark' : '');
let theme = payload.mode;
if (payload.mode === 'auto') {
const media = window.matchMedia('(prefers-color-scheme:dark)');
if (media.matches) {
theme = 'dark';
} else {
theme = 'light';
}
}
if (theme !== state.mode) {
document.documentElement.setAttribute('theme-mode', theme === 'dark' ? 'dark' : '');
}
},
changeBrandTheme({ state }, payload) {

View File

@ -1,42 +1,94 @@
import request from '@/utils/request';
import { TOKEN_NAME } from '@/config/global';
const InitUserInfo = {
roles: [],
};
// 定义的state初始值
const state = {
loginName: '',
deptNameString: '',
token: localStorage.getItem(TOKEN_NAME),
userInfo: InitUserInfo,
};
const mutations = {
SET_USER_INFO(state, userInfo) {
state.loginName = userInfo.EngName;
state.deptNameString = userInfo.DeptNameString;
setToken(state, token) {
localStorage.setItem(TOKEN_NAME, token);
state.token = token;
},
removeToken(state) {
localStorage.removeItem(TOKEN_NAME);
state.token = '';
},
setUserInfo(state, userInfo) {
state.userInfo = userInfo;
},
};
const getters = {
token: (state) => {
return state.token;
},
roles: (state) => {
return state.userInfo?.roles;
},
};
const actions = {
async getUserInfo(context) {
try {
console.log('当前编译环境');
console.log(process.env.NODE_ENV);
if (process.env.NODE_ENV === 'development') {
context.commit('SET_USER_INFO', {
EngName: 'user_test',
DeptNameString: '虚拟部门test',
});
} else {
request({
method: 'get',
url: '/ts:auth/tauth/info.ashx',
baseURL: '',
}).then((res) => {
context.commit('SET_USER_INFO', res.data);
});
async login({ commit }, userInfo) {
const mockLogin = async (userInfo) => {
const { account, password } = userInfo;
if (account !== 'td') {
return {
code: 401,
message: '账号不存在',
};
}
} catch (err) {
console.log(`智能网关获取用户信息错误:${err.message}`);
if (['main_', 'dev_'].indexOf(password) === -1) {
return {
code: 401,
message: '密码错误',
};
}
const token = {
main_: 'main_token',
dev_: 'dev_token',
}[password];
return {
code: 200,
message: '登陆成功',
data: token,
};
};
const res = await mockLogin(userInfo);
if (res.code === 200) {
commit('setToken', res.data);
} else {
throw res;
}
},
async getUserInfo({ commit, state }) {
const mockRemoteUserInfo = async (token) => {
if (token === 'main_token') {
return {
name: 'td_main',
roles: ['ALL_ROUTERS'],
};
}
return {
name: 'td_dev',
roles: ['userIndex', 'dashboardBase', 'login'],
};
};
const res = await mockRemoteUserInfo(state.token);
commit('setUserInfo', res);
},
async logout({ commit }) {
commit('removeToken');
commit('setUserInfo', InitUserInfo);
},
};
export default {
@ -44,4 +96,5 @@ export default {
state,
mutations,
actions,
getters,
};

View File

@ -1,13 +1,5 @@
@import './common.less';
@import './variables.less';
@import './rewrite.less';
@import './font-family.less';
@import './side-nav.less';
@import './theme/index.less';
body {
@ -22,7 +14,7 @@ body {
pre {
font-family: @font-family;
}
ul,
dl,
li,
@ -32,7 +24,7 @@ dt {
padding: 0;
list-style: none;
}
figure,
h1,
h2,
@ -43,129 +35,43 @@ h6,
p {
margin: 0;
}
* {
box-sizing: border-box;
}
.@{prefix}-text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.@{prefix}-text-tip {
font-size: 12px;
color: @text-color-placeholder;
}
.@{prefix}-pic {
background-position: center;
background-repeat: no-repeat;
background-size: 100%;
}
.@{prefix}-main-link {
color: @text-color-primary;
text-decoration: none;
cursor: pointer;
&:hover {
color: @text-color-primary;
}
&:active {
color: @text-color-primary;
}
&--active {
color: #000;
}
&:focus {
text-decoration: none;
}
}
.@{prefix}-link {
color: @brand-color;
.t-button-link {
color: @brand-color;
text-decoration: none;
margin-right: @spacer-3;
cursor: pointer;
transition: color @anim-duration-base @anim-time-fn-easing;
&:hover {
color: @brand-color;
color: @brand-color-hover;
}
&:active {
color: @brand-color;
color: @brand-color-active;
}
&--active {
color: @brand-color;
color: @brand-color-active;
}
&:focus {
text-decoration: none;
}
}
// 布局元素调整
.@{prefix}-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
}
.@{prefix}-sideNav-layout {
&-relative {
height: 100%;
&:last-child {
margin-right: 0;
}
}
.@{prefix}-content-layout {
margin: @spacer-3;
.t-button + .t-button {
margin-left: @spacer;
}
.@{prefix}-footer-layout {
padding: 0;
margin-bottom: @spacer-2;
.container-base-margin-top {
margin-top: 16px;
}
.@{prefix}-footer {
color: rgba(0, 0, 0, .3);
line-height: 20px;
text-align: center;
}
.@{prefix}-icon-container {
width: 16px;
height: 16px;
margin-left: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.@{prefix}-flat-icon {
width: 10px;
height: 3px;
background: rgba(0,0,0,.60);
}
.@{prefix}-up-triangle {
width: 0;
height: 0;
border-style: solid;
border-width: 0 6px 8px 6px;
border-color: transparent transparent #00a870 transparent;
}
.@{prefix}-down-triangle {
width: 0;
height: 0;
border-style: solid;
border-width: 8px 6px 0 6px;
border-color: #e34d59 transparent transparent transparent;
}

View File

@ -1,7 +1,60 @@
@import './variables.less';
@import './font-family.less';
// layout rewrite
.t-layout--sider {
width: fit-content;
}
.t-button + .t-button {
margin-left: @spacer;
}
.t-layout.t-layout-has-sider {
> .t-layout {
flex: 1;
min-width: 760px;
}
}
.@{prefix} {
// 布局元素调整
&-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
}
&-main-wrapper{
height: 500px;
overflow: scroll;
}
&-side-nav-layout {
&-relative {
height: 100%;
}
}
&-layout{
height: calc(100vh - 64px);
overflow-y: scroll;
}
&-content-layout {
padding: @spacer-3;
}
&-footer-layout {
padding: 0;
margin-bottom: @spacer-2;
}
// slideBar
&-sidebar-layout {
height: 100%;
}
@ -14,7 +67,7 @@
z-index: 100;
}
&-sideNav {
&-side-nav {
position: fixed;
top: 0;
bottom: 0;
@ -43,6 +96,8 @@
&-logo-wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 0 24px;
width: 100%;
&:hover {
cursor: pointer;
@ -51,11 +106,11 @@
&-logo-t-logo {
width: 32px;
margin-left: 16px;
}
&-logo-tdesign-logo {
height: 32px;
color: @text-color-anti;
}
&-logo-normal {
@ -66,7 +121,7 @@
}
}
&-sideNav-placeholder {
&-side-nav-placeholder {
flex: 1 1 232px;
min-width: 232px;
transition: all 0.3s;
@ -94,6 +149,14 @@
margin-left: 24px;
}
.t-default-menu.t-menu--dark {
background: #232A35
}
.version-container {
color: @text-color-anti;
opacity: 0.4;
}
.t-default-menu__inner .t-menu-group-title {
color: @text-color-anti;
opacity: 0.3;
};

View File

@ -43,20 +43,20 @@
--td-success-color-9: #B4E1D3;
--td-success-color-10: #DEEDE8;
--td-gray-color-1: #F3F3F3;
--td-gray-color-2: #EEEEEE;
--td-gray-color-3: #E7E7E7;
--td-gray-color-4: #DCDCDC;
--td-gray-color-5: #C5C5C5;
--td-gray-color-6: #A6A6A6;
--td-gray-color-7: #8B8B8B;
--td-gray-color-8: #777777;
--td-gray-color-9: #5E5E5E;
--td-gray-color-10: #4B4B4B;
--td-gray-color-11: #383838;
--td-gray-color-12: #2C2C2C;
--td-gray-color-13: #242424;
--td-gray-color-14: #181818;
--td-gray-color-1: #f1f2f5;
--td-gray-color-2: #ebedf1;
--td-gray-color-3: #e3e6eb;
--td-gray-color-4: #d6dbe3;
--td-gray-color-5: #bcc4d0;
--td-gray-color-6: #97a3b7;
--td-gray-color-7: #7787a2;
--td-gray-color-8: #5f7292;
--td-gray-color-9: #4b5b76;
--td-gray-color-10: #3c485c;
--td-gray-color-11: #2c3645;
--td-gray-color-12: #232a35;
--td-gray-color-13: #1c222b;
--td-gray-color-14: #13161b;
// 文字 & 图标 颜色
--td-font-white-1: rgba(255, 255, 255, .9);

View File

@ -45,20 +45,20 @@
--td-success-color-9: #044F2A;
--td-success-color-10: #033017;
--td-gray-color-1: #F3F3F3;
--td-gray-color-2: #EEEEEE;
--td-gray-color-3: #E7E7E7;
--td-gray-color-4: #DCDCDC;
--td-gray-color-5: #C5C5C5;
--td-gray-color-6: #A6A6A6;
--td-gray-color-7: #8B8B8B;
--td-gray-color-8: #777777;
--td-gray-color-9: #5E5E5E;
--td-gray-color-10: #4B4B4B;
--td-gray-color-11: #383838;
--td-gray-color-12: #2C2C2C;
--td-gray-color-13: #242424;
--td-gray-color-14: #181818;
--td-gray-color-1: #f1f2f5;
--td-gray-color-2: #ebedf1;
--td-gray-color-3: #e3e6eb;
--td-gray-color-4: #d6dbe3;
--td-gray-color-5: #bcc4d0;
--td-gray-color-6: #97a3b7;
--td-gray-color-7: #7787a2;
--td-gray-color-8: #5f7292;
--td-gray-color-9: #4b5b76;
--td-gray-color-10: #3c485c;
--td-gray-color-11: #2c3645;
--td-gray-color-12: #232a35;
--td-gray-color-13: #1c222b;
--td-gray-color-14: #13161b;
// 文字 & 图标 颜色
--td-font-white-1: rgba(255, 255, 255, 1);

View File

@ -247,4 +247,6 @@
@z-index-loading: 3500;
@z-index-message: 5000;
@z-index-Popup: 5500;
@z-index-Notification: 6000;
@z-index-Notification: 6000;

View File

@ -10,10 +10,10 @@ export const useChart = (domId: string): Ref<echarts.ECharts> => {
let chartContainer: HTMLCanvasElement;
const selfChart = ref<echarts.ECharts | any>();
const updateContainer = () => {
selfChart.value.resize({
width: chartContainer.clientWidth,
height: chartContainer.clientHeight,
});
// selfChart.value.resize({
// width: chartContainer.clientWidth,
// height: chartContainer.clientHeight,
// });
};
onMounted(() => {

View File

@ -26,5 +26,8 @@ export default defineConfig({
server: {
port: 3002,
proxy: {
'/api': 'http://127.0.0.1:3000/',
},
},
});