docs: init

This commit is contained in:
94dreamer 2021-11-26 11:32:16 +08:00
parent 0ddeb3f3e7
commit 9fba04f8a5
20 changed files with 3018 additions and 1 deletions

48
.eslintrc Normal file
View File

@ -0,0 +1,48 @@
{
"extends": [
"plugin:@typescript-eslint/recommended",
"eslint-config-airbnb-base",
"plugin:vue/vue3-recommended",
"plugin:prettier/recommended"
],
"env": {
"browser": true,
"node": true,
"jest": true,
"es6": true
},
"plugins": [
"vue",
"@typescript-eslint"
],
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module",
"allowImportExportEverywhere": true,
"ecmaFeatures": {
"jsx": true
}
},
"settings": {
"import/extensions": [
".js",
".jsx",
".ts",
".tsx"
]
},
"rules": {
"import/extensions": "off",
"import/no-unresolved": "off",
"@typescript-eslint/no-explicit-any": "off",
"no-continue": "off",
"no-restricted-syntax": "off",
"guard-for-in": "off",
"import/no-extraneous-dependencies": "off",
"import/prefer-default-export": "off",
"no-plusplus": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-param-reassign": "off",
"no-shadow": "off"
}
}

4
.gitignore vendored
View File

@ -16,7 +16,9 @@ temp*
coverage
test-report.html
.idea/
yarn.lock
yarn-error.log
package-lock.json
*.zip
.history
.stylelintcache
.stylelintcache

15
cache.dockerfile Normal file
View File

@ -0,0 +1,15 @@
# 选择一个 Base 镜像
FROM node:12
# 设置工作目录
WORKDIR /space
# 将 by 中的文件列表 COPY 过来
COPY . .
# 根据 COPY 过来的文件进行依赖的安装
RUN npm i
# 设置好需要的环境变量
ENV NODE_PATH=/space/node_modules

86
package.json Normal file
View File

@ -0,0 +1,86 @@
{
"name": "tdesign-vue-next-starter",
"version": "0.0.1",
"scripts": {
"dev:mock": "vite --open --mode mock",
"dev": "vite --open --mode development",
"dev:linux": "vite --mode developmenet",
"build:test": "vite build --mode test",
"build": "vue-tsc --noEmit && vite build",
"serve": "vite preview",
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
"lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix",
"stylelint": "stylelint src/**/*.{html,vue,sass,less}",
"stylelint:fix": "stylelint --cache --fix src/**/*.{html,vue,vss,sass,less}"
},
"dependencies": {
"dayjs": "^1.10.6",
"echarts": "^5.2.1",
"nprogress": "^0.2.0",
"qrcode.vue": "^3.2.2",
"tdesign-vue-next": "^0.4.1",
"vue": "^3.1.5",
"vue-router": "^4.0.11",
"vue3-clipboard": "^1.0.0",
"vuex": "^4.0.2"
},
"devDependencies": {
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@typescript-eslint/eslint-plugin": "^4.29.3",
"@typescript-eslint/parser": "^4.29.3",
"@types/echarts": "^4.9.10",
"@vitejs/plugin-vue": "^1.3.0",
"@vitejs/plugin-vue-jsx": "^1.1.7",
"@vue/compiler-sfc": "^3.0.5",
"axios": "^0.21.1",
"commitizen": "^4.2.4",
"compressorjs": "^1.0.7",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^7.16.0",
"http-proxy-agent": "^4.0.1",
"husky": "^7.0.4",
"less": "^4.1.1",
"mockjs": "^1.1.0",
"prettier": "^2.4.1",
"stylelint": "^13.13.1",
"stylelint-config-airbnb": "^0.0.0",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.20.0",
"typescript": "^4.4.3",
"vite": "^2.4.4",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-svg-sprite-component": "^1.0.10",
"vite-svg-loader": "^3.1.0",
"vue-clipboard3": "^1.0.1",
"vue-tsc": "^0.2.2",
"vuex-router-sync": "^5.0.0"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"prettier --write",
"npm run lint:fix",
"git add"
],
"*.{html,vue,vss,sass,less}": [
"npm run stylelint:fix",
"git add"
]
}
}

18
src/main.ts Normal file
View File

@ -0,0 +1,18 @@
import { createApp } from 'vue';
import TDesign from 'tdesign-vue-next/esm';
import VueClipboard from 'vue3-clipboard';
import App from './App.vue';
import { store } from './store';
import router from './router';
import '@/style/index.less';
const app = createApp(App);
app.use(TDesign);
app.use(store);
app.use(router);
app.use(VueClipboard);
app.mount('#app');

View File

@ -0,0 +1,201 @@
import { TdBaseTableProps } from 'tdesign-vue-next';
interface DashboardPannel {
title: string;
number: string | number;
leftType: string;
upTrend?: string;
downTrend?: string;
}
interface TendItem {
growUp?: number;
productName: string;
count: number;
date: string;
}
export const PANE_LIST: Array<DashboardPannel> = [
{
title: '总收入',
number: '¥ 28,425.00',
upTrend: '20.5%',
leftType: 'echarts-line',
},
{
title: '总退款',
number: '¥ 768.00',
downTrend: '20.5%',
leftType: 'echarts-bar',
},
{
title: '活跃用户(个)',
number: '1126',
downTrend: '20.5%',
leftType: 'icon-usergroup',
},
{
title: '产生订单(个)',
number: 527,
downTrend: '20.5%',
leftType: 'icon-file-paste',
},
];
export const SALE_TEND_LIST: Array<TendItem> = [
{
growUp: 1,
productName: '国家电网有限公司',
count: 7059,
date: '2021-09-01',
},
{
growUp: -1,
productName: '深圳燃气集团股份有限公司',
count: 6437,
date: '2021-09-01',
},
{
growUp: 4,
productName: '国家烟草专卖局',
count: 4221,
date: '2021-09-01',
},
{
growUp: 3,
productName: '中国电信集团有限公司',
count: 3317,
date: '2021-09-01',
},
{
growUp: -3,
productName: '中国移动通信集团有限公司',
count: 3015,
date: '2021-09-01',
},
{
growUp: -3,
productName: '新余市办公用户采购项目',
count: 2015,
date: '2021-09-12',
},
];
export const BUY_TEND_LIST: Array<TendItem> = [
{
growUp: 1,
productName: '腾讯科技(深圳)有限公司',
count: 3015,
date: '2021-09-01',
},
{
growUp: -1,
productName: '大润发有限公司',
count: 2015,
date: '2021-09-01',
},
{
growUp: 6,
productName: '四川海底捞股份有限公司',
count: 1815,
date: '2021-09-11',
},
{
growUp: -3,
productName: '索尼(中国)有限公司',
count: 1015,
date: '2021-09-21',
},
{
growUp: -4,
productName: '松下电器(中国)有限公司',
count: 445,
date: '2021-09-19',
},
{
growUp: -3,
productName: '新余市办公用户采购项目',
count: 2015,
date: '2021-09-12',
},
];
export const SALE_COLUMNS: TdBaseTableProps['columns'] = [
{
align: 'center',
colKey: 'index',
title: '排名',
width: 64,
},
{
align: 'left',
ellipsis: true,
colKey: 'productName',
title: '客户名称',
minWidth: '200',
},
{
align: 'center',
colKey: 'growUp',
width: 100,
title: '较上周',
},
{
align: 'center',
colKey: 'count',
title: '订单量',
width: 100,
},
{
align: 'center',
colKey: 'date',
width: 132,
title: '合同签订日期',
},
{
align: 'center',
colKey: 'operation',
title: '操作',
width: 76,
},
];
export const BUY_COLUMNS: TdBaseTableProps['columns'] = [
{
align: 'center',
colKey: 'index',
title: '排名',
width: 64,
},
{
align: 'left',
ellipsis: true,
colKey: 'productName',
title: '供应商名称',
minWidth: 200,
},
{
align: 'center',
colKey: 'growUp',
width: 100,
title: '较上周',
},
{
align: 'center',
colKey: 'count',
title: '订单量',
width: '100',
},
{
align: 'center',
colKey: 'date',
width: 132,
title: '合同签订日期',
},
{
align: 'center',
colKey: 'operation',
title: '操作',
width: 76,
},
];

