mirror of https://github.com/dtm-labs/dtm.git
38 changed files with 4277 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||
VITE_PROXY=[["/api", "http://localhost:36789"]] |
|||
VITE_ADMIN_VERSION="v0.0.0-dev" |
|||
@ -0,0 +1,68 @@ |
|||
module.exports = { |
|||
parser: "vue-eslint-parser", |
|||
"env": { |
|||
"browser": true, |
|||
"es2021": true |
|||
}, |
|||
"extends": [ |
|||
"plugin:@typescript-eslint/recommended", |
|||
"plugin:vue/vue3-recommended" |
|||
], |
|||
"parserOptions": { |
|||
"parser": "@typescript-eslint/parser", |
|||
"sourceType": "module", |
|||
ecmaFeature: { |
|||
jsx: true, |
|||
tsx: true |
|||
} |
|||
}, |
|||
"plugins": [ |
|||
"@typescript-eslint" |
|||
], |
|||
"rules": { |
|||
'vue/max-attributes-per-line': ['error', { |
|||
singleline: 10, |
|||
multiline: { |
|||
max: 1, |
|||
allowFirstLine: false |
|||
} |
|||
}], |
|||
'vue/singleline-html-element-content-newline': 'off', |
|||
'vue/multiline-html-element-content-newline': 'off', |
|||
'vue/html-indent': ['error', 4], |
|||
indent: ['error', 4], // 4行缩进
|
|||
'vue/script-indent': ['error', 4], |
|||
quotes: ['error', 'single'], // 单引号
|
|||
// 'vue/html-quotes': ['error', 'single'],
|
|||
semi: ['error', 'never'], // 禁止使用分号
|
|||
'space-infix-ops': ['error', { int32Hint: false }], // 要求操作符周围有空格
|
|||
'no-multi-spaces': 'error', // 禁止多个空格
|
|||
'no-whitespace-before-property': 'error', // 禁止在属性前使用空格
|
|||
'space-before-blocks': 'error', // 在块之前强制保持一致的间距
|
|||
'space-before-function-paren': ['error', 'never'], // 在“ function”定义打开括号之前强制不加空格
|
|||
'space-in-parens': ['error', 'never'], // 强制括号左右的不加空格
|
|||
'spaced-comment': ['error', 'always'], // 注释间隔
|
|||
'template-tag-spacing': ['error', 'always'], // 在模板标签及其文字之间需要空格
|
|||
'no-var': 'error', |
|||
'prefer-destructuring': ['error', { // 优先使用数组和对象解构
|
|||
array: true, |
|||
object: true |
|||
}, { |
|||
enforceForRenamedProperties: false |
|||
}], |
|||
'comma-dangle': ['error', 'never'], // 最后一个属性不允许有逗号
|
|||
'arrow-spacing': 'error', // 箭头函数空格
|
|||
'prefer-template': 'error', |
|||
'template-curly-spacing': 'error', |
|||
'quote-props': ['error', 'as-needed'], // 对象字面量属性名称使用引号
|
|||
'object-curly-spacing': ['error', 'always'], // 强制在花括号中使用一致的空格
|
|||
'no-unneeded-ternary': 'error', // 禁止可以表达为更简单结构的三元操作符
|
|||
'no-restricted-syntax': ['error', 'WithStatement', 'BinaryExpression[operator="in"]'], // 禁止with/in语句
|
|||
'no-lonely-if': 'error', // 禁止 if 语句作为唯一语句出现在 else 语句块中
|
|||
'newline-per-chained-call': ['error', { ignoreChainWithDepth: 2 }], // 要求方法链中每个调用都有一个换行符
|
|||
// 路径别名设置
|
|||
'no-submodule-imports': ['off', '/@'], |
|||
'no-implicit-dependencies': ['off', ['/@']], |
|||
'@typescript-eslint/no-explicit-any': 'off' // 类型可以使用any
|
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
# Logs |
|||
logs |
|||
*.log |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
pnpm-debug.log* |
|||
lerna-debug.log* |
|||
|
|||
node_modules |
|||
dist |
|||
dist-ssr |
|||
*.local |
|||
|
|||
# Editor directories and files |
|||
.vscode/* |
|||
!.vscode/extensions.json |
|||
.idea |
|||
.DS_Store |
|||
*.suo |
|||
*.ntvs* |
|||
*.njsproj |
|||
*.sln |
|||
*.sw? |
|||
@ -0,0 +1 @@ |
|||
# DTM-Admin |
|||
@ -0,0 +1,13 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<link rel="icon" href="/favicon.ico" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<title>Vite App</title> |
|||
</head> |
|||
<body> |
|||
<div id="app"></div> |
|||
<script type="module" src="/src/main.ts"></script> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,39 @@ |
|||
{ |
|||
"name": "dtm-admin", |
|||
"private": true, |
|||
"version": "0.0.0", |
|||
"scripts": { |
|||
"dev": "vite", |
|||
"build": "vite build", |
|||
"preview": "vite preview" |
|||
}, |
|||
"dependencies": { |
|||
"ant-design-vue": "^3.1.1", |
|||
"vue": "^3.2.25", |
|||
"vue-request": "^1.2.4" |
|||
}, |
|||
"devDependencies": { |
|||
"@types/node": "^17.0.23", |
|||
"@types/nprogress": "^0.2.0", |
|||
"@typescript-eslint/eslint-plugin": "^5.18.0", |
|||
"@typescript-eslint/parser": "^5.18.0", |
|||
"@vitejs/plugin-vue": "^2.3.0", |
|||
"autoprefixer": "^10.4.4", |
|||
"axios": "^0.26.1", |
|||
"eslint": "^8.13.0", |
|||
"eslint-plugin-vue": "^8.6.0", |
|||
"nprogress": "^0.2.0", |
|||
"pinia": "^2.0.0-rc.10", |
|||
"postcss": "^8.4.12", |
|||
"postcss-import": "^14.1.0", |
|||
"postcss-nested": "^5.0.6", |
|||
"postcss-simple-vars": "^6.0.3", |
|||
"tailwindcss": "^3.0.24", |
|||
"typescript": "^4.5.4", |
|||
"unplugin-vue-components": "^0.19.3", |
|||
"vite": "^2.9.1", |
|||
"vite-plugin-svg-icons": "^1.1.0", |
|||
"vue-router": "^4.0.13", |
|||
"vue-tsc": "^0.29.8" |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
module.exports = { |
|||
plugins: { |
|||
tailwindcss: {}, |
|||
autoprefixer: {}, |
|||
}, |
|||
} |
|||
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,6 @@ |
|||
<template> |
|||
<router-view /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"></script> |
|||
|
|||
@ -0,0 +1,22 @@ |
|||
import { AxiosResponse } from 'axios' |
|||
import request from '/@/utils/request' |
|||
|
|||
export interface IListAllTransactionsReq { |
|||
limit: number |
|||
position?: number |
|||
} |
|||
|
|||
export function listAllTransactions<T>(payload: IListAllTransactionsReq): Promise<AxiosResponse<T>> { |
|||
return request({ |
|||
url: '/api/dtmsvr/all', |
|||
method: 'get', |
|||
params: payload |
|||
}) |
|||
} |
|||
|
|||
export function getDtmVersion(): Promise<AxiosResponse<any>> { |
|||
return request({ |
|||
url: '/api/dtmsvr/version', |
|||
method: 'get', |
|||
}) |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
/* @tailwind base; */ |
|||
@tailwind components; |
|||
@tailwind utilities; |
|||
@ -0,0 +1,14 @@ |
|||
// generated by unplugin-vue-components
|
|||
// We suggest you to commit this file into source control
|
|||
// Read more: https://github.com/vuejs/vue-next/pull/3399
|
|||
import '@vue/runtime-core' |
|||
|
|||
declare module '@vue/runtime-core' { |
|||
export interface GlobalComponents { |
|||
RouterLink: typeof import('vue-router')['RouterLink'] |
|||
RouterView: typeof import('vue-router')['RouterView'] |
|||
SvgIcon: typeof import('./components/SvgIcon/index.vue')['default'] |
|||
} |
|||
} |
|||
|
|||
export {} |
|||
@ -0,0 +1,42 @@ |
|||
<template> |
|||
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" /> |
|||
<svg v-else :class="svgClass" aria-hidden="true"> |
|||
<use :xlink:href="iconName" /> |
|||
</svg> |
|||
</template> |
|||
<script setup lang='ts'> |
|||
|
|||
const props = defineProps({ |
|||
iconClass: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
className: { |
|||
type: String, |
|||
default: '' |
|||
} |
|||
}) |
|||
|
|||
const isExternal = /^(https?:|mailto:|tel:)/.test(props.iconClass) |
|||
const iconName = `#icon-${props.iconClass}` |
|||
const svgClass = props.className ? `svg-icon ${props.className}` : 'svg-icon ' |
|||
const styleExternalIcon = () => { |
|||
return { |
|||
mask: `url(${props.iconClass}) no-repeat 50% 50%`, |
|||
'-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%` |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="postcss" scoped> |
|||
.svg-icon { |
|||
vertical-align: -0.15em; |
|||
fill: currentColor; |
|||
overflow: hidden; |
|||
} |
|||
.svg-external-icon { |
|||
background-color: currentColor; |
|||
mask-size: cover !important; |
|||
display: inline-block; |
|||
} |
|||
</style> |
|||
@ -0,0 +1 @@ |
|||
# ICon Component |
|||
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,78 @@ |
|||
<template> |
|||
<a-layout> |
|||
<a-layout-sider width="200" style="background: #fff"> |
|||
<Sidebar /> |
|||
</a-layout-sider> |
|||
<a-layout style="padding: 0 24px 24px"> |
|||
<div v-if="layout.dtmVersion && layout.dtmVersion != dashVer" style="color:#f00"> !!! admin version: {{dashVer}} != dtm version: {{layout.dtmVersion}}. </div> |
|||
<a-breadcrumb style="margin: 16px 0"> |
|||
<a-breadcrumb-item>{{ mainNav }}</a-breadcrumb-item> |
|||
<a-breadcrumb-item>{{ subNav }}</a-breadcrumb-item> |
|||
<a-breadcrumb-item>{{ page }}</a-breadcrumb-item> |
|||
</a-breadcrumb> |
|||
<a-layout-content |
|||
:style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }" |
|||
> |
|||
<Content /> |
|||
</a-layout-content> |
|||
</a-layout> |
|||
</a-layout> |
|||
</template> |
|||
|
|||
<script setup lang='ts'> |
|||
import Sidebar from './components/sidebar.vue' |
|||
import Content from './components/content.vue' |
|||
import { useRoute } from 'vue-router' |
|||
import { useLayoutStore } from '../store/modules/layout' |
|||
import { IMenubarList } from '../type/store/layout' |
|||
import { findCurrentMenubar } from '../utils/util' |
|||
import { computed, onMounted, ref } from 'vue' |
|||
import { getDtmVersion } from '../api/api_dtm' |
|||
|
|||
|
|||
const dashVer = import.meta.env.VITE_ADMIN_VERSION |
|||
const route = useRoute() |
|||
const layout = useLayoutStore() |
|||
|
|||
const mainNav = computed(() => { |
|||
const currentMenubar = findCurrentMenubar(layout.getMenubar.menuList, true) |
|||
return currentMenubar?.meta.title |
|||
}) |
|||
|
|||
const subNav = computed(() => { |
|||
let subNav = '' |
|||
const currentMenubar = findCurrentMenubar(layout.getMenubar.menuList, true) |
|||
currentMenubar.children?.forEach(v => { |
|||
if (route.path.indexOf(v.path) !== -1) { |
|||
subNav = v.meta.title |
|||
} |
|||
}) |
|||
|
|||
return subNav |
|||
}) |
|||
|
|||
const page = computed(() => { |
|||
let page = '' |
|||
const currentMenubar = findCurrentMenubar(layout.getMenubar.menuList, true) |
|||
currentMenubar.children?.forEach(v => { |
|||
v.children?.forEach(vv => { |
|||
if (route.path == vv.path) { |
|||
page = vv.meta.title |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
return page |
|||
}) |
|||
|
|||
onMounted(() => { |
|||
layout.loadDtmVersion() |
|||
}) |
|||
|
|||
</script> |
|||
|
|||
<style lang="postcss" scoped> |
|||
.ant-layout.ant-layout-has-sider { |
|||
min-height: calc(100vh - 64px); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,17 @@ |
|||
<template> |
|||
<router-view v-slot="{Component}"> |
|||
<transition name="fade-transform" mode="out-in"> |
|||
<keep-alive> |
|||
<component :is="Component" :key="key" /> |
|||
</keep-alive> |
|||
</transition> |
|||
</router-view> |
|||
</template> |
|||
<script setup lang='ts'> |
|||
import { computed } from 'vue' |
|||
import { useRoute } from 'vue-router' |
|||
|
|||
const route = useRoute() |
|||
|
|||
const key = computed(() => route.path) |
|||
</script> |
|||
@ -0,0 +1,42 @@ |
|||
<template> |
|||
<div> |
|||
<a-layout-header class="header flex"> |
|||
<div class="flex items-center logo h-16"> |
|||
<svg-icon style="width: 36px; height: 36px; margin-right: 84px;" icon-class="svg-logo" /> |
|||
<span class="text-gray-400 text-lg">DTM admin {{ version }}</span> |
|||
</div> |
|||
<a-menu |
|||
v-model:selectedKeys="activeMenu" |
|||
theme="dark" |
|||
mode="horizontal" |
|||
:style="{ lineHeight: '64px' }" |
|||
@select="onOpenChange" |
|||
> |
|||
<a-menu-item v-for="v in getMenubar.menuList" :key="v.path">{{ v.meta.title }}</a-menu-item> |
|||
</a-menu> |
|||
</a-layout-header> |
|||
</div> |
|||
</template> |
|||
<script setup lang='ts'> |
|||
import { computed, ref } from 'vue'; |
|||
import { useRoute, useRouter } from 'vue-router'; |
|||
import { useLayoutStore } from '/@/store/modules/layout' |
|||
|
|||
const route = useRoute() |
|||
const router = useRouter() |
|||
const { getMenubar, getCurrentVersion } = useLayoutStore() |
|||
const firstRedirectPath = '/admin' |
|||
const version = import.meta.env.VITE_ADMIN_VERSION |
|||
|
|||
const activeMenu = ref([route.meta.activeMenu !== firstRedirectPath ? route.meta.activeMenu : '/']) |
|||
|
|||
const onOpenChange = (d:any) => { |
|||
router.push({ path: d.key }) |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.logo { |
|||
margin-right: 20px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,57 @@ |
|||
<template> |
|||
<a-menu |
|||
v-model:selectedKeys="activeMenu" |
|||
v-model:openKeys="openKeys" |
|||
mode="inline" |
|||
:style="{ height: '100%', borderRight: 0 }" |
|||
@select="onOpenChange" |
|||
> |
|||
<a-sub-menu v-for="v in filterSubMenubarData" :key="v.path"> |
|||
<template #title> |
|||
<span> |
|||
{{ v.meta.title }} |
|||
</span> |
|||
</template> |
|||
<a-menu-item v-for="vv in v.children" :key="vv.path">{{ vv.meta.title }}</a-menu-item> |
|||
</a-sub-menu> |
|||
</a-menu> |
|||
</template> |
|||
|
|||
<script setup lang='ts'> |
|||
import { ref, computed } from 'vue' |
|||
import { useRoute, useRouter } from 'vue-router' |
|||
import { useLayoutStore } from '/@/store/modules/layout' |
|||
import { IMenubarList } from '/@/type/store/layout' |
|||
import { findCurrentMenubar } from '/@/utils/util' |
|||
|
|||
const route = useRoute() |
|||
const router = useRouter() |
|||
const { getMenubar } = useLayoutStore() |
|||
|
|||
const filterSubMenubarData = computed(() => { |
|||
return findCurrentMenubar(getMenubar.menuList) as IMenubarList[] |
|||
}) |
|||
|
|||
const activeMenu = computed({ |
|||
get: () => { |
|||
return [route.path] |
|||
}, |
|||
set: (val) => { |
|||
// do nothing, just for eliminate warn |
|||
} |
|||
}) |
|||
|
|||
const openKeys = computed({ |
|||
get: () => { |
|||
const pos = route.path.lastIndexOf('/') |
|||
return [route.path.substring(0, pos)] |
|||
}, |
|||
set: (val) => { |
|||
// do onthing, just for eliminate warn |
|||
} |
|||
}) |
|||
|
|||
const onOpenChange = (d:any) => { |
|||
router.push({ path: d.key }) |
|||
} |
|||
</script> |
|||
@ -0,0 +1,12 @@ |
|||
<script setup lang="ts"> |
|||
import Header from './components/header.vue' |
|||
</script> |
|||
|
|||
<template> |
|||
<a-layout> |
|||
<a-layout-header class="header"> |
|||
<Header /> |
|||
</a-layout-header> |
|||
<router-view /> |
|||
</a-layout> |
|||
</template> |
|||
@ -0,0 +1,13 @@ |
|||
import { createApp } from 'vue' |
|||
import App from './App.vue' |
|||
import router from '/@/router/index' |
|||
import { pinia } from '/@/store' |
|||
import '/@/permission' |
|||
|
|||
import '/@/assets/css/index.css' |
|||
import 'virtual:svg-icons-register' |
|||
|
|||
const app = createApp(App) |
|||
app.use(router) |
|||
app.use(pinia) |
|||
app.mount('#app') |
|||
@ -0,0 +1,23 @@ |
|||
import router from '/@/router' |
|||
import { configure, start, done } from 'nprogress' |
|||
import { useLayoutStore } from './store/modules/layout' |
|||
|
|||
configure({ showSpinner: false }) |
|||
|
|||
const defaultRoutePath = '/' |
|||
|
|||
router.beforeEach((to) => { |
|||
start() |
|||
|
|||
const { getMenubar, concatAllowRoutes } = useLayoutStore() |
|||
|
|||
if (getMenubar.menuList.length === 0) { |
|||
concatAllowRoutes() |
|||
|
|||
return to.fullPath |
|||
} |
|||
}) |
|||
|
|||
router.afterEach(() => { |
|||
done() |
|||
}) |
|||
@ -0,0 +1,18 @@ |
|||
const modules = import.meta.glob('../views/**/**.vue') |
|||
const components: IObject<() => Promise<typeof import('*.vue')>> = { |
|||
LayoutHeader: (() => import('/@/layout/index.vue')) as unknown as () => Promise<typeof import('*.vue')> |
|||
} |
|||
|
|||
Object.keys(modules).forEach(key => { |
|||
const nameMatch = key.match(/^\.\.\/views\/(.+)\.vue/) |
|||
if (!nameMatch) return |
|||
if (nameMatch[1].includes('_Components')) return |
|||
const indexMatch = nameMatch[1].match(/(.*)\/Index$/i) |
|||
let name = indexMatch ? indexMatch[1] : nameMatch[1]; |
|||
[name] = name.split('/').splice(-1) |
|||
components[name] = modules[key] as () => Promise<typeof import('*.vue')> |
|||
}) |
|||
|
|||
export { |
|||
components |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' |
|||
import { IMenubarList } from '../type/store/layout'; |
|||
import { components } from './asyncRouter'; |
|||
|
|||
const Components: IObject<() => Promise<typeof import('*.vue')>> = Object.assign({}, components, { |
|||
LayoutHeader: (() => import('/@/layout/index.vue')) as unknown as () => Promise<typeof import('*.vue')>, |
|||
LayoutMain: (() => import('/@/layout/aside.vue')) as unknown as () => Promise<typeof import('*.vue')> |
|||
}) |
|||
|
|||
export const allowRouter: Array<IMenubarList> = [ |
|||
{ |
|||
name: 'Admin', |
|||
path: '/', |
|||
redirect: '/admin/global-transactions/all', |
|||
component: Components['LayoutHeader'], |
|||
meta: { title: 'Admin', activeMenu: '/admin' }, |
|||
children: [ |
|||
{ |
|||
name: 'Nodes', |
|||
path: '/admin/nodes', |
|||
component: Components['LayoutMain'], |
|||
meta: { title: 'Nodes' }, |
|||
children: [ |
|||
{ |
|||
name: 'LivingNodes', |
|||
path: '/admin/nodes/living', |
|||
component: Components['LivingNodes'], |
|||
meta: { title: 'Living Nodes' }, |
|||
} |
|||
] |
|||
}, { |
|||
name: 'GlobalTransactions', |
|||
path: '/admin/global-transactions', |
|||
component: Components['LayoutMain'], |
|||
meta: { title: 'Global Transactions' }, |
|||
children: [ |
|||
{ |
|||
name: 'AllTransactions', |
|||
path: '/admin/global-transactions/all', |
|||
component: Components['AllTransactions'], |
|||
meta: { title: 'All Transactions' }, |
|||
}, { |
|||
name: 'UnfinishedTransactions', |
|||
path: '/admin/global-transactions/unfinished', |
|||
component: Components['UnfinishedTransactions'], |
|||
meta: { title: 'Unfinished Transactions' }, |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
] |
|||
|
|||
const router = createRouter({ |
|||
history: createWebHistory(), |
|||
routes: allowRouter as RouteRecordRaw[] |
|||
}) |
|||
|
|||
export default router |
|||
@ -0,0 +1,2 @@ |
|||
import { createPinia } from 'pinia' |
|||
export const pinia = createPinia() |
|||
@ -0,0 +1,38 @@ |
|||
import { defineStore } from 'pinia'; |
|||
import { allowRouter } from '/@/router'; |
|||
import { ILayout, IMenubar, IMenubarList, IStatus } from '/@/type/store/layout'; |
|||
import { getDtmVersion } from '/@/api/api_dtm'; |
|||
|
|||
export const useLayoutStore = defineStore({ |
|||
id: 'layout', |
|||
state: (): ILayout => ({ |
|||
menubar: { |
|||
menuList: [] |
|||
}, |
|||
status: { |
|||
isLoading: false |
|||
}, |
|||
dtmVersion: "", |
|||
}), |
|||
getters: { |
|||
getMenubar(): IMenubar { |
|||
return this.menubar |
|||
}, |
|||
getStatus(): IStatus { |
|||
return this.status |
|||
}, |
|||
}, |
|||
actions: { |
|||
setRoutes(data: Array<IMenubarList>): void { |
|||
this.menubar.menuList = data |
|||
}, |
|||
concatAllowRoutes(): void { |
|||
allowRouter.reverse().forEach(v => this.menubar.menuList.unshift(v)) |
|||
}, |
|||
async loadDtmVersion(): Promise<void> { |
|||
const { data: { version } } = await getDtmVersion() |
|||
this.dtmVersion = version |
|||
console.log("dtm version: ", this.dtmVersion) |
|||
} |
|||
} |
|||
}) |
|||
@ -0,0 +1,17 @@ |
|||
export { } |
|||
declare global { |
|||
interface IObject<T> { |
|||
[index: string]: T |
|||
} |
|||
interface ImportMetaEnv { |
|||
VITE_APP_TITLE: string |
|||
VITE_PORT: number |
|||
VITE_PROXY: string |
|||
VITE_ADMIN_VERSION: string |
|||
} |
|||
interface ITable<T = any> { |
|||
data: Array<T> |
|||
next_position: number, |
|||
size: number |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
declare module '*.vue' { |
|||
import { defineComponent } from 'vue'; |
|||
const Component: ReturnType<typeof defineComponent> |
|||
export default Component |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
export interface IMenubar { |
|||
menuList: Array<IMenubarList> |
|||
} |
|||
|
|||
export interface ILayout { |
|||
menubar: IMenubar |
|||
status: IStatus |
|||
dtmVersion: string |
|||
} |
|||
|
|||
export interface IStatus { |
|||
isLoading: boolean |
|||
} |
|||
|
|||
export interface IMenubarList { |
|||
parentId?: number | string |
|||
id?: number | string |
|||
name: string |
|||
path: string |
|||
redirect?: string |
|||
meta: { |
|||
icon?: string |
|||
title: string |
|||
permission?: string[] |
|||
activeMenu?: string |
|||
hidden?: boolean |
|||
alwaysShow?: boolean |
|||
} |
|||
component: (() => Promise<typeof import('*.vue')>) | string |
|||
children?: Array<IMenubarList> |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import axios from 'axios' |
|||
|
|||
const request = axios.create({ |
|||
baseURL: import.meta.env.VITE_APP_API_BASE_URL as string | undefined, |
|||
timeout: 60000 |
|||
}) |
|||
|
|||
export default request |
|||
@ -0,0 +1,23 @@ |
|||
import { useRoute } from 'vue-router'; |
|||
import { IMenubarList } from '../type/store/layout'; |
|||
|
|||
export const findCurrentMenubar = (menuList: IMenubarList[], root?:boolean) => { |
|||
const route = useRoute() |
|||
let arr:IMenubarList[] | IMenubarList = [] |
|||
for (let i = 0; i < menuList.length; i++) { |
|||
const v = menuList[i]; |
|||
const usePath = v.meta.activeMenu || v.redirect || v.path; |
|||
const pos = usePath.lastIndexOf('/') |
|||
const rootPath = pos == 0 ? usePath : usePath.substring(0, pos) |
|||
if (route.path.indexOf(rootPath) !== -1) { |
|||
if (!root) { |
|||
arr = v.children as IMenubarList[] |
|||
} else { |
|||
arr = v |
|||
} |
|||
break |
|||
} |
|||
} |
|||
|
|||
return arr |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
<template> |
|||
<div> |
|||
<a-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="false"> |
|||
<template #bodyCell="{column, record}"> |
|||
<template v-if="column.key === 'status'"> |
|||
<span> |
|||
<a-tag :key="record.status" :color="record.status === 'succeed' ? 'green' : 'volcano'">{{ record.status.toUpperCase() }}</a-tag> |
|||
</span> |
|||
</template> |
|||
<template v-else-if="column.key === 'action'"> |
|||
<span> |
|||
<a class="mr-2 font-medium">Detail</a> |
|||
<a class="text-red-400 font-medium">Stop</a> |
|||
</span> |
|||
</template> |
|||
</template> |
|||
</a-table> |
|||
<div class="flex justify-center mt-2 text-lg pager" v-if="canPrev || canNext"> |
|||
<a-button type="text" :disabled="!canPrev" @click="handlePrevPage">Previous</a-button> |
|||
<a-button type="text" :disabled="!canNext" @click="handleNextPage">Next</a-button> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { IListAllTransactions, listAllTransactions } from '/@/api/api_dtm' |
|||
import { ref, onMounted, reactive, computed } from 'vue-demi' |
|||
import { usePagination } from 'vue-request' |
|||
import { TableProps } from 'ant-design-vue/es/vc-table/Table' |
|||
const columns = [ |
|||
{ |
|||
title: 'GID', |
|||
dataIndex: 'gid', |
|||
key: 'gid' |
|||
}, { |
|||
title: 'TransType', |
|||
dataIndex: 'trans_type', |
|||
key: 'trans_type' |
|||
}, { |
|||
title: 'Status', |
|||
dataIndex: 'status', |
|||
key: 'status' |
|||
}, { |
|||
title: 'Protocol', |
|||
dataIndex: 'protocol', |
|||
key: 'protocol' |
|||
}, { |
|||
title: 'CreateTime', |
|||
dataIndex: 'create_time', |
|||
key: 'create_time' |
|||
}, { |
|||
title: 'Action', |
|||
key: 'action' |
|||
} |
|||
] |
|||
|
|||
const pager = ref([""]) |
|||
const currentState = ref(1) |
|||
|
|||
const canPrev = computed(() => { |
|||
if (currentState.value === 1) { |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}) |
|||
|
|||
const canNext = computed(() => { |
|||
return data.value?.data.next_position !== "" |
|||
}) |
|||
|
|||
type Data = { |
|||
transactions: { |
|||
gid: string |
|||
trans_type: string |
|||
status: string |
|||
protocol: string |
|||
create_time: string |
|||
}[] |
|||
next_position: string |
|||
} |
|||
|
|||
const queryData = (params: IListAllTransactions) => { |
|||
return listAllTransactions<Data>(params) |
|||
} |
|||
|
|||
const { data, run, current, loading, pageSize } = usePagination(queryData, { |
|||
defaultParams: [ |
|||
{ |
|||
limit: 100, |
|||
} |
|||
], |
|||
pagination: { |
|||
pageSizeKey: 'limit' |
|||
} |
|||
}) |
|||
|
|||
const dataSource = computed(() => data.value?.data.transactions || []) |
|||
|
|||
const handlePrevPage = () => { |
|||
currentState.value -= 1; |
|||
const params = { |
|||
limit: 100 |
|||
} |
|||
if (pager.value[currentState.value - 1]) { |
|||
params.position = pager.value[currentState.value - 1] |
|||
} |
|||
run(params) |
|||
} |
|||
|
|||
const handleNextPage = () => { |
|||
currentState.value += 1; |
|||
if (currentState.value >= 2) { |
|||
pager.value[currentState.value - 1] = data.value?.data.next_position |
|||
} |
|||
|
|||
run({ |
|||
position: data.value?.data.next_position, |
|||
limit: 5 |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="postcss" scoped> |
|||
::v-deep .ant-pagination-item { |
|||
display: none; |
|||
} |
|||
.pager .ant-btn-text { |
|||
font-weight: 500; |
|||
}1 |
|||
.pager .ant-btn { |
|||
padding: 6px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,5 @@ |
|||
<template> |
|||
<h1>Coming Soon</h1> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
</script> |
|||
@ -0,0 +1,6 @@ |
|||
<template> |
|||
<h1>Coming Soon</h1> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
</script> |
|||
@ -0,0 +1,7 @@ |
|||
module.exports = { |
|||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], |
|||
theme: { |
|||
extend: {}, |
|||
}, |
|||
plugins: [], |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"target": "esnext", |
|||
"useDefineForClassFields": true, |
|||
"module": "esnext", |
|||
"moduleResolution": "node", |
|||
"strict": true, |
|||
"jsx": "preserve", |
|||
"sourceMap": true, |
|||
"resolveJsonModule": true, |
|||
"esModuleInterop": true, |
|||
"baseUrl": ".", |
|||
"paths": { |
|||
"/@/*": ["src/*"], |
|||
}, |
|||
"lib": ["esnext", "dom"], |
|||
"types": ["vite/client", "node"] |
|||
}, |
|||
"include": ["**/*.ts", "**/*.d.ts", "**/*.tsx", "**/*.vue"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
import { ConfigEnv, UserConfigExport } from 'vite'; |
|||
import path from 'path'; |
|||
import vue from '@vitejs/plugin-vue'; |
|||
import viteSvgIcons from 'vite-plugin-svg-icons'; |
|||
import Components from 'unplugin-vue-components/vite'; |
|||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' |
|||
|
|||
const setAlias = (alias: [string, string][]) => alias.map((v) => { |
|||
return { find: v[0], replacement: path.resolve(__dirname, v[1]) }; |
|||
}); |
|||
|
|||
export default ({ }: ConfigEnv): UserConfigExport => { |
|||
return { |
|||
resolve: { |
|||
alias: setAlias([['/@', 'src']]), |
|||
}, |
|||
plugins: [ |
|||
vue(), |
|||
viteSvgIcons({ |
|||
iconDirs: [path.resolve(process.cwd(), 'src/icons')], |
|||
symbolId: 'icon-[dir]-[name]' |
|||
}), |
|||
Components({ |
|||
dts: 'src/components.d.ts', |
|||
resolvers: [ |
|||
AntDesignVueResolver() |
|||
] |
|||
}) |
|||
], |
|||
server: { |
|||
port: 5000, |
|||
base: 'admin', |
|||
proxy: { |
|||
'/api': { |
|||
target: 'http://localhost:36789', |
|||
}, |
|||
} |
|||
}, |
|||
css: { |
|||
postcss: { |
|||
plugins: [ |
|||
require('autoprefixer'), |
|||
require('tailwindcss'), |
|||
require('postcss-simple-vars'), |
|||
require('postcss-import') |
|||
] |
|||
} |
|||
} |
|||
}; |
|||
}; |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue