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