View File

@ -0,0 +1,317 @@
<template>
<div class="dashboard-panel">
<!-- 顶部 card -->
<t-row :gutter="16" class="row-container">
<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">
<div class="dashboard-item">
<div class="dashboard-item-top">
<span>{{ item.number }}</span>
</div>
<div class="dashbord-item-left">
<div v-if="index === 0">
<div
id="moneyContainer"
class="dashboard-chart-container"
style="width: 96px; height: 40px; margin-top: 36px"
/>
</div>
<div v-else-if="index === 1">
<div
id="refundContainer"
class="dashboard-chart-container"
style="width: 120px; height: 42px; margin-top: 24px"
/>
</div>
<span v-else-if="index === 2">
<t-icon name="usergroup" />
</span>
<span v-else>
<t-icon name="file" />
</span>
</div>
<div class="dashboard-item-bottom">
<div class="dashboard-item-block">
自从上周以来
<trend
class="dashboard-item-trend"
:type="item.upTrend ? 'up' : 'down'"
:is-reverse-color="index === 0"
:describe="item.upTrend || item.downTrend"
/>
</div>
<t-icon name="chevron-right" />
</div>
</div>
</card>
</t-col>
</t-row>
<!-- 中部图表 -->
<t-row :gutter="16" class="row-container">
<t-col :xs="12" :xl="9">
<card title="统计数据">
<template #option>
<div class="dashboard-chart-title-left">(万元) {{ currentMonth }}</div>
<div class="dashboard-chart-title-container">
<t-date-picker
class="card-date-picker-slig"
theme="primary"
mode="date"
range
:default-value="LAST_7_DAYS"
@change="onCurrencyChange"
/>
</div>
</template>
<div
id="monitorContainer"
ref="monitorContainer"
class="dashboard-chart-container"
style="width: 100%; height: 326px"
/>
</card>
</t-col>
<t-col :xs="12" :xl="3">
<card title="销售渠道">
<template #option>
<div class="dashboard-chart-title-left">{{ currentMonth }}</div>
</template>
<div
id="countContainer"
ref="countContainer"
style="width: 100%; height: 326px"
class="dashboard-chart-container"
/>
</card>
</t-col>
</t-row>
<!-- 列表排名 -->
<t-row :gutter="16" class="row-container">
<t-col :xs="12" :xl="6">
<card title="销售订单排名">
<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-table :data="SALE_TEND_LIST" :columns="SALE_COLUMNS" row-key="productName">
<template #index="{ rowIndex }">
<span :class="getRankClass(rowIndex)">
{{ rowIndex + 1 }}
</span>
</template>
<template #growUp="{ row }">
<span>
<trend :type="row.growUp > 0 ? 'up' : 'down'" :describe="Math.abs(row.growUp)" />
</span>
</template>
</t-table>
</card>
</t-col>
<t-col :xs="12" :xl="6">
<card title="采购订单排名">
<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-table :data="BUY_TEND_LIST" :columns="BUY_COLUMNS" row-key="productName">
<template #index="{ rowIndex }">
<span :class="getRankClass(rowIndex)">
{{ rowIndex + 1 }}
</span>
</template>
<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>
</template>
</t-table>
</card>
</t-col>
</t-row>
<!-- 出入库概览 -->
<div class="overview-pannel">
<t-row>
<t-col :xs="12" :xl="9">
<card title="出入库概览">
<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>
</template>
<div
id="stokeContainer"
ref="stokeContainer"
style="width: 100%; height: 351px"
class="dashboard-chart-container"
/>
</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>
<t-row>
<t-col :xs="6" :xl="12">
<card describe="本月出库总计(件)" :style="{ height: '168px' }" size="small">
<div class="dashboard-item">
<div class="dashboard-item-top">
<span>1726</span>
</div>
<div class="dashboard-item-bottom">
<div class="dashboard-item-block">
自从上周以来
<trend class="dashboard-item-trend" type="down" :is-reverse-color="false" describe="20.3%" />
</div>
</div>
</div>
</card>
</t-col>
<t-col :xs="6" :xl="12">
<card describe="本月入库总计(件)" :style="{ height: '168px' }" size="small">
<div class="dashboard-item">
<div class="dashboard-item-top">
<span>226</span>
</div>
<div class="dashboard-item-bottom">
<div class="dashboard-item-block">
自从上周以来
<trend class="dashboard-item-trend" type="down" :is-reverse-color="false" describe="20.3%" />
</div>
</div>
</div>
</card>
</t-col>
</t-row>
</div>
</t-col>
</t-row>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, watch, ref } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core';
import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components';
import { PieChart, LineChart, BarChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { LAST_7_DAYS } from '@/utils/date';
import { useChart } from '@/utils/hooks';
//
import Trend from '@/components/trend/index.vue';
import Card from '@/components/card/index.vue';
import {
changeChartsTheme,
constructInitDataset,
getPieChartDataSet,
getLineChartDataSet,
constructInitDashbordDataset,
} from './index';
import { PANE_LIST, SALE_TEND_LIST, BUY_TEND_LIST, SALE_COLUMNS, BUY_COLUMNS } from './constants';
echarts.use([TooltipComponent, LegendComponent, PieChart, GridComponent, LineChart, BarChart, CanvasRenderer]);
const getThisMonth = (checkedValues?: string[]) => {
let date;
if (!checkedValues || checkedValues.length === 0) {
date = new Date();
return `${date.getFullYear()}-${date.getMonth() + 1}`;
}
date = new Date(checkedValues[0]);
const date2 = new Date(checkedValues[1]);
return `${date.getFullYear()}-${date.getMonth() + 1}${date2.getFullYear()}-${date2.getMonth() + 1}`;
};
export default defineComponent({
name: 'DashboardBase',
components: {
Card,
Trend,
},
setup() {
const refundCharts = useChart('refundContainer');
const moneyCharts = useChart('moneyContainer');
const stokeCharts = useChart('stokeContainer');
const monitorChart = useChart('monitorContainer');
const countChart = useChart('countContainer');
const initCharts = () => {
refundCharts.value.setOption(constructInitDashbordDataset('bar'));
moneyCharts.value.setOption(constructInitDashbordDataset('line'));
stokeCharts.value.setOption(constructInitDataset(LAST_7_DAYS));
monitorChart.value.setOption(getLineChartDataSet());
countChart.value.setOption(getPieChartDataSet());
};
const currentMonth = ref(getThisMonth());
onMounted(() => {
initCharts();
});
const store = useStore();
watch(
() => store.state.setting.brandTheme,
() => {
changeChartsTheme([
refundCharts.value,
moneyCharts.value,
stokeCharts.value,
monitorChart.value,
countChart.value,
]);
},
);
return {
currentMonth,
LAST_7_DAYS,
PANE_LIST,
BUY_TEND_LIST,
SALE_TEND_LIST,
SALE_COLUMNS,
BUY_COLUMNS,
onCurrencyChange(checkedValues: string[]) {
currentMonth.value = getThisMonth(checkedValues);
monitorChart.value.setOption(getLineChartDataSet(checkedValues));
},
onWharehouseChange(checkedValues: string[]) {
stokeCharts.value.setOption(constructInitDataset(checkedValues));
},
rehandleClickOp(val: MouseEvent) {
console.log(val);
},
getRankClass(index: number) {
return ['dashbord-rank', { 'dashbord-rank__top': index < 3 }];
},
};
},
});
</script>
<style lang="less" scoped>
@import url('./index.less');
</style>

View File

@ -0,0 +1,115 @@
<template>
<div class="dashboard-panel-detail">
<card title="本月采购申请情况">
<t-row :gutter="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>
<div class="dashboard-detail-container-item-text">
<span
>环比<trend :type="item.percent > 0 ? 'up' : 'down'" :describe="`${Math.abs(item.percent)}%`"
/></span>
<t-icon name="chevron-right" />
</div>
</div>
</t-col>
</t-row>
</card>
<t-row :gutter="16">
<t-col :xs="12" :xl="9">
<card title="采购商品申请趋势">
<template #option>
<div class="card-date-picker">
<t-date-picker
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"
range
@change="onMaterialChange"
/>
</div>
</template>
<div id="lineContainer" style="width: 100%; height: 418px" />
</card>
</t-col>
<t-col :xs="12" :xl="3">
<product-card v-for="(item, index) in PRODUCT_LIST" :key="index" :product="item" />
</t-col>
</t-row>
<card title="采购商品满意度分布">
<template #option>
<t-date-picker
class="card-date-picker"
:default-value="LAST_7_DAYS"
theme="primary"
mode="date"
range
@change="onHappinesChange"
/>
<t-button class="card-date-button"> 导出数据 </t-button>
</template>
<div id="scatterContainer" style="width: 100%; height: 330px" />
</card>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, watch } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core';
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import { LineChart, ScatterChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import ProductCard from '@/pages/list/card/components/Card.vue';
import { changeChartsTheme, getFolderlineDataSet, getScattlerDataSet } from '../base/index';
import { PANE_LIST_DATA, PRODUCT_LIST } from './constants';
import { useChart } from '@/utils/hooks';
import { LAST_7_DAYS } from '@/utils/date';
import Trend from '@/components/trend/index.vue';
import Card from '@/components/card/index.vue';
echarts.use([GridComponent, LegendComponent, TooltipComponent, LineChart, ScatterChart, CanvasRenderer]);
export default defineComponent({
name: 'DashboardDetail',
components: {
Card,
Trend,
ProductCard,
},
setup() {
const lineChart = useChart('lineContainer');
const scatterChart = useChart('scatterContainer');
const store = useStore();
watch(
() => store.state.setting.brandTheme,
(val) => {
changeChartsTheme([lineChart.value, scatterChart.value], val);
},
);
onMounted(() => {
lineChart.value.setOption(getFolderlineDataSet());
scatterChart.value.setOption(getScattlerDataSet());
});
return {
LAST_7_DAYS,
PRODUCT_LIST,
PANE_LIST_DATA,
onHappinesChange() {
scatterChart.value.setOption(getScattlerDataSet());
},
onMaterialChange(value) {
lineChart.value.setOption(getFolderlineDataSet(value));
},
};
},
});
</script>
<style lang="less" scoped>
@import url('./index.less');
</style>

View File

@ -0,0 +1,186 @@
<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>
</template>
<div id="dataContainer" style="width: 100%; height: 265px" />
</card>
</t-col>
</t-row>
<!-- 项目列表 -->
<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">
<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>
</template>
</t-dialog>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core';
import { TitleComponent, ToolboxComponent, TooltipComponent, GridComponent, LegendComponent } from 'echarts/components';
import { BarChart, LineChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { changeChartsTheme, getSmoothLineDataSet, get2ColBarChartDataSet } from '../../dashboard/base/index';
import { BASE_INFO_DATA, TABLE_COLUMNS } from './constants';
import { PREFIX } from '@/config/global';
import Card from '@/components/card/index.vue';
import { ResDataType } from '@/interface';
import request from '@/utils/request';
import { useChart } from '@/utils/hooks';
echarts.use([
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
LegendComponent,
BarChart,
LineChart,
CanvasRenderer,
]);
export default defineComponent({
name: 'DetailDeploy',
components: { Card },
setup() {
const data = ref([]);
const pagination = ref({
defaultPageSize: 10,
total: 100,
defaultCurrent: 1,
});
const fetchData = async () => {
try {
const res: ResDataType = await request.get('/api/get-project-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
}
};
const visible = ref(false);
const monitorChart = useChart('monitorContainer');
const dataChart = useChart('dataContainer');
const intervalTimer = null;
onMounted(() => {
fetchData();
dataChart.value.setOption(get2ColBarChartDataSet());
monitorChart.value.setOption(getSmoothLineDataSet());
setInterval(() => {
monitorChart.value.setOption(getSmoothLineDataSet());
}, 3000);
});
onUnmounted(() => {
clearInterval(intervalTimer);
});
const onAlertChange = () => {
dataChart.value.setOption(get2ColBarChartDataSet());
};
const store = useStore();
watch(
() => store.state.setting.brandTheme,
(val) => {
changeChartsTheme([monitorChart.value, dataChart.value], val);
},
);
return {
PREFIX,
BASE_INFO_DATA,
columns: TABLE_COLUMNS,
data,
pagination,
visible,
sortChange(val) {
console.log(val);
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
listClick() {
visible.value = true;
},
onConfirm() {
visible.value = false;
},
deleteClickOp(e) {
data.value.splice(e.index, 1);
},
onAlertChange,
};
},
});
</script>
<style lang="less" scoped>
@import url('./index.less');
</style>

View File

@ -0,0 +1,210 @@
<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>
<!-- 合同收付类型 -->
<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-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-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>
</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>
</div>
</div>
</template>
<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({
name: 'FormBase',
setup() {
const formData = ref({ ...INITIAL_DATA });
const files = ref([]);
return {
PREFIX,
TYPE_OPTIONS,
PARTY_A_OPTIONS,
PARTY_B_OPTIONS,
FORM_RULES,
formData,
files,
onReset() {
MessagePlugin.warning('取消新建');
},
onSubmit({ validateResult }) {
if (validateResult === true) {
MessagePlugin.success('新建成功');
}
},
beforeUpload(file) {
if (!/\.(pdf)$/.test(file.name)) {
MessagePlugin.warning('请上传pdf文件');
return false;
}
if (file.size > 60 * 1024 * 1024) {
MessagePlugin.warning('上传文件不能大于60M');
return false;
}
return true;
},
handleFail({ file }) {
MessagePlugin.error(`文件 ${file.name} 上传失败`);
},
// error url /
formatResponse(res) {
return { ...res, error: '上传失败,请重试', url: res.url };
},
};
},
});
</script>
<style lang="less" scoped>
@import url('./index.less');
</style>

View File

@ -0,0 +1,199 @@
<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>
<template #suffix-icon>
<t-icon-search size="20px" />
</template>
</t-input>
</div>
</t-row>
<div class="table-container">
<t-table
:data="data"
:columns="COLUMNS"
:row-key="rowKey"
vertical-align="top"
:hover="true"
:pagination="pagination"
:selected-row-keys="selectedRowKeys"
:loading="dataLoading"
@page-change="rehandlePageChange"
@change="rehandleChange"
@select-change="rehandleSelectChange"
>
<template #status="{ row }">
<t-tag v-if="row.status === CONTRACT_STATUS.FAIL" theme="danger" variant="light"> 审核失败 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.AUDIT_PENDING" theme="warning" variant="light"> 待审核 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.EXEC_PENDING" theme="warning" variant="light"> 待履行 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.EXECUTING" theme="success" variant="light"> 履行中 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.FINISH" theme="success" variant="light"> 已完成 </t-tag>
</template>
<template #contractType="{ row }">
<p v-if="row.contractType === CONTRACT_TYPES.MAIN">审核失败</p>
<p v-if="row.contractType === CONTRACT_TYPES.SUB">待审核</p>
<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">
付款<trend class="dashboard-item-trend" type="up" />
</p>
<p v-if="row.paymentType === CONTRACT_PAYMENT_TYPES.RECIPT" class="payment-col">
收款<trend class="dashboard-item-trend" type="down" />
</p>
</template>
<template #op="slotProps">
<a :class="`${PREFIX}-link`" @click="handleClickDetail()">详情</a>
<a :class="`${PREFIX}-link`" @click="handleClickDelete(slotProps)">删除</a>
</template>
</t-table>
</div>
</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 { 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 { ResDataType } from '@/interface';
import request from '@/utils/request';
import { COLUMNS } from './constants';
export default defineComponent({
name: 'ListBaseCard',
components: {
TIconSearch,
Trend,
},
setup() {
const data = ref([]);
const pagination = ref({
defaultPageSize: 20,
total: 100,
defaultCurrent: 1,
});
const searchValue = ref('');
const dataLoading = ref(false);
const fetchData = async () => {
dataLoading.value = true;
try {
const res: ResDataType = await request.get('/api/get-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
} finally {
dataLoading.value = false;
}
};
const deleteIdx = ref(-1);
const confirmBody = computed(() => {
if (deleteIdx.value > -1) {
const { no, name } = data.value[deleteIdx.value];
return `产品编号:${no}, 产品名称: ${name}`;
}
return '';
});
onMounted(() => {
fetchData();
});
const confirmVisible = ref(false);
const selectedRowKeys = ref([1, 2]);
const router = useRouter();
const resetIdx = () => {
deleteIdx.value = -1;
};
const onConfirmDelete = () => {
//
data.value.splice(deleteIdx.value - 1, 1);
pagination.value.total = data.value.length;
const selectedIdx = selectedRowKeys.value.indexOf(deleteIdx.value);
if (selectedIdx > -1) {
selectedRowKeys.value.splice(selectedIdx, 1);
}
confirmVisible.value = false;
MessagePlugin.success('删除成功');
resetIdx();
};
const onCancel = () => {
resetIdx();
};
return {
CONTRACT_STATUS,
CONTRACT_TYPES,
CONTRACT_PAYMENT_TYPES,
COLUMNS,
PREFIX,
data,
searchValue,
dataLoading,
pagination,
confirmBody,
confirmVisible,
rowKey: 'index',
onConfirmDelete,
onCancel,
selectedRowKeys,
rehandleSelectChange(val: number[]) {
selectedRowKeys.value = val;
},
rehandlePageChange(curr, pageInfo) {
console.log('分页变化', curr, pageInfo);
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
handleClickDetail() {
router.push('/detail/base');
},
handleSetupContract() {
router.push('/form/base');
},
handleClickDelete({ row }) {
deleteIdx.value = row.index;
confirmVisible.value = true;
},
};
},
methods: {},
});
</script>
<style lang="less" scoped>
@import url('./index.less');
</style>

View File

@ -0,0 +1,240 @@
<template>
<div :class="cardClass">
<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" />
</div>
<p :class="cardStatusClass">
{{ product.isSetup ? '已启用' : '已停用' }}
</p>
</t-row>
<p class="list-card-item_detail--name">
{{ product.name }}
</p>
<p class="list-card-item_detail--desc">
{{ product.description }}
</p>
<t-row justify="space-between" align="middle" :class="cardControlClass">
<div>
<t-button shape="circle" :disabled="!product.isSetup">
{{ typeMap[product.type - 1] }}
</t-button>
<t-button shape="circle" :disabled="!product.isSetup">
<t-icon-add />
</t-button>
</div>
<t-dropdown
:disabled="!product.isSetup"
:options="[
{
content: '管理',
value: 'manage',
onClick: () => handleClickManage(product),
},
{
content: '删除',
value: 'delete',
onClick: () => handleClickDelete(product),
},
]"
>
<t-icon-more />
</t-dropdown>
</t-row>
</div>
</div>
</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';
export interface CardProductType {
type: number;
isSetup: boolean;
description: string;
name: string;
}
export default defineComponent({
name: 'ListCardComponent',
components: {
TIconShop,
TIconCalendar,
TIconService,
TIconUserAvatar,
TIconLaptop,
TIconMore,
TIconAdd,
},
props: {
product: {
type: Object as PropType<CardProductType>,
default: () => {
return {};
},
},
},
emits: ['manage-product', 'delete-item'],
setup(props, ctx) {
const { emit } = ctx;
const cardClass = computed(() => [
'list-card-item',
{
'list-card-item__disabled': !props.product.isSetup,
},
]);
const cardLogoClass = computed(() => [
'list-card-item_detail--logo',
{
'list-card-item_detail--logo__disabled': !props.product.isSetup,
},
]);
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',
{
'list-card-item_detail--control__disabled': !props.product.isSetup,
},
]);
return {
cardClass,
cardLogoClass,
cardStatusClass,
cardControlClass,
typeMap: ['A', 'B', 'C', 'D', 'E'],
handleClickManage(product) {
emit('manage-product', product);
},
handleClickDelete(product) {
emit('delete-item', product);
},
};
},
});
</script>
<style lang="less" scoped>
@import '@/style/index';
.list-card-item {
display: flex;
flex-direction: column;
border-radius: @border-radius;
overflow: hidden;
cursor: pointer;
color: @text-color-secondary;
&_detail {
flex: 1;
background: @bg-color-container;
padding: 24px 32px;
min-height: 140px;
&--logo {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background: @brand-color-1;
font-size: 32px;
color: @brand-color;
&__disabled {
color: @text-color-disabled;
}
}
&--name {
margin: 24px 0 8px 0;
font-size: 16px;
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;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-bottom: 24px;
height: 40px;
}
&--control {
> div:first-child {
position: relative;
> button:last-child {
position: absolute;
left: 18px;
background-color: @brand-color-2;
--ripple-color: @brand-color-2;
color: @brand-color;
}
}
&__disabled {
> div:first-child {
> button:first-child {
background-color: @gray-color-6;
color: @text-color-anti;
}
> button:last-child {
background-color: @gray-color-2;
color: @text-color-disabled;
}
}
}
}
}
&__disabled {
color: @text-color-disabled;
}
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<t-dialog v-model:visible="formVisible" header="新建产品" :width="680" :footer="false">
<template #body>
<!-- 表单内容 -->
<t-form ref="form" :data="formData" :rules="rules" :label-width="100" @submit="onSubmit">
<t-form-item label="产品名称" name="name">
<t-input v-model="formData.name" :style="{ width: '480px' }" placeholder="请输入产品名称" />
</t-form-item>
<t-form-item label="产品状态" name="status">
<t-radio-group v-model="formData.status">
<t-radio value="0"> 已停用 </t-radio>
<t-radio value="1"> 已启用 </t-radio>
</t-radio-group>
</t-form-item>
<t-form-item label="产品描述" name="description">
<t-input v-model="formData.description" :style="{ width: '480px' }" placeholder="请输入产品描述" />
</t-form-item>
<t-form-item label="产品类型" name="type">
<t-select v-model="formData.type" clearable :style="{ width: '480px' }">
<t-option v-for="(item, index) in SELECT_OPTIONS" :key="index" :value="item.value" :label="item.label">
{{ item.label }}
</t-option>
</t-select>
</t-form-item>
<t-form-item label="备注" name="mark">
<t-textarea v-model="textareaValue" :style="{ width: '480px' }" placeholder="请输入内容" name="description" />
</t-form-item>
<t-form-item style="float: right">
<t-button variant="outline" @click="onClickCloseBtn"> 取消 </t-button>
<t-button theme="primary" type="submit"> 确定 </t-button>
</t-form-item>
</t-form>
</template>
</t-dialog>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
const INITIAL_DATA = {
name: '',
status: '',
description: '',
type: '',
mark: '',
amount: 0,
};
const SELECT_OPTIONS = [
{ label: '网关', value: '1' },
{ label: '人工智能', value: '2' },
{ label: 'CVM', value: '3' },
];
export default defineComponent({
props: {
visible: {
type: Boolean,
default: false,
},
data: Object,
},
setup(props, ctx) {
const formVisible = ref(false);
const formData = ref(props.data);
const textareaValue = ref('');
const onSubmit = ({ result, firstError }) => {
if (!firstError) {
MessagePlugin.success('提交成功');
formVisible.value = false;
} else {
console.log('Errors: ', result);
MessagePlugin.warning(firstError);
}
};
const onClickCloseBtn = () => {
formVisible.value = false;
formData.value = { ...INITIAL_DATA };
};
watch(
() => formVisible.value,
(val) => {
const { emit } = ctx;
emit('update:visible', val);
},
);
watch(
() => props.visible,
(val) => {
formVisible.value = val;
},
);
watch(
() => props.data,
(val) => {
formData.value = val;
},
);
return {
SELECT_OPTIONS,
formVisible,
formData,
textareaValue,
onSubmit,
onClickCloseBtn,
rules: {
name: [{ required: true, message: '请输入产品名称', type: 'error' }],
},
};
},
});
</script>
<style></style>

View File

@ -0,0 +1,206 @@
<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>
<template #suffix-icon>
<t-icon-search v-if="searchValue === ''" size="20px" />
</template>
</t-input>
</div>
</div>
<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>
</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>
<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 { 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';
import { ResDataType } from '@/interface';
const INITIAL_DATA = {
name: '',
status: '',
description: '',
type: '',
mark: '',
amount: 0,
};
export default defineComponent({
name: 'ListBase',
components: {
TIconSearch,
Card,
DialogForm,
},
setup() {
const pagination = ref({ current: 1, pageSize: 12, total: 0 });
const deleteProduct = ref(undefined);
const productList = ref([]);
const dataLoading = ref(true);
const fetchData = async () => {
try {
const res: ResDataType = await request.get('/api/get-card-list');
if (res.code === 0) {
const { list = [] } = res.data;
productList.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
} finally {
dataLoading.value = false;
}
};
const confirmBody = computed(() =>
deleteProduct.value ? `产品名称:${deleteProduct.value.name}, 产品描述: ${deleteProduct.value?.description}` : '',
);
onMounted(() => {
fetchData();
});
const formDialogVisible = ref(false);
const searchValue = ref('');
const confirmVisible = ref(false);
const formData = ref({ ...INITIAL_DATA });
return {
PREFIX,
pagination,
productList,
dataLoading,
formDialogVisible,
confirmBody,
searchValue,
confirmVisible,
formData,
onPageSizeChange(size: number) {
pagination.value.pageSize = size;
pagination.value.current = 1;
},
onCurrentChange(current: number) {
pagination.value.current = current;
},
handleDeleteItem(product) {
confirmVisible.value = true;
deleteProduct.value = product;
},
onConfirmDelete() {
const { index } = deleteProduct.value;
productList.value.splice(index - 1, 1);
confirmVisible.value = false;
MessagePlugin.success('删除成功');
},
onCancel() {
deleteProduct.value = undefined;
formData.value = { ...INITIAL_DATA };
},
handleManageProduct(product) {
formDialogVisible.value = true;
formData.value = { ...product, status: product?.isSetup ? '1' : '0' };
},
};
},
});
</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;
}
}
</style>

View File

@ -0,0 +1,320 @@
<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">
<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-form>
<div class="table-container">
<t-table
:data="data"
:columns="COLUMNS"
:row-key="rowKey"
:vertical-align="verticalAlign"
:hover="hover"
:pagination="pagination"
:loading="dataLoading"
@page-change="rehandlePageChange"
@change="rehandleChange"
>
<template #status="{ row }">
<t-tag v-if="row.status === CONTRACT_STATUS.FAIL" theme="danger" variant="light"> 审核失败 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.AUDIT_PENDING" theme="warning" variant="light"> 待审核 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.EXEC_PENDING" theme="warning" variant="light"> 待履行 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.EXECUTING" theme="success" variant="light"> 履行中 </t-tag>
<t-tag v-if="row.status === CONTRACT_STATUS.FINISH" theme="success" variant="light"> 已完成 </t-tag>
</template>
<template #contractType="{ row }">
<p v-if="row.contractType === CONTRACT_TYPES.MAIN">审核失败</p>
<p v-if="row.contractType === CONTRACT_TYPES.SUB">待审核</p>
<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">
付款<trend class="dashboard-item-trend" type="up" />
</p>
<p v-if="row.paymentType === CONTRACT_PAYMENT_TYPES.RECIPT" class="payment-col">
收款<trend class="dashboard-item-trend" type="down" />
</p>
</template>
<template #op="slotProps">
<a :class="PREFIX + '-link'" @click="rehandleClickOp(slotProps)">管理</a>
<a :class="PREFIX + '-link'" @click="handleClickDelete(slotProps)">删除</a>
</template>
</t-table>
<t-dialog
v-model:visible="confirmVisible"
header="是否确认删除该产品"
:body="confirmBody"
:on-cancel="onCancel"
@confirm="onConfirmDelete"
/>
</div>
</div>
</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 Trend from '@/components/trend/index.vue';
import { COLUMNS } from './constants';
import request from '@/utils/request';
import { ResDataType } from '@/interface';
import {
CONTRACT_STATUS,
CONTRACT_STATUS_OPTIONS,
CONTRACT_TYPES,
CONTRACT_TYPE_OPTIONS,
CONTRACT_PAYMENT_TYPES,
} from '@/constants';
const searchForm = {
name: '',
no: undefined,
status: undefined,
type: '',
};
export default defineComponent({
name: 'ListTable',
components: {
Trend,
TIconChevronDown,
},
setup() {
const formData = ref({ ...searchForm });
const tableConfig = {
rowKey: 'index',
verticalAlign: 'top',
hover: true,
};
const pagination = ref({
defaultPageSize: 20,
total: 100,
defaultCurrent: 1,
});
const confirmVisible = ref(false);
const isExpand = ref(false);
const toggleExpand = () => {
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);
const fetchData = async () => {
dataLoading.value = true;
try {
const res: ResDataType = await request.get('/api/get-list');
if (res.code === 0) {
const { list = [] } = res.data;
data.value = list;
pagination.value = {
...pagination.value,
total: list.length,
};
}
} catch (e) {
console.log(e);
} finally {
dataLoading.value = false;
}
};
const deleteIdx = ref(-1);
const confirmBody = computed(() => {
if (deleteIdx.value > -1) {
const { no, name } = data.value[deleteIdx.value];
return `产品编号:${no}, 产品名称: ${name}`;
}
return '';
});
const resetIdx = () => {
deleteIdx.value = -1;
};
const onConfirmDelete = () => {
//
data.value.splice(deleteIdx.value - 1, 1);
pagination.value.total = data.value.length;
confirmVisible.value = false;
MessagePlugin.success('删除成功');
resetIdx();
};
const onCancel = () => {
resetIdx();
};
onMounted(() => {
fetchData();
});
return {
PREFIX,
data,
COLUMNS,
CONTRACT_STATUS,
CONTRACT_STATUS_OPTIONS,
CONTRACT_TYPES,
CONTRACT_TYPE_OPTIONS,
CONTRACT_PAYMENT_TYPES,
formData,
pagination,
confirmVisible,
operationStyle,
confirmBody,
...tableConfig,
onConfirmDelete,
onCancel,
dataLoading,
handleClickDelete({ row }) {
deleteIdx.value = row.index;
confirmVisible.value = true;
},
onReset(val) {
console.log(val);
},
onSubmit(val) {
console.log(val);
},
rehandlePageChange(curr, pageInfo) {
console.log('分页变化', curr, pageInfo);
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
rehandleClickOp({ text, row }) {
console.log(text, row);
},
isExpand,
toggleExpand,
};
},
});
</script>
<style lang="less" scoped>
@import '@/style/index';
.@{prefix} {
&-list-table {
background-color: @bg-color-container;
padding: 30px 32px;
border-radius: @border-radius;
&-form {
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;
}
}
}
.table-container {
margin-top: 30px;
}
}
&-operater-row {
margin-bottom: 16px;
}
&-form-item-content {
width: 240px;
display: inline-block;
margin-right: 40px;
}
}
.payment-col {
display: flex;
.trend-container {
display: flex;
align-items: center;
margin-left: 8px;
}
}
</style>

View File

@ -0,0 +1,91 @@
<template>
<div :class="`${PREFIX}-tree-panel`">
<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" />
</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 />
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import TIconSearch from 'tdesign-vue-next/lib/icon/search';
import { PREFIX } from '@/config/global';
import { TREE_DATA } from './constants';
import ListCommonTable from '../components/Table.vue';
export default defineComponent({
name: 'ListTree',
components: {
TIconSearch,
ListCommonTable,
},
setup() {
const filterByText = ref();
const filterText = ref();
return {
PREFIX,
TREE_DATA,
expanded: ['0', '0-0', '0-1', '0-2', '0-3', '0-4'],
filterText,
filterByText,
onInput() {
filterByText.value = (node) => {
const rs = node.label.indexOf(filterText.value) >= 0;
return rs;
};
},
};
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.@{prefix} {
&-tree-panel {
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;
}
}
.list-tree-wrapper {
overflow-y: hidden;
}
.list-tree-operator {
width: 200px;
float: left;
padding: 30px 32px;
}
.list-tree-content {
border-left: 1px solid @border-level-1-color;
overflow: auto;
}
.selet-base {
width: 88px;
}
</style>

View File

@ -0,0 +1,164 @@
<template>
<!-- 密码登陆 -->
<div v-if="type == 'pwd'" class="item-container login-pwd">
<div class="input-container">
<t-input v-model="userInfo.EngName" style="width: 400px" size="large" placeholder="请输入您的邮箱/手机号">
<template #prefix-icon>
<t-icon name="user" />
</template>
</t-input>
<t-popup placement="right" trigger="focus" show-arrow>
<t-input
v-model="psw"
style="width: 400px"
size="large"
:type="showPsw ? 'text' : 'password'"
clearablec
placeholder="请输入密码"
@keyup="checkPsw"
>
<template #prefix-icon>
<t-icon name="lock-on" />
</template>
<template #suffix-icon>
<t-icon :name="showPsw ? 'browse' : 'browse-off'" @click="showPsw = !showPsw" />
</template>
</t-input>
<template #content>
<div>
<div :class="['rex-check', { 'format-correct': check1 }]">
<t-icon name="check-circle-filled" size="large" />
<span>1-20个英文字符</span>
</div>
<div :class="['rex-check', { 'format-correct': check2 }]">
<t-icon name="check-circle-filled" size="large" />
<span>需包含下划线</span>
</div>
</div>
</template>
</t-popup>
<div class="check-container">
<t-checkbox>记住账号</t-checkbox>
<span class="tip">忘记账号</span>
</div>
<t-button class="button-container" style="width: 400px" size="large" @click="handleClickToLogIn"> 登录 </t-button>
</div>
<div class="bottom-container">
<span class="tip" @click="switchType('qrcode')">使用微信扫码登录</span>
<i>|</i>
<span class="tip" @click="switchType('phone')">使用短信登录</span>
</div>
</div>
<!-- 扫码登陆 -->
<div v-else-if="type == 'qrcode'" class="item-container login-qrcode">
<div class="input-container">
<div class="tip-container">
<span class="tip1">请使用微信扫一扫登录</span>
<span class="tip2 refresh">刷新 <t-icon name="refresh" color="#0052D9" /> </span>
</div>
<qrcode-vue value="" :size="192" level="H" />
</div>
<div class="bottom-container">
<span class="tip" @click="switchType('pwd')">使用账号密码登录</span>
<i>|</i>
<span class="tip" @click="switchType('phone')">使用短信登录</span>
</div>
</div>
<!-- 手机号登陆 -->
<div v-else class="item-container login-phone">
<div class="input-container">
<t-input v-model="userInfo.EngName" style="width: 400px" size="large" placeholder="请输入您的手机号">
<template #prefix-icon>
<t-icon name="user" />
</template>
</t-input>
<div class="verification-code">
<t-input style="width: 282px" size="large" placeholder="请输入验证码" />
<t-button variant="outline" :disabled="countDown > 0" @click="handleCounter">
{{ countDown == 0 ? '发送验证码' : `${countDown}秒后可重发` }}
</t-button>
</div>
<t-button class="button-container" style="width: 400px" size="large" @click="handleClickToLogIn"> 登录 </t-button>
</div>
<div class="bottom-container">
<span class="tip" @click="switchType('pwd')">使用账号密码登录</span>
<i>|</i>
<span class="tip" @click="switchType('qrcode')">使用微信扫码登录</span>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import QrcodeVue from 'qrcode.vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { useCounter } from '@/utils/hooks';
const INITIAL_DATA = {
EngName: '',
};
export default defineComponent({
components: { QrcodeVue },
setup() {
const type = ref('pwd');
const psw = ref('');
const check1 = ref(false);
const check2 = ref(false);
const userInfo = ref({ ...INITIAL_DATA });
const showPsw = ref(false);
const [countDown, handleCounter] = useCounter();
const checkPsw = () => {
const regExp = /^[a-z0-9_]{1,20}$/;
if (regExp.test(psw.value)) {
check1.value = true;
} else {
check1.value = false;
}
if (psw.value.indexOf('_') !== -1) {
check2.value = true;
} else {
check2.value = false;
}
};
const switchType = (val: string) => {
type.value = val;
};
const router = useRouter();
const store = useStore();
const handleClickToLogIn = () => {
store.commit('user/SET_USER_INFO', userInfo.value);
MessagePlugin.success('登录成功');
router.push({
path: '/',
});
};
return {
showPsw,
psw,
userInfo,
checkPsw,
check1,
check2,
type,
switchType,
countDown,
handleCounter,
handleClickToLogIn,
};
},
});
</script>

View File

@ -0,0 +1,74 @@
export const USER_INFO_LIST = [
{
title: '手机',
content: '+86 13923734567',
},
{
title: '座机',
content: '734567',
},
{
title: '办公室邮箱',
content: 'Account@qq.com',
},
{
title: '座位',
content: 'T32F 012',
},
{
title: '管理主体',
content: '腾讯集团',
},
{
title: '直属上级',
content: 'Account@qq.com',
},
{
title: '直属上级',
content: '高级 UI 设计师',
},
{
title: '入职时间',
content: '2021-07-01',
},
{
title: '所属团队',
content: '腾讯/腾讯公司/某事业群/某产品部/某运营中心/商户服务组',
span: 6,
},
];
export const TEAM_MEMBERS = [
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar5.png',
title: 'Lovellzhang 张庆霖',
description: '直客销售 港澳拓展组员工',
},
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar1.png',
title: 'Jiajingwang 王佳静',
description: '前端开发 前台研发组员工',
},
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar5.png',
title: 'cruisezhang 张超',
description: '技术产品 产品组员工',
},
{
avatar: 'https://tdesign.gtimg.com/pro-template/personal/avatar3.png',
title: 'Lovellzhang 张庆霖',
description: '产品运营 港澳拓展组员工',
},
];
export const PRODUCT_LIST = [
{
logo: 'https://tdesign.gtimg.com/pro-template/tdesign-icon1.png',
},
{
logo: 'https://tdesign.gtimg.com/pro-template/tdesign-icon2.png',
},
{
logo: 'https://tdesign.gtimg.com/pro-template/tdesign-icon3.png',
},
];

154
src/pages/user/index.vue Normal file
View File

@ -0,0 +1,154 @@
<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>
<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>
</div>
</div>
</t-col>
<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>
<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>
<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>
</template>
<script lang="ts">
import { defineComponent, onMounted, watch } from 'vue';
import { DateValue } from 'tdesign-vue-next';
import { useStore } from 'vuex';
import * as echarts from 'echarts/core';
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import { LineChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { LAST_7_DAYS } from '@/utils/date';
import { useChart } from '@/utils/hooks';
import { USER_INFO_LIST, TEAM_MEMBERS, PRODUCT_LIST } from './constants';
import { changeChartsTheme, getFolderlineDataSet } from '@/pages/dashboard/base/index';
echarts.use([GridComponent, TooltipComponent, LineChart, CanvasRenderer, LegendComponent]);
export default defineComponent({
setup() {
const lineChart = useChart('lineContainer');
const onLineChange = (value: DateValue) => {
lineChart.value.setOption(getFolderlineDataSet(value));
};
onMounted(() => {
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,
(val) => {
changeChartsTheme([lineChart.value], val);
},
);
return {
LAST_7_DAYS,
USER_INFO_LIST,
TEAM_MEMBERS,
PRODUCT_LIST,
onLineChange,
};
},
});
</script>
<style lang="less" scoped>
@import url('./index.less');
</style>

250
src/style/variables.less Normal file
View File

@ -0,0 +1,250 @@
/** 公共前缀 */
@prefix: tdesign-pro;
// 颜色色板
@brand-color-1: var(--td-brand-color-1);
@brand-color-2: var(--td-brand-color-2);
@brand-color-3: var(--td-brand-color-3);
@brand-color-4: var(--td-brand-color-4);
@brand-color-5: var(--td-brand-color-5);
@brand-color-6: var(--td-brand-color-6);
@brand-color-7: var(--td-brand-color-7);
@brand-color-8: var(--td-brand-color-8);
@brand-color-9: var(--td-brand-color-9);
@brand-color-10: var(--td-brand-color-10);
@warning-color-1: var(--td-warning-color-1);
@warning-color-2: var(--td-warning-color-2);
@warning-color-3: var(--td-warning-color-3);
@warning-color-4: var(--td-warning-color-4);
@warning-color-5: var(--td-warning-color-5);
@warning-color-6: var(--td-warning-color-6);
@warning-color-7: var(--td-warning-color-7);
@warning-color-8: var(--td-warning-color-8);
@warning-color-9: var(--td-warning-color-9);
@warning-color-10: var(--td-warning-color-10);
@error-color-1: var(--td-error-color-1);
@error-color-2: var(--td-error-color-2);
@error-color-3: var(--td-error-color-3);
@error-color-4: var(--td-error-color-4);
@error-color-5: var(--td-error-color-5);
@error-color-6: var(--td-error-color-6);
@error-color-7: var(--td-error-color-7);
@error-color-8: var(--td-error-color-8);
@error-color-9: var(--td-error-color-9);
@error-color-10: var(--td-error-color-10);
@success-color-1: var(--td-success-color-1);
@success-color-2: var(--td-success-color-2);
@success-color-3: var(--td-success-color-3);
@success-color-4: var(--td-success-color-4);
@success-color-5: var(--td-success-color-5);
@success-color-6: var(--td-success-color-6);
@success-color-7: var(--td-success-color-7);
@success-color-8: var(--td-success-color-8);
@success-color-9: var(--td-success-color-9);
@success-color-10: var(--td-success-color-10);
@gray-color-1: var(--td-gray-color-1);
@gray-color-2: var(--td-gray-color-2);
@gray-color-3: var(--td-gray-color-3);
@gray-color-4: var(--td-gray-color-4);
@gray-color-5: var(--td-gray-color-5);
@gray-color-6: var(--td-gray-color-6);
@gray-color-7: var(--td-gray-color-7);
@gray-color-8: var(--td-gray-color-8);
@gray-color-9: var(--td-gray-color-9);
@gray-color-10: var(--td-gray-color-10);
@gray-color-11: var(--td-gray-color-11);
@gray-color-12: var(--td-gray-color-12);
@gray-color-13: var(--td-gray-color-13);
@gray-color-14: var(--td-gray-color-14);
// 文字 & 图标 颜色
@font-white-1: var(--td-font-white-1);
@font-white-2: var(--td-font-white-2);
@font-white-3: var(--td-font-white-3);
@font-white-4: var(--td-font-white-4);
@font-gray-1: var(--td-font-gray-1);
@font-gray-2: var(--td-font-gray-2);
@font-gray-3: var(--td-font-gray-3);
@font-gray-4: var(--td-font-gray-4);
// 基础颜色
@brand-color: var(--td-brand-color); // 色彩-品牌-可操作
@warning-color: var(--td-warning-color); // 色彩-功能-警告
@error-color: var(--td-error-color); // 色彩-功能-失败
@success-color: var(--td-success-color); // 色彩-功能-成功
// 基础颜色的扩展 用于 hover / 聚焦 / 禁用 / 点击 等状态
@brand-color-hover: var(--td-brand-color-hover); // hover态
@brand-color-focus: var(--td-brand-color-focus); // focus态包括鼠标和键盘
@brand-color-active: var(--td-brand-color-active); // 点击态
@brand-color-disabled: var(--td-brand-color-disabled); // 禁用态
@brand-color-light: var(--td-brand-color-light); // 浅色的选中态
// 警告色扩展
@warning-color-hover: var(--td-warning-color-hover);
@warning-color-focus: var(--td-warning-color-focus);
@warning-color-active: var(--td-warning-color-active);
@warning-color-disabled: var(--td-warning-color-disabled);
@warning-color-light: var(--td-warning-color-light);
// 失败/错误色扩展
@error-color-hover: var(--td-error-color-hover);
@error-color-focus: var(--td-error-color-focus);
@error-color-active: var(--td-error-color-active);
@error-color-disabled: var(--td-error-color-disabled);
@error-color-light: var(--td-error-color-light);
// 成功色扩展
@success-color-hover: var(--td-success-color-hover);
@success-color-focus: var(--td-success-color-focus);
@success-color-active: var(--td-success-color-active);
@success-color-disabled: var(--td-success-color-disabled);
@success-color-light: var(--td-success-color-light);
// 遮罩
@mask-active: var(--td-mask-active); // 遮罩-弹出
@mask-disabled: var(--td-mask-disabled); // 遮罩-禁用
// 背景色
@bg-color-page: var(--td-bg-color-page); // 色彩 - page
@bg-color-container: var(--td-bg-color-container); // 色彩 - 容器
@bg-color-container-hover: var(--td-bg-color-container-hover); // 色彩 - 容器 - hover
@bg-color-container-active: var(--td-bg-color-container-active); // 色彩 - 容器 - active
@bg-color-container-select: var(--td-bg-color-container-select); // 色彩 - 容器 - select
@bg-color-secondarycontainer: var(--td-bg-color-secondarycontainer); // 色彩 - 次级容器
@bg-color-secondarycontainer-hover: var(--td-bg-color-secondarycontainer-hover); // 色彩 - 次级容器 - hover
@bg-color-secondarycontainer-active: var(--td-bg-color-secondarycontainer-active); // 色彩 - 次级容器 - active
@bg-color-component: var(--td-bg-color-component); // 色彩 - 组件
@bg-color-component-hover: var(--td-bg-color-component-hover); // 色彩 - 组件 - hover
@bg-color-component-active: var(--td-bg-color-component-active); // 色彩 - 组件 - active
@bg-color-component-disabled: var(--td-bg-color-component-disabled); // 色彩 - 组件 - disabled
// TODO: 考虑是否在组件内部做判断,不增加额外变量
// 特殊组件背景色,目前只用于 button、input 组件多主题场景,浅色主题下固定为白色,深色主题下为 transparent 适配背景颜色
@bg-color-specialcomponent: var(--td-bg-color-specialcomponent);
// 文本颜色
@text-color-primary: var(--td-text-color-primary); // 色彩-文字-主要
@text-color-secondary: var(--td-text-color-secondary); // 色彩-文字-次要
@text-color-placeholder: var(--td-text-color-placeholder); // 色彩-文字-占位符/说明
@text-color-disabled: var(--td-text-color-disabled); // 色彩-文字-禁用
@text-color-anti: var(--td-text-color-anti); // 色彩-文字-反色
@text-color-brand: var(--td-text-color-brand); // 色彩-文字-品牌
@text-color-link: var(--td-text-color-link); // 色彩-文字-链接
// 分割线
@border-level-1-color: var(--td-border-level-1-color);
@component-stroke: var(--td-component-stroke);
// 边框
@border-level-2-color: var(--td-border-level-2-color);
@component-border: var(--td-component-border);
// shadow
// 基础/下层 投影 hover 使用的组件包括:表格 /
@shadow-1: var(--td-shadow-1);
// 中层投影 下拉 使用的组件包括:下拉菜单 / 气泡确认框 / 选择器 /
@shadow-2: var(--td-shadow-2);
// 上层投影(警示/弹窗)使用的组件包括:全局提示 / 消息通知
@shadow-3: var(--td-shadow-3);
// 内投影 用于弹窗类组件(气泡确认框 / 全局提示 / 消息通知)的内描边
@shadow-inset-top: var(--td-shadow-inset-top);
@shadow-inset-right: var(--td-shadow-inset-right);
@shadow-inset-bottom: var(--td-shadow-inset-bottom);
@shadow-inset-left: var(--td-shadow-inset-left);
@shadow-inset: @shadow-inset-top, @shadow-inset-right, @shadow-inset-bottom, @shadow-inset-left;
// 融合阴影
@shadow-2-inset: @shadow-2, @shadow-inset;
@shadow-3-inset: @shadow-3, @shadow-inset;
// Spacer
@spacer: 8px;
@spacer-s: @spacer * .5; // 间距-4
@spacer-l: @spacer * 1.5; // 间距-12
@spacer-1: @spacer; // 间距-8
@spacer-2: @spacer * 2; // 间距-16
@spacer-3: @spacer * 3; // 间距-24
@spacer-4: @spacer * 4; // 间距-32
@spacer-5: @spacer * 5; // 间距-大-40
@spacer-6: @spacer * 6; // 间距-大-48
@spacer-7: @spacer * 7; // 间距-大-48
@spacer-8: @spacer * 8; // 间距-大-48
@spacer-9: @spacer * 9; // 间距-大-48
@spacer-10: @spacer * 10; // 间距-大-80
// Font
@font-size: 10px;
@font-size-s: @font-size * 1.2; // 字号-五级字号
@font-size-base: @font-size * 1.4; // 字号-四级字号
@font-size-l: @font-size * 1.6; // 字号-三级字号
@font-size-xl: @font-size * 2; // 字号-二级字号
@font-size-xxl: @font-size * 3.6; // 字号-一级字号
// Line Height
@text-line-height: 1.5; // 行高-常规
@text-line-height-s: 20px; // 行高-对应五级文字
@text-line-height-base: 22px; // 行高-对应四级文字
@text-line-height-l: 24px; // 行高-对应三级文字
@text-line-height-xl: 28px; // 行高-对应二级文字
@text-line-height-xxl: 44px; //行高-对应一级文字
@font-family: PingFang SC, Microsoft YaHei, Arial Regular; // 字体-磅数-常规
@font-family-medium: PingFang SC, Microsoft YaHei, Arial Medium; // 字体-磅数-粗体
// Border Radius
@border-radius: 3px; // 圆角-全局
@border-radius-50: 50%; // 圆角-全圆角
// 表单相关
@form-height: 30px;
@form-text-color: @text-color-primary;
@form-bg-color: @bg-color-container;
@form-border-color: @border-level-2-color;
// 图标尺寸
@icon-default: 16px;
@icon-l: 24px;
// 滚动条颜色
@scrollbar-color: var(--td-scrollbar-color);
// 响应式断点
@screen-sm: 768px;
@screen-md: 992px;
@screen-lg: 1200px;
@screen-sm-min: @screen-sm;
@screen-md-min: @screen-md;
@screen-lg-min: @screen-lg;
@screen-sm-max: (@screen-md-min - 1px);
@screen-md-max: (@screen-lg-min - 1px);
// 动画
@anim-time-fn-easing: cubic-bezier(.38, 0, .24, 1);
@anim-time-fn-ease-out: cubic-bezier(0, 0, .15, 1);
@anim-time-fn-ease-in: cubic-bezier(.82, 0, 1, .9);
@anim-duration-base: .2s;
@anim-duration-moderate: .24s;
@anim-duration-slow: .28s;
// 统一管理各组件层级关系
@z-index-affix: 500;
@z-index-drawer: 1500;
@z-index-dialog: 2500;
@z-index-loading: 3500;
@z-index-message: 5000;
@z-index-Popup: 5500;
@z-index-Notification: 6000;