Commit 52ba9a54 authored by mxm-web-develop's avatar mxm-web-develop

init

parents
node_modules
.DS_Store
dist
dist-ssr
*.local
mining-manager
\ No newline at end of file
{
"recommendations": ["johnsoncodehk.volar"]
}
# Vue 3 + Typescript + Vite
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
}
export {}
// 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
declare module 'vue' {
export interface GlobalComponents {
Controllbar: typeof import('./src/components/controllbar.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
Tabs: typeof import('./src/components/tabs.vue')['default']
Workspace: typeof import('./src/components/workspace.vue')['default']
}
}
export { }
<!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>
{
"name": "mining-manager",
"version": "0.0.0",
"scripts": {
"dev": "vite --host",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^0.2.6",
"@vueuse/core": "^7.5.4",
"@vueuse/integrations": "^7.5.4",
"axios": "^0.25.0",
"crypto-js": "^4.1.1",
"downloadjs": "^1.4.7",
"element-plus": "^1.3.0-beta.8",
"vue": "^3.2.25",
"vue-router": "4"
},
"devDependencies": {
"@types/crypto-js": "^4.1.0",
"@types/downloadjs": "^1.4.3",
"@vitejs/plugin-vue": "^2.0.0",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"tailwindcss": "^3.0.16",
"typescript": "^4.4.4",
"unplugin-auto-import": "^0.5.11",
"unplugin-vue-components": "^0.17.14",
"vite": "^2.7.2",
"vue-tsc": "^0.29.8"
}
}
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
\ No newline at end of file
<script setup lang="ts">
import Layout from './Layout/index.vue';
import Workspace from './components/workspace.vue';
import Login from './components/login.vue';
import { getStore } from './untils/userinfo';
import { onMounted, ref, watch } from 'vue';
const userInfo = getStore('mining-manager')
// const userLogined = ref(false)
</script>
<template>
<Layout>
<template #content>
<Workspace v-if="userInfo"></Workspace>
<Login v-else></Login>
</template>
</Layout>
</template>
<script lang="ts" setup>
import Header from "./Header.vue";
import { ElMessage } from 'element-plus'
import { getStore,removeUser } from '../untils/userinfo';
import { ref } from "vue";
const appname = 'mining-manager'
const store = getStore(appname)
const dialogVisible = ref(false)
const logout = ()=>{
console.log(store?.token);
if(store){
removeUser(appname)
location.reload()
ElMessage({
message:'用户已经登出',
type:'info'
})
}
}
const toggleLogout = ()=>{
if(store){
dialogVisible.value = true
}
}
</script>
<template>
<div class="layout">
<el-dialog
v-model="dialogVisible"
title="用户登出"
width="30%"
>
<span>是否确认注销当前登陆用户</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button class=" bg-red-500" type="danger" @click="logout"
>登出</el-button
>
</span>
</template>
</el-dialog>
<div class="header h-14 w-full bg-gray-900 text-white">
<div class="flex items-center h-full w-full container mx-auto justify-between">
<div class="text-xl">矿池管理后台</div>
<div class="flex items-center cursor-pointer" @click="toggleLogout">
<el-avatar :size="32">
<img
:src="store? 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png':''"
/>
</el-avatar>
<div class="mx-3 text-sm">{{store?'admin':'用户名'}}</div>
</div>
</div>
</div>
<div class="content bg-gray-100 py-10 h-[calc(100vh_-_3.5rem)]">
<slot name="content"></slot>
</div>
</div>
</template>
import axios from 'axios'
import downloadjs from 'downloadjs'
import { getStore, USR } from '../untils/userinfo'
interface LoginData{
name: string
password: string
}
const instance = axios.create({
timeout:15000
})
instance.interceptors.request.use((config)=>{
if(config.url !== '/api/admin/login'){
config.headers= {
'Auth-Token':store?store.token?store.token:'':''
}
return config
}else{
return config
}
})
const store = getStore<USR<any>>('mining-manager')
export const doLogin = async (data:LoginData) => {
console.log(111);
return instance.post('/api/admin/login',
data
).then(res=>{
if(res.data.data.isSucc){
return res.headers['auth-token']
}
}).catch((err)=>{
console.log(err);
})
}
export const getPlatList = async()=>{
return instance.get('/api/admin/plat-list',).then(res=> res.data)
}
export interface GetTransListParams{
platId?:number
status?:number
start?:number
end?:number
addr?:string
page?:number
pageSize?:number
}
export const getTransList = async(params:GetTransListParams)=>{
params.page?params.page:params.page=1
params.pageSize?params.pageSize:params.pageSize=10
return instance.get('/api/admin/trans-list',{
params:params
}).then(res=>{
return res.data
})
}
export const downloadTxt = async(params:GetTransListParams)=>{
return instance.get('/download/unsigntxs',{
params: params
}).then(async res=>{
await downloadjs(res.data, 'report.txt', 'text/plain')
return
})
}
\ No newline at end of file
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { getPlatList } from "../api";
import { States } from "./types";
const value = ref("");
const value1 = ref("");
const datePick = ref("");
const address = ref("");
const platList = ref();
const props = defineProps<{
btnEnable:boolean
}>()
const emit = defineEmits(["eventEmitted", "dodownload"]);
const downloadTxt = () => {
emit("dodownload");
};
const hanleDataChanged = (v: any, t: any) => {
emit('eventEmitted',{
data:v,
type:t
})
};
const options = [
{
value: 1,
label: "准备中",
},
{
value: 2,
label: "已导出",
},
{
value: 3,
label: "失败",
},
{
value: 4,
label: "成功",
},
];
onMounted(async () => {
const res = await getPlatList();
platList.value = res.data.list;
});
</script>
<template>
<div class="flex bg-inherit relative w-full items-center justify-between">
<div class="flex flex-col w-full py-5 bg-slate-50 px-2">
<!-- <div class="label text-sm py-2 pl-2">精确查询</div> -->
<div class="flex justify-between w-full">
<div class="flex">
<el-select v-model="value" placeholder="精确地址" @change="hanleDataChanged($event,'platId')">
<el-option
v-for="item in platList"
:key="item.addr"
:label="item.ext ? item.ext : `${item.addr}...`"
:value="item.id"
>
</el-option>
</el-select>
<el-select v-model="value1" @change="hanleDataChanged($event,'status')" class="mx-2 w-36" placeholder="状态">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
<el-date-picker
v-model="datePick"
type="daterange"
range-separator="到"
start-placeholder="开始"
end-placeholder="截止"
@change="hanleDataChanged($event,'date')"
>
</el-date-picker>
<el-input
v-model="address"
@change="hanleDataChanged($event,'addr')"
class="mx-2"
maxlength="34"
placeholder="Please input"
show-word-limit
type="text"
>
</el-input>
</div>
<el-tooltip placement="top">
<template #content> 下载需要至少设定精准id和时间范围 </template>
<el-button
:disabled="props.btnEnable"
class="py-1 px-3 bg-blue-500 w-24 text-white text-sm rounded hover:bg-blue-300 transition-all duration-200"
@click="downloadTxt"
>
下载txt
</el-button>
</el-tooltip>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { doLogin } from '../api';
import { ElMessage } from 'element-plus'
import { now, useStorage } from '@vueuse/core'
import { setStore } from '../untils/userinfo';
const data = reactive({
name:'',
password:''
})
const login = async()=>{
const getToken = await doLogin(data)
if(getToken){
setStore({
app:'mining-manager',
timeStamp:now(),
token:getToken,
expire:'1d'
})
location.reload()
ElMessage({
message:"用户登陆成功",
type:"success"
})
}else{
ElMessage({
message:"登陆失败",
type:"error"
})
}
}
</script>
<template>
<div class="login w-96 mx-auto rounded shadow px-3 py-5 bg-white">
<div class=" text-xl border-b pb-3">
请先登陆
</div>
<div class=" flex flex-col pt-3">
<el-input
v-model="data.name"
class="py-3"
type="text"
placeholder="请输入用户名"
size="large"
/>
<el-input
v-model="data.password"
size="large"
type="password"
placeholder="请输入密码"
show-password
/>
</div>
<div class=" w-full flex justify-end pt-12">
<el-button type="primary" class=" bg-blue-500" @click="login">登陆</el-button>
</div>
</div>
</template>
\ No newline at end of file
<script lang="ts" setup>
const emit = defineEmits(['fresh'])
const doFresh = () =>emit('fresh')
</script>
<template>
<div class=" text-slate-400 mb-2 flex items-center justify-between">
<div>
用户管理 / 用户信息
</div>
<div class=" cursor-pointer" @click="doFresh">刷新 </div>
</div>
</template>
\ No newline at end of file
export enum States{
AddrReady =1,
AddrSend,
AddrFailed,
AddrSucc
}
\ No newline at end of file
<script lang="ts" setup>
import Tabs from './tabs.vue';
import Controllbar from './controllbar.vue';
import { useAxios } from '@vueuse/integrations/useAxios'
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { downloadTxt, getTransList } from '../api';
import dayjs from 'dayjs'
const reqData = reactive({
platId:0,
status:0,
start:0,
end:0,
page:1,
pageSize:10,
addr:''
})
const loading = ref(false)
const disableDownload = ref(true)
const table = ref([])
const pageController = reactive({
size:0
})
watch(reqData,async (newV)=>{
await initTable()
})
watch(reqData,(newV)=>{
if(newV.platId && newV.end && newV.start){
disableDownload.value = false
}else{
disableDownload.value =true
}
})
const columnSet = [
{
prop:'addr',
label:'用户地址',
},
{
prop:'amount',
label:'空投金额'
},
{
prop:'startTime',
label:'导出时间'
},
{
prop:'blockTime',
label:'完成时间'
},
{
prop:'status',
label:"状态"
}
]
const initTable = async ()=>{
loading.value= true
const res = await getTransList(reqData)
pageController.size = parseInt(res.data.count);
table.value = res.data.tx
loading.value = false
}
const childDataChange = (v:{data:any,type:string})=>{
switch (v.type) {
case 'addr':
reqData.addr = v.data
break;
case 'status':
reqData.status = v.data
break;
case 'date':
reqData.start = dayjs(v.data[0]).unix()
reqData.end = dayjs(v.data[1]).unix()
break;
case 'platId':
reqData.platId = parseInt(v.data)
break;
}
console.log(reqData);
}
const download = async()=> await downloadTxt(reqData)
const dateFilter = computed(()=>{
return function(v:any){
const time = parseInt(v)*1000
const formatDate = dayjs(time).format('YYYY-MM-DD')
if(formatDate){
return formatDate
}else{
return '无'
}
}
})
const statusFilter = computed((v)=>{
return function(v:any){
switch (v) {
case 1:
return '未发送'
case 2:
return '已导出'
case 3:
return '失败'
case 4:
return '成功'
}
}
})
const requestDataChanged = (v:any)=> reqData.page = v
onMounted(async ()=>{
await initTable()
})
</script>
<template>
<div class="container relative flex py-6 mx-auto min-h-full min-h-workspace bg-white">
<!-- <div class="left w-60 border-r px-3 ">
</div> -->
<div class="right relative flex-grow px-3 pb-28 ">
<Tabs @fresh="initTable"></Tabs>
<Controllbar @eventEmitted="childDataChange" @dodownload="download" :btnEnable="disableDownload"></Controllbar>
<div class="table-view w-full ">
<div class=" text-sm opacity-50 py-3 px-3">当前交易笔数:{{pageController.size}}</div>
<el-table
v-loading="loading"
element-loading-text="加载中,请等待..."
:data="table"
class=" container">
<el-table-column v-for="i in columnSet" :prop="i.prop" :label="i.label">
<template #default="scope" v-if="i.prop === 'status'">
{{statusFilter(scope.row.status)}}
</template>
<template #default="scope" v-if="i.prop === 'startTime'">
{{dateFilter(scope.row.startTime)}}
</template>
<template #default="scope" v-if="i.prop === 'blockTime'">
{{dateFilter(scope.row.blockTime)}}
</template>
</el-table-column>
</el-table>
</div>
<div class=" w-full mx-auto absolute left-0 bottom-0">
<el-pagination
:page-size="reqData.pageSize"
@current-change="requestDataChanged"
class=" w-96 mx-auto"
background
layout="prev, pager, next" :total="pageController.size"></el-pagination>
</div>
</div>
</div>
</template>
<style scoped>
.el-table__header{
width:100%;
}
</style>
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './style.css'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
@tailwind base;
@tailwind components;
@tailwind utilities;
\ No newline at end of file
import { AES,enc } from 'crypto-js'
export interface USR<U> {
app:string;
timeStamp:number;
userInfo?:U;
token?:string | undefined;
encode?:boolean;
/** 1s,1m,1h,1d */
expire?:string | undefined;
}
/**获取app相关的本地数据 */
const getStore =<T> (storeName:string) =>{
const store = localStorage.getItem(storeName)
const time = new Date().getTime()
if(!store){
console.log('没有找到用户信息');
return
}
let data = JSON.parse(store) as USR<T>
const expireDate = parseInt(data.expire?data.expire:'')
if(data.token){
data.token = data.encode?decodeToken(data.token,data.app):data.token
}
if(time>data.timeStamp + expireDate){
localStorage.removeItem(data.app)
return undefined
}
return data
}
const removeUser = (appname:string)=>{
localStorage.removeItem(appname)
}
/**设置app相关的数据 */
const setStore =<T> (data:USR<T>) =>{
const token = ecodeToken(data)
const input = {
app:data.app,
timeStamp:new Date().getTime(),
userInfo:data.userInfo?data.userInfo:{},
token:token,
encode:data.encode?data.encode:false,
expire:data.expire?setExpire(data.expire):undefined
}
localStorage.setItem(input.app,JSON.stringify(input))
return input
}
/**更新用户信息 */
const updateUserInfo=<T>(app:string, userInfo:T)=>{
const store = localStorage.getItem(app)
if(store){
const data = JSON.parse(store) as USR<T>
const newData = {
app:data.app,
timeStamp:data.timeStamp,
userInfo:userInfo,
token:data.token,
encode:data.encode,
/** 1s,1m,1h,1d */
expire:data.expire
}
localStorage.setItem(app,JSON.stringify(newData))
}else{
console.log('没有找到用户信息');
}
}
const ecodeToken = (data:USR<any>) =>{
if(!data.token) return undefined
if(!data.encode) return data.token
const encodeToken = AES.encrypt(data.token,data.app).toString()
return encodeToken
}
const decodeToken = (token:string,secret:string) =>{
const bytes = AES.decrypt(token, secret);
return bytes.toString(enc.Utf8)
}
const setExpire = (expireInput:string)=>{
const t = expireInput.charAt(expireInput.length-1)
const n = parseInt(expireInput.split(t)[0])
const d = 1000
switch(t.toLowerCase()){
case 's':
return n*d
case "m":
return n*60*d
case "h":
return n*60*60*d
case "d":
return n*60*60*24*d
}
}
export {setStore,getStore,updateUserInfo,removeUser}
\ No newline at end of file
module.exports = {
content: ["./src/**/*.{vue,ts}"],
theme: {
extend: {
},
minHeight:{
"workspace":'720px'
},
},
plugins: [],
}
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server:{
proxy:{
'/api': {
target: 'http://172.16.100.59:8091/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
'/download':{
target:'http://172.16.100.59:8091',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/download/, '')
}
}
}
})
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment