mirror of
https://github.com/Tencent/tdesign-vue-next-starter.git
synced 2024-11-10 07:48:22 +08:00
docs: init
This commit is contained in:
parent
0ddeb3f3e7
commit
9fba04f8a5
48
.eslintrc
Normal file
48
.eslintrc
Normal 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
4
.gitignore
vendored
|
@ -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
15
cache.dockerfile
Normal 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
86
package.json
Normal 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
18
src/main.ts
Normal 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');
|
201
src/pages/dashboard/base/constants.ts
Normal file
201
src/pages/dashboard/base/constants.ts
Normal 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,
|
||||
},
|
||||
];
|
317
src/pages/dashboard/base/index.vue
Normal file
317
src/pages/dashboard/base/index.vue
Normal 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>
|
115
src/pages/dashboard/detail/index.vue
Normal file
115
src/pages/dashboard/detail/index.vue
Normal 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>
|
186
src/pages/detail/deploy/index.vue
Normal file
186
src/pages/detail/deploy/index.vue
Normal 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>
|
210
src/pages/form/base/index.vue
Normal file
210
src/pages/form/base/index.vue
Normal 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>
|
199
src/pages/list/base/index.vue
Normal file
199
src/pages/list/base/index.vue
Normal 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>
|
240
src/pages/list/card/components/Card.vue
Normal file
240
src/pages/list/card/components/Card.vue
Normal 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>
|
121
src/pages/list/card/components/DialogForm.vue
Normal file
121
src/pages/list/card/components/DialogForm.vue
Normal 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>
|
206
src/pages/list/card/index.vue
Normal file
206
src/pages/list/card/index.vue
Normal 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>
|
320
src/pages/list/components/Table.vue
Normal file
320
src/pages/list/components/Table.vue
Normal 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>
|
91
src/pages/list/tree/index.vue
Normal file
91
src/pages/list/tree/index.vue
Normal 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>
|
164
src/pages/login/components/Login.vue
Normal file
164
src/pages/login/components/Login.vue
Normal 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>
|
74
src/pages/user/constants.ts
Normal file
74
src/pages/user/constants.ts
Normal 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
154
src/pages/user/index.vue
Normal 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">
|
||||
Hi,Image
|
||||
<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
250
src/style/variables.less
Normal 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;
|
Loading…
Reference in New Issue
Block a user