Commit da43839f by GiottoMaster

init

parents
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100
* text=auto eol=lf
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig"
]
}
# vue-project
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```
/// <reference types="vite/client" />
import pluginVue from 'eslint-plugin-vue'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
},
pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
)
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ming-kr-admin</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "vue-project",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint": "eslint . --fix"
},
"dependencies": {
"axios": "^1.7.9",
"element-plus": "^2.9.6",
"font-awesome": "^4.7.0",
"less": "^4.2.2",
"pinia": "^3.0.1",
"vant": "^4.9.17",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.13.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-typescript": "^14.4.0",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.20.1",
"eslint-plugin-vue": "^9.32.0",
"jiti": "^2.4.2",
"npm-run-all2": "^7.0.2",
"typescript": "~5.7.3",
"vite": "^6.1.0",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.2"
}
}
<template>
<RouterView />
</template>
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<style lang="less">
#container {
display: flex;
#main {
flex: 1;
background: #f1f2f7;
}
}
.container {
margin-left: 280px;
margin-top: 56px;
padding: 30px 30px 0 30px;
nav {
height: 52px;
display: flex;
justify-content: space-between;
padding: 0 30px;
align-items: center;
background: #fff;
margin-bottom: 30px;
.title {
font-size: 18px;
}
.right {
display: flex;
}
.label {
color: #212529;
font-size: 16px;
&::before {
content: "/";
padding: 0 5px;
color: #ccc;
}
}
.link {
color: #878787;
font-size: 16px;
cursor: pointer;
text-decoration: none;
&:hover {
color: #212529;
}
&:focus-visible {
color: #212529;
}
}
}
.tag {
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
position: relative;
height: 50px;
border: 1px solid transparent;
border-radius: 4px;
.info {
display: flex;
align-items: center;
.btn {
margin-right: 5px;
}
}
.delete {
font-size: 22px;
font-weight: bold;
color: #999;
padding: 12px 20px;
position: absolute;
top: 0;
right: 0;
height: 50px;
line-height: 26px;
cursor: pointer;
font-weight: bold;
&:hover {
color: #000;
}
}
}
.success-tag {
background-color: #d4edda;
border-color: #c3e6cb;
.btn {
padding: 0 6px;
background: #28a745;
color: #fff;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
}
.content {
border-radius: 5px;
background: #fff;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.08);
.head {
font-size: 16px;
background: #00000008;
color: #212529;
font-weight: bold;
padding: 12px 20px;
border-radius: 5px 5px 0 0;
border-bottom: 1px solid rgba(0,0,0,.125);
}
.card {
padding: 20px;
.search {
display: flex;
align-items: center;
margin-bottom: 8px;
input {
width: 250px;
display: block;
height: calc(2.25rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15sease-in-out, box-shadow .15sease-in-out;
margin-right: 8px;
&:focus {
color: #495057;
background-color: #fff;
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}
}
.button {
padding: 6px 12px;
color: #fff;
background: #007bff;
border-radius: 3px;
cursor: pointer;
transition: all .15s ease-in-out;
&:hover {
background: #0069d9;
}
}
}
}
.footer {
height: 46px;
padding: 10px 20px;
background-color: #f0f3f5;
border-top: 1px solid #c2cfd6;
}
.table-bordered {
width: 100%;
margin-bottom: 1rem;
background-color: transparent;
border: 1px solid #dee2e6;
border-collapse: collapse;
}
table {
th {
padding: 12px;
border: 1px solid #dee2e6;
border-bottom: 2px solid #dee2e6;
font-weight: bold;
text-align: left;
}
tbody {
tr {
&:nth-child(odd) {
background-color: rgba(0, 0, 0, .05);
}
}
}
td {
padding: 12px;
border: 1px solid #dee2e6;
.button {
padding: 4px 8px;
background: #f0f0f0;
display: inline-block;
color: #878787;
border-radius: 3px;
cursor: pointer;
span, i {
font-size: 14px;
}
}
.btn {
font-size: 16px;
height: 38px;
padding: 6px 12px;
display: inline-block;
}
.text_btn {
color: #878787;
cursor: pointer;
&:hover {
color: #000;
}
}
.success {
border: 1px solid #28a745;
color: #28a745;
border-radius: 3px;
cursor: pointer;
transition: all .15s ease-in-out;
margin-right: 3px;
&:hover {
background: #28a745;
color: #fff;
}
}
.delete {
border: 1px solid #dc3545;
color: #dc3545;
border-radius: 3px;
cursor: pointer;
transition: all .15s ease-in-out;
&:hover {
background: #dc3545;
color: #fff;
}
}
}
}
.pagination {
display: flex;
margin: 20px 0;
justify-content: center;
>div {
margin: 0 5px;
padding: 5px 10px;
background: #f8f9fa;
border: 1px solid #ddd;
color: #007bff;
cursor: pointer;
&:hover {
background: #e2e6ea;
}
}
.disabled {
cursor: no-drop;
color: #6c757d;
&:hover {
background: #f8f9fa;
}
}
.active {
background: #007bff;
color: #fff;
&:hover {
background: #007bff;
}
}
}
}
}
input, select {
display: block;
height: calc(2.25rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15sease-in-out, box-shadow .15sease-in-out;
margin-right: 8px;
&:focus {
color: #495057;
background-color: #fff;
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}
}
</style>
import request from '@/utils/http.ts'
import config from '@/utils/config.ts'
// 登录
export function loginApi(data: any) {
return request({
url: `${config.baseURL}/admin/login`,
method: 'post',
data
})
}
// 会员列表
export function getMembersListApi(params: any) {
return request({
url: `${config.baseURL}/admin/member/list`,
method: 'get',
params
})
}
// 会员详情
export function getMembersDetailApi(params: any) {
return request({
url: `${config.baseURL}/admin/member/detail/${params}`,
method: 'get'
})
}
// 会员详情
export function updateMembersDetailApi(data: any) {
return request({
url: `${config.baseURL}/admin/member/update/${data.id}`,
method: 'post',
data: data
})
}
// 修改会员状态
export function changeMemberStatusApi(data: any) {
return request({
url: `${config.baseURL}/admin/member/change/state/${data}`,
method: 'post'
})
}
// 删除会员
export function deleteMemberApi(data: any) {
return request({
url: `${config.baseURL}/admin/member/destroy/${data}`,
method: 'post'
})
}
// 上传文件
export function uploadFileApi(data: any) {
return request({
url: `${config.baseURL}/admin/staking/upload`,
method: 'post',
headers: {
"Content-Type": "multipart/form-data",
},
data
})
}
// 持币列表
export function getCoinListApi(params: any) {
return request({
url: `${config.baseURL}/admin/member/coin/list/${params}`,
method: 'get'
})
}
// 修改持币信息
export function editCoinApi(data: any) {
return request({
url: `${config.baseURL}/admin/member/coin/update/${data.id}`,
method: 'post',
data
})
}
// 质押列表
export function getStakingListApi(params: any) {
return request({
url: `${config.baseURL}/admin/member/staking/${params}`,
method: 'get'
})
}
// 开始质押
export function startStakingApi(data: any) {
return request({
url: `${config.baseURL}/admin/member/staking/start/${data.id}`,
method: 'post',
data
})
}
// 结束质押
export function endStakingApi(data: any) {
return request({
url: `${config.baseURL}/admin/member/staking/end/${data.id}`,
method: 'post',
data
})
}
// 邀请码列表
export function getCodeListApi(params: any) {
return request({
url: `${config.baseURL}/admin/signup_code/list`,
method: 'get',
params
})
}
// 添加邀请码
export function addCodeApi(data: any) {
return request({
url: `${config.baseURL}/admin/signup_code/store`,
method: 'post',
data
})
}
// 删除邀请码
export function deleteCodeApi(data: any) {
return request({
url: `${config.baseURL}/admin/signup_code/destroy/${data}`,
method: 'post'
})
}
// 充值/提款列表
export function getMoneyInOutListApi(params: any) {
return request({
url: `${config.baseURL}/admin/money_in_out/list`,
method: 'get',
params
})
}
// 变更充值/提款状态
export function editMoneyInOutStatusApi(data: any) {
return request({
url: `${config.baseURL}/admin/money_in_out/change/state/${data.id}`,
method: 'post',
data
})
}
// 删除充值/提款
export function deleteMoneyInOutApi(data: any) {
return request({
url: `${config.baseURL}/admin/money_in_out/destroy/${data}`,
method: 'post'
})
}
// 质押产品列表
export function getStakingProductListApi(params: any) {
return request({
url: `${config.baseURL}/admin/staking/product/list`,
method: 'get',
params
})
}
// 质押产品详情
export function getStakingProductDetailApi(params: any) {
return request({
url: `${config.baseURL}/admin/staking/product/detail/${params}`,
method: 'get'
})
}
// 修改质押产品
export function editStakingProductApi(data: any) {
return request({
url: `${config.baseURL}/admin/staking/product/update/${data.id}`,
method: 'post',
data
})
}
// 删除质押产品
export function deleteStakingProductApi(data: any) {
return request({
url: `${config.baseURL}/admin/staking/product/destroy/${data}`,
method: 'post'
})
}
// 质押进度列表
export function getStakingUserListApi(params: any) {
return request({
url: `${config.baseURL}/admin/staking/user/list`,
method: 'get',
params
})
}
// 删除质押进度
export function deleteStakingUserApi(data: any) {
return request({
url: `${config.baseURL}/admin/staking/user/destroy/${data.id}`,
method: 'post',
data
})
}
// 质押历史列表
export function getStakingHistoryListApi(params: any) {
return request({
url: `${config.baseURL}/admin/staking/user/list/history`,
method: 'get',
params
})
}
// 质押奖励支付列表
export function getStakingRewardListApi(params: any) {
return request({
url: `${config.baseURL}/admin/staking/user/reward`,
method: 'get',
params
})
}
// // 质押奖励支付列表
// export function getStakingRewardListApi(params: any) {
// return request({
// url: `${config.baseURL}/pledge/upload`,
// method: 'get',
// params
// })
// }
/* color palette from <https://github.com/vuejs/theme> */
*{
box-sizing: border-box;
margin: 0;
font-size: 16px;
}
body {
min-height: 100vh;
color: var(--color-text);
background: #fff;
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif !important;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.el-pagination {
justify-content: center;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
@import './base.css';
* {
box-sizing: border-box;
}
\ No newline at end of file
<template>
<div id="footer">Copyright © sample</div>
</template>
<style scoped lang="less">
#footer {
height: 90px;
line-height: 90px;
background: #fff;
padding: 0 16px;
font-size: 14px;
color: #78909c;
margin-top: 60px;
}
</style>
<template>
<div id="header">
<div class="title">마이닝 파트너</div>
<div class="user">
<img src="/image/user.png" alt="" />
</div>
</div>
</template>
<script lang="ts" setup></script>
<style scoped lang="less">
#header {
height: 55px;
background: #fff;
border-bottom: 1px solid #e8e9ed;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
position: fixed;
top: 0;
width: 100%;
z-index: 1001;
.title {
font-size: 20px;
color: #878787;
}
.user {
position: relative;
cursor: pointer;
&::before {
content: "";
position: absolute;
right: -3px;
bottom: 10px;
background: #49a342;
width: 7px;
height: 7px;
border-radius: 100%;
border: 2px solid white;
z-index: 1;
}
}
img {
width: 40px;
}
}
</style>
<template>
<div id="Menu">
<div v-for="(menu, index) in MenuList" :key="index">
<div class="menu-name">{{ menu.name }}</div>
<div v-for="(item, menuIndex) in menu.list" :key="menuIndex" class="menu-item" @click="goPage(item)" :class="{ active: item.active?.indexOf(name) >= 0 }">
<div class="icon">
<i class="fa" :class="item.icon"></i>
</div>
<span class="menu-item-name">{{ item.name }}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useCounterStore } from '../../stores/counter'
const store = useCounterStore()
const router = useRouter()
const MenuList = [
{
name: '회원관리',
list: [
{ name: '회원 목록', icon: 'fa-id-badge', link: 'members', active: ['members', 'detail', 'coin', 'staking'] },
{ name: '가입코드(파트너) 목록', icon: 'fa-id-badge', link: 'code', active: ['code'] },
{ name: '입/출금신청 목록', icon: 'fa-id-badge', link: 'money_history', active: ['money_history'] }
]
},
{
name: '스테이킹',
list: [
{ name: '스테이킹 상품목록', icon: 'fa-info', link: 'product', active: ['product', 'stakingDetail'] },
{ name: '스테이킹 진행목록', icon: 'fa-info', link: 'user', active: ['user'] },
{ name: '스테이킹 기록', icon: 'fa-info', link: 'history', active: ['history'] },
{ name: '스테이킹보상 지급 내역', icon: 'fa-info', link: 'reward', active: ['reward'] }
]
},
{
name: 'ACCOUNT',
list: [
{ name: '로그아웃', icon: 'fa-sign-out', link: 'login', active: ['login'] }
]
}
]
const name = ref(null)
const goPage = (e) => {
store.keepList = ref([])
setTimeout(() => {
store.keepList = store.keepDefaultList
router.push({ name: e.link })
name.value = e.link
}, 0)
}
onMounted(() => {
name.value = router.currentRoute.value.name
console.log(name.value)
})
</script>
<style scoped lang="less">
#Menu {
width: 280px;
height: calc(100vh - 56px);
box-shadow: 1px 0 20px rgba(0, 0, 0, 0.08);
background: #fff;
position: fixed;
top: 55px;
z-index: 1000;
.menu-name {
height: 50px;
line-height: 50px;
padding: 0 30px;
font-weight: bold;
font-size: 14px;
}
.menu-item {
height: 46px;
display: flex;
align-items: center;
padding: 0 30px;
color: #607d8b;
font-size: 14px;
cursor: pointer;
span, i {
font-size: 14px;
&::before {
font-size: 14px;
}
}
&:hover {
color: #03a9f3;
}
.icon {
width: 55px;
}
}
.active {
background: #fafafa;
color: #03a9f3;
}
}
</style>
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import Vant, { ConfigProvider } from 'vant'
import 'font-awesome/css/font-awesome.min.css'
const app = createApp(App)
app.use(ElementPlus)
app.use(createPinia())
app.use(router).use(Vant).use(ConfigProvider)
app.mount('#app')
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
redirect: 'members/index'
},
{
path: '/members',
component: () => import('../views/main/main.vue'),
children: [
{
path: 'index',
component: () => import('../views/members/members.vue'),
name: 'members',
meta: {
KeepAlive: true
}
},
{
path: 'detail',
component: () => import('../views/members/detail.vue'),
name: 'detail'
},
{
path: 'coin',
component: () => import('../views/members/coin.vue'),
name: 'coin'
},
{
path: 'staking',
component: () => import('../views/members/staking.vue'),
name: 'staking'
},
{
path: 'code',
component: () => import('../views/members/code.vue'),
name: 'code'
},
{
path: 'addCode',
component: () => import('../views/members/addCode.vue'),
name: 'addCode'
},
{
path: 'money_history',
component: () => import('../views/members/money_history.vue'),
name: 'money_history'
},
]
},
{
path: '/staking',
component: () => import('../views/main/main.vue'),
children: [
{
path: 'product',
component: () => import('../views/staking/product.vue'),
name: 'product'
},
{
path: 'history',
component: () => import('../views/staking/history.vue'),
name: 'history'
},
{
path: 'user',
component: () => import('../views/staking/user.vue'),
name: 'user'
},
{
path: 'reward',
component: () => import('../views/staking/reward.vue'),
name: 'reward'
},
{
path: 'detail',
component: () => import('../views/staking/detail.vue'),
name: 'stakingDetail'
}
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/login/login.vue')
}
],
})
export default router
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { useRouter } from 'vue-router'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
const router = useRouter()
const keepDefaultList = ref(['MemberIndex', 'MoneyHistory', 'product', 'StakingUser', 'StakingHistory', 'StakingReward'])
const keepList = ref(['MemberIndex', 'MoneyHistory', 'product', 'StakingUser', 'StakingHistory', 'StakingReward'])
return { count, doubleCount, increment, router, keepList, keepDefaultList }
})
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
// 前往下一个页面
export function goPage(e: string) {
store.router.push(e)
}
// 前往下一个页面
export function goBack() {
history.back(-1)
}
const baseURL = 'https://api.kaiawellet.com'
const uploadURL = 'https://ple.lovebridgess.com'
export default {
/* 服务器地址 */
baseURL,
uploadURL
}
import axios from 'axios'
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
// 创建axios实例
const service = axios.create({
timeout: 20000 // 请求超时时间
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
console.log(config)
// 可以在这里添加请求头等信息
const token = localStorage.getItem('token')
config.headers.Authorization = token ? `Bearer ${token}` : undefined
return config
},
(error) => {
// 请求错误处理
console.log(error) // for debug
Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做处理,例如只返回data部分
const res = response.data
// 根据业务判断是否需要进行错误处理
if (res.code === 401) {
// 清除token 以及用户信息
localStorage.removeItem('token')
setTimeout(() => {
store.router.push('/login')
}, 500)
}else if (res.code === 500) {
alert(res.msg)
}
return res
},
(error) => {
console.log(error)
const res = error.response.data
// token 过期处理
if (res.code === 401) {
// 清除token 以及用户信息
localStorage.removeItem('token')
setTimeout(() => {
router.push('login')
}, 1500)
return Promise.reject(error)
}
// 响应错误处理
console.log('err' + error) // for debug
// uni.showToast({
// title: error.msg || 'error',
// icon: 'none'
// })
return Promise.reject(error)
}
)
export default service
<template>
<div id="login">
<div class="warp">
<header>마이닝</header>
<div class="card">
<input type="text" v-model="params.username" placeholder="아이디를 입력해주세요." @keypress.enter="submit"/>
<input type="password" v-model="params.password" placeholder="비밀번호를 입력해주세요." @keypress.enter="submit" />
<button @click="submit">로그인</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { loginApi } from '../../api/api'
const router = useRouter()
const params = ref({
username: '',
password: ''
})
const submit = async () => {
if(params.value.username && params.value.password) {
const res = await loginApi(params.value)
console.log(res)
if(res.code === 200) {
localStorage.setItem('role', res.data.role)
localStorage.setItem('token', res.data.token)
router.push('members/index')
}
}
}
</script>
<style lang="less" scoped>
#login {
overflow: hidden;
width: 100%;
.warp {
margin: 115px auto;
width: 540px;
}
header {
color: #878787;
margin-bottom: 15px;
cursor: pointer;
text-align: center;
}
.card {
height: 212px;
padding: 30px 30px 20px;
background: #fff;
}
button {
height: 54px;
line-height: 54px;
text-align: center;
color: #fff;
border: 0;
width: 100%;
background: #28a745;
border-radius: 3px;
cursor: pointer;
&:hover {
background: #218838;
}
}
}
input {
width: 100%;
display: block;
height: calc(2.25rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
margin-bottom: 16px;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15sease-in-out, box-shadow .15sease-in-out;
margin-right: 8px;
&:focus {
color: #495057;
background-color: #fff;
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}
}
</style>
<template>
<Header />
<div id="container">
<Menu />
<!-- <span style="position: fixed; z-index: 999999; left: 0; top: 0;width: 100%; text-align: center">{{ store.keepList }}</span> -->
<div id="main">
<router-view v-slot="{ Component }">
<keep-alive :include="store.keepList">
<component :is="Component" v-if="route?.meta?.KeepAlive"/>
</keep-alive>
<component :is="Component" v-if="!route?.meta?.KeepAlive"/>
</router-view>
</div>
</div>
</template>
<script setup lang="ts">
import { RouterView, useRoute } from 'vue-router'
import Header from '../../components/Header/Header.vue'
import Menu from '../../components/Menu/Menu.vue'
import Footer from '../../components/Footer/Footer.vue'
import { useCounterStore } from '../../stores/counter'
const store = useCounterStore()
const route = useRoute()
</script>
<template>
<div class="container">
<nav>
<div class="title">가입코드 추가</div>
<div class="right">
<router-link to="" class="link">회원관리</router-link>
<router-link to="/members/code" class="link label">가입코드</router-link>
<div class="label">가입코드 추가</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">가입코드(파트너관리자) 추가</div>
<div class="card">
<div class="row">
<div class="label">가입코드</div>
<div class="flex">
<input type="text" v-model="params.invite_code" placeholder="가입코드" @keypress.enter="addCode">
</div>
</div>
<div class="row">
<div class="label">아이디</div>
<div class="flex">
<input type="text" v-model="params.username" placeholder="아이디" @keypress.enter="addCode">
</div>
</div>
<div class="row">
<div class="label">성함</div>
<div class="flex">
<input type="text" v-model="params.name" placeholder="성함" @keypress.enter="addCode">
</div>
</div>
<div class="row">
<div class="label">비밀번호</div>
<div class="flex">
<input type="text" v-model="params.password" placeholder="비밀번호" @keypress.enter="addCode">
</div>
</div>
</div>
<div class="footer">
<div class="button" @click="addCode">
<i class="fa fa-dot-circle-o"></i>
<span> Submit</span>
</div>
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { addCodeApi } from '../../api/api'
import { ref } from 'vue'
const tagTitle = ref('')
const successTagShow = ref(false)
// const getMembersDetail = async() => {
// const res = await getStakingProductListApi(params.value)
// userDetail.value = res.data.data.filter(item => item.id == userId.value)[0]
// }
const params = ref({
username: '',
invite_code: '',
password: '',
name: '',
})
const loading = ref(false)
const addCode = async ()=> {
if(loading.value) {
return
}
loading.value = true
const addStatus = await window.confirm('가입코드를 추가하시겠습니까?')
if(addStatus) {
const res = await addCodeApi(params.value)
loading.value = false
if(res.code === 200) {
tagTitle.value = '처리되었습니다.'
successTagShow.value = true
params.value.username = ''
params.value.invite_code = ''
params.value.password = ''
params.value.name = ''
}
}
loading.value = false
}
</script>
<style lang="less" scoped>
.row {
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.label {
flex: 3
}
.text {
flex: 9
}
input {
flex: 9;
width: 100%;
display: block;
height: calc(2.25rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15sease-in-out, box-shadow .15sease-in-out;
margin-right: 8px;
&:focus {
color: #495057;
background-color: #fff;
border-color: #80bdff !important;
outline: 0;
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}
}
.disabled {
background: #e9ecef;
}
.disabled2{
background: rgba(239, 239, 239, 0.3);
color: #999;
width: 210px !important;
}
.flex {
flex: 9;
padding: 0.375rem 0.75rem;
display: flex;
input {
flex: none;
&:focus {
background-color: #fff;
border-color: transparent;
outline: 0;
box-shadow: none;
}
}
.file {
width: inherit;
border: none;
}
}
}
.footer {
.button {
cursor: pointer;
i, span {
color:#868e96;
font-size: 12px;
}
}
}
</style>
<template>
<div class="container">
<nav>
<div class="title">가입코드(파트너) 목록</div>
<div class="right">
<router-link to="" class="link">회원관리</router-link>
<div class="label">가입코드</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">
<span>전체 가입코드(파트너) 목록</span>
<div class="btn" @click="goPage('/members/addCode')">추가</div>
</div>
<div class="card">
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>가입코드</th>
<th>아이디</th>
<th>이름</th>
<!-- <th>패스워드</th> -->
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in codeList" :key="index">
<td>{{ item.invite_code }}</td>
<td>{{ item.username }}</td>
<td>{{ item.name }}</td>
<td>
<div class="btn delete" @click="deleteCode(item)">삭제</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="pagination">
<div class="prev" :class="{ disabled: codeData.current_page === 1 }" @click="switchPage(codeData.current_page - 1)">« 이전</div>
<div class="page" :class="{ active: codeData.current_page === item }" v-for="(item, index) in codeData.last_page" :key="index" @click="switchPage(item)">{{ item }}</div>
<div class="next" :class="{ disabled: codeData.current_page === codeData.last_page }" @click="switchPage(codeData.current_page + 1)">다음 »</div>
</div> -->
<el-pagination background layout="prev, pager, next" :total="codeData.total" :current-page="params.page" @current-change="switchPage" prev-text="« 이전" next-text="다음 »" />
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getCodeListApi, deleteCodeApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { goPage } from '../../utils/common'
const codeData = ref({})
const codeList = ref([])
const params = ref({
page: 1,
limit: 10
})
const tagTitle = ref('')
const successTagShow = ref(false)
const getCodeList = async() => {
const res = await getCodeListApi(params.value)
codeData.value = res.data
codeList.value = res.data.data
}
onMounted(() => {
getCodeList()
})
// 切换页面
const switchPage = (page: number) => {
if(page < 1 || page === codeData.value.current_page || page > codeData.value.last_page) {
return
}
params.value.page = page
getCodeList()
}
//删除邀请码
const deleteCode = async(item) => {
const del = await window.confirm('회원을 삭제하시겠습니까?')
if(del) {
const res = await deleteCodeApi(item.id)
if(res.code === 200) {
getCodeList()
tagTitle.value = '회원이 삭제되었습니다.'
successTagShow.value = true
}
}
}
</script>
<style lang="less" scoped>
.head {
display: flex;
justify-content: space-between;
.btn {
color: #878787;
cursor: pointer;
font-weight: 100;
&:hover {
color: #000;
}
}
}
</style>
<template>
<div class="container">
<nav>
<div class="title">보유코인</div>
<div class="right">
<router-link to="" class="link">회원관리</router-link>
<router-link to="/members/index" class="link label">회원목록</router-link>
<div class="label">보유코인</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">{{ userDetail.username }} <span style="font-weight: 100">님 보유코인현황</span></div>
<div class="card">
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th style="width: 500px">코인</th>
<th> 보유수량</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in coinList" :key="index">
<td>{{ item.symbol }}</td>
<td>
<div class="flex">
<input type="text" v-model="item.nums" @keypress.enter="editCoin(item)">
<div class="btn success" @click="editCoin(item)" >변경</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getMembersDetailApi, getCoinListApi, editCoinApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { useCounterStore } from '../../stores/counter'
const store = useCounterStore()
const userId = ref(null)
onMounted(() => {
userId.value = store.router.currentRoute.query?.id
getMembersDetail()
getCoinList()
})
// 获取详情
const userDetail = ref({})
const tagTitle = ref('')
const successTagShow = ref(false)
const getMembersDetail = async() => {
const res = await getMembersDetailApi(userId.value)
userDetail.value = res.data
}
// 持币列表
const coinList = ref([])
const getCoinList = async() => {
const res = await getCoinListApi(userId.value)
coinList.value = res.data
}
// 修改持币信息
const editCoin = async(e) => {
const edit = await window.confirm('변경하시겠습니까?')
if(edit) {
const params = {
symbol: e.symbol,
nums: e.nums,
id: userId.value
}
const res = await editCoinApi(params)
if(res.code === 200) {
getCoinList()
tagTitle.value = '처리되었습니다.'
successTagShow.value = true
}
}
}
</script>
<style lang="less" scoped>
.flex{
display: flex;
input {
flex: 1;
margin-right: 3px;
}
}
</style>
<template>
<div class="container">
<nav>
<div class="title">회원상세</div>
<div class="right">
<router-link to="" class="link">회원관리</router-link>
<!-- <router-link @click="goBack" to="/members/index" class="link label">회원목록</router-link> -->
<router-link to="" @click="goBack" class="link label">회원목록</router-link>
<div class="label">{{ userDetail.username }}</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">{{ userDetail.username }} <span style="font-weight: 100">정보</span></div>
<div class="card">
<div class="row">
<div class="label">아이디</div>
<div class="text">{{ userDetail.username }}</div>
</div>
<div class="row">
<div class="label">패스워드</div>
<input type="text" v-model="userDetail.show_pwd" @keypress.enter="updateMembersDetail">
</div>
<div class="row">
<div class="label">이름</div>
<input type="text" v-model="userDetail.name" @keypress.enter="updateMembersDetail">
</div>
<div class="row">
<div class="label">휴대폰번호</div>
<input type="text" v-model="userDetail.phone" @keypress.enter="updateMembersDetail">
</div>
<div class="row">
<div class="label">은행</div>
<input type="text" v-model="userDetail.bank_name" @keypress.enter="updateMembersDetail">
</div>
<div class="row">
<div class="label">계좌번호</div>
<input type="text" v-model="userDetail.bank_card_no" @keypress.enter="updateMembersDetail">
</div>
<div class="row">
<div class="label">예금주</div>
<input type="text" v-model="userDetail.bank_username" @keypress.enter="updateMembersDetail">
</div>
<div class="row">
<div class="label">보유현금</div>
<input type="text" v-model="userDetail.balance" @keypress.enter="updateMembersDetail">
</div>
<div class="row">
<div class="label">출금 허용 </div>
<select style="width: 100%" v-model="userDetail.withdraw_state">
<option :value="1">허용</option>
<option :value="2">거절</option>
</select>
</div>
</div>
<div class="footer" @click="updateMembersDetail">
<div class="button">
<i class="fa fa-dot-circle-o"></i>
<span> Submit</span>
</div>
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getMembersDetailApi, updateMembersDetailApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { useCounterStore } from '../../stores/counter'
import { goBack } from '../../utils/common'
const store = useCounterStore()
const userId = ref(null)
onMounted(() => {
userId.value = store.router.currentRoute.query?.id
getMembersDetail()
})
const userDetail = ref({})
const tagTitle = ref('')
const successTagShow = ref(false)
const getMembersDetail = async() => {
const res = await getMembersDetailApi(userId.value)
userDetail.value = res.data
}
const updateMembersDetail = async() => {
userDetail.value.password = userDetail.value.show_pwd
const res = await updateMembersDetailApi(userDetail.value)
if(res.code === 200) {
tagTitle.value = res.msg
successTagShow.value = true
getMembersDetail()
}
}
</script>
<style lang="less" scoped>
.row {
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.label {
flex: 3
}
.text {
flex: 9
}
input,select {
flex: 9;
width: 100%;
display: block;
height: calc(2.25rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15sease-in-out, box-shadow .15sease-in-out;
margin-right: 8px;
&:focus {
color: #495057;
background-color: #fff;
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}
}
}
.footer {
.button {
cursor: pointer;
i, span {
color:#868e96;
font-size: 12px;
}
}
}
</style>
<template>
<div class="container">
<nav>
<div class="title">회원목록</div>
<div class="right">
<router-link to="" class="link">회원관리</router-link>
<div class="label">회원목록</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">
<span>전체회원목록</span>
<span>{{ membersData.total }}</span>
</div>
<div class="card">
<div class="search">
<input type="text" placeholder="검색어를 입력하세요." v-model="params.search" @keypress.enter="getMembersList()">
<div class="button" @click="getMembersList()">검색</div>
</div>
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>추천코드</th>
<th>이름</th>
<th>아이디</th>
<th>비밀번호</th>
<th>계좌정보</th>
<th>휴대폰번호</th>
<th>보유금액</th>
<th>보유코인</th>
<th>입출금내역</th>
<th>스테이킹</th>
<th>가입일자</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in membersList" :key="index">
<td>{{ item.invite_code }}</td>
<td>
<div class="text_btn" @click="goPage(`/members/detail?id=${item.id}`)">{{ item.name }}</div>
</td>
<td>{{ item.username }}</td>
<td>{{ item.show_pwd }}</td>
<td>{{ item.bank_name }} {{ item.bank_card_no }}({{ item.bank_username }})</td>
<td>{{ item.phone || 'None' }}</td>
<td>{{ item.balance }}</td>
<td>
<div class="button" @click="goPage(`/members/coin?id=${item.id}`)">
<i class="fa fa-link"></i>
<span> 조회</span>
</div>
</td>
<td>
<div class="button" @click="goPage(`/members/money_history?name=${item.username}`)">
<i class="fa fa-link"></i>
<span> 조회</span>
</div>
</td>
<td>
<div class="button" @click="goPage(`/members/staking?id=${item.id}`)">
<i class="fa fa-link"></i>
<span> 조회</span>
</div>
</td>
<td>{{ item.created_at }}</td>
<td>
<div class="btn success" @click="changeMemberStatus(item)" v-if="item.state === 2">승인</div>
<div class="btn delete" @click="deleteMember(item)">회원삭제</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="pagination">
<div class="prev" :class="{ disabled: membersData.current_page === 1 }" @click="switchPage(membersData.current_page - 1)">« 이전</div>
<div class="page" :class="{ active: membersData.current_page === item }" v-for="(item, index) in membersData.last_page" :key="index" @click="switchPage(item)">{{ item }}</div>
<div class="next" :class="{ disabled: membersData.current_page === membersData.last_page }" @click="switchPage(membersData.current_page + 1)">다음 »</div>
</div> -->
<el-pagination background layout="prev, pager, next" :total="membersData.total" :current-page="params.page" @current-change="switchPage" prev-text="« 이전" next-text="다음 »" />
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getMembersListApi, changeMemberStatusApi, deleteMemberApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { goPage } from '../../utils/common'
defineOptions({
name: 'MemberIndex',
})
const membersData = ref({})
const membersList = ref([])
const params = ref({
search: '',
page: 1,
limit: 10
})
const tagTitle = ref('')
const successTagShow = ref(false)
const getMembersList = async() => {
const res = await getMembersListApi(params.value)
membersData.value = res.data
membersList.value = res.data.data
}
onMounted(() => {
getMembersList()
})
// 切换页面
const switchPage = (page: number) => {
if(page < 1 || page === membersData.value.current_page || page > membersData.value.last_page) {
return
}
params.value.page = page
getMembersList()
}
// 修改会员状态
const changeMemberStatus = async (item) => {
const change = await window.confirm('회원을 승인하시겠습니까?')
if(change) {
const res = await changeMemberStatusApi(item.id)
if(res.code === 200) {
getMembersList()
tagTitle.value = '완료되었습니다.'
successTagShow.value = true
}
}
}
//删除会员
const deleteMember = async(item) => {
const del = await window.confirm('회원을 삭제하시겠습니까?')
if(del) {
const res = await deleteMemberApi(item.id)
if(res.code === 200) {
getMembersList()
tagTitle.value = '회원이 삭제되었습니다.'
successTagShow.value = true
}
}
}
</script>
<style scoped>
.head {
display: flex;
justify-content: space-between;
}
</style>
<template>
<div class="container">
<nav>
<div class="title">입출금 신청</div>
<div class="right">
<router-link to="" class="link">입출금</router-link>
<div class="label">입출금신청 내역</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">입출금신청 내역</div>
<div class="card">
<div class="search">
<input type="text" placeholder="검색어를 입력하세요." v-model="params.search" @keypress.enter="getMoneyInOutList()">
<div class="button" @click="getMoneyInOutList()">검색</div>
</div>
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>유저</th>
<th>유저계좌</th>
<th>유저잔액</th>
<th>타입</th>
<th>요청금액</th>
<th>요청일시</th>
<th>상태</th>
<th>삭제</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in moneyInOutList" :key="index">
<td>
<div class="text_btn" @click="goPage(`/members/detail?id=${item.user_id}`)">{{ item?.user?.username }}</div>
</td>
<td>{{ item?.user?.bank_name }} {{ item?.user?.bank_card_no }}({{ item?.user?.bank_username }})</td>
<td>{{ item?.user?.balance }}</td>
<td>{{ item.type_text }}</td>
<td>{{ item.money }}</td>
<td>{{ item.created_at }}</td>
<td>
<div class="flex">
<select v-model="item.state2" :disabled="item.state !== '1'">
<option value="1">대기중</option>
<option value="2">승인</option>
<option value="3">거절</option>
<option value="4" disabled>취소 완료</option>
</select>
<div class="btn success" v-if="item.state === '1'" @click="changeMoneyInOutStatus(item)">확인</div>
</div>
</td>
<td>
<div class="btn delete" @click="deleteMoneyInOut(item)">삭제</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="pagination">
<div class="prev" :class="{ disabled: moneyInOutData.current_page === 1 }" @click="switchPage(moneyInOutData.current_page - 1)">« 이전</div>
<div class="page" :class="{ active: moneyInOutData.current_page === item }" v-for="(item, index) in moneyInOutData.last_page" :key="index" @click="switchPage(item)">{{ item }}</div>
<div class="next" :class="{ disabled: moneyInOutData.current_page === moneyInOutData.last_page }" @click="switchPage(moneyInOutData.current_page + 1)">다음 »</div>
</div> -->
<el-pagination background layout="prev, pager, next" :total="moneyInOutData.total" :current-page="params.page" @current-change="switchPage" prev-text="« 이전" next-text="다음 »" />
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getMoneyInOutListApi, editMoneyInOutStatusApi, deleteMoneyInOutApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { goPage } from '../../utils/common'
defineOptions({
name: 'MoneyHistory',
})
const moneyInOutData = ref({})
const moneyInOutList = ref([])
const params = ref({
search: '',
page: 1,
limit: 10
})
const tagTitle = ref('')
const successTagShow = ref(false)
const getMoneyInOutList = async() => {
const res = await getMoneyInOutListApi(params.value)
const list = res.data.data.map(item => {
item.state2 = item.state
return item
})
moneyInOutData.value = res.data
moneyInOutList.value = list
console.log(list)
}
onMounted(() => {
const location = window.location.href.split('?')
if(location.length > 1) {
params.value.search = location[1].split('=')[1]
}
getMoneyInOutList()
})
// 切换页面
const switchPage = (page: number) => {
if(page < 1 || page === moneyInOutData.value.current_page || page > moneyInOutData.value.last_page) {
return
}
params.value.page = page
getMoneyInOutList()
}
// 修改会员状态
const changeMoneyInOutStatus = async (item) => {
const change = await window.confirm('처리하시겠습니까?')
if(change) {
const params = {
id: item.id,
state: item.state2
}
const res = await editMoneyInOutStatusApi(params)
if(res.code === 200) {
getMoneyInOutList()
tagTitle.value = '완료되었습니다.'
successTagShow.value = true
}
}
}
//删除会员
const deleteMoneyInOut = async(item) => {
const del = await window.confirm('삭제하시겠습니까? 출금건인 경우 임시회수한 회원의 금액이 자동반환, 승인된 입금건인 경우 입금된 회원의 금액이 회수됩니다.')
if(del) {
const res = await deleteMoneyInOutApi(item.id)
if(res.code === 200) {
getMoneyInOutList()
tagTitle.value = '회원이 삭제되었습니다.'
successTagShow.value = true
}
}
}
</script>
<style lang="less" scoped>
.flex {
display: flex;
select {
flex: 1;
margin-right: 3px;
}
}
</style>
<template>
<div class="container">
<nav>
<div class="title">{{ userDetail.username }} 님 스테이킹 관리</div>
<div class="right">
<router-link to="" class="link">회원관리</router-link>
<router-link to="/members/index" class="link label">회원목록</router-link>
<div class="label">스테이킹 관리</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">{{ userDetail.username }} 님 스테이킹 관리</div>
<div class="card">
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>회원</th>
<th>스테이킹 상품</th>
<th>채굴기 수량</th>
<th>진행중 금액</th>
<th>현재스테이킹 누적보상</th>
<th>시작일</th>
<th>채굴기 대당보상금</th>
<th>총 누적금</th>
<th>종료</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in stakingList" :key="index">
<td>
<div class="text_btn" @click="goPage(`/members/detail?id=${userId}`)">{{ item.user }}</div>
</td>
<td>
<div class="text_btn" @click="goPage(`/members/detail?id=${userId}`)">{{ item.name }}</div>
</td>
<td>
<!-- <input type="text" v-model="item.odds" @change="changeInput(item)"> -->
<van-field type="digit" v-model="item.odds" @input="changeInput(item)" @keypress.enter="startStaking(item)" />
</td>
<td>{{ item.total_money }}</td>
<td>{{ item.reward_money }}</td>
<td>{{ item.created_at }}</td>
<td>{{ item.total_money }}</td>
<td>
<div class="flex">
<input type="text" v-model="item.money" disabled class="disabled">
<div class="btn delete" @click="startStaking(item)" >실행</div>
</div>
</td>
<td>
<div class="btn delete" @click="endStaking(item)" >종료</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getMembersDetailApi, getStakingListApi, startStakingApi, endStakingApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { useCounterStore } from '../../stores/counter'
import { goPage } from '../../utils/common'
const store = useCounterStore()
const userId = ref(null)
onMounted(() => {
userId.value = store.router.currentRoute.query?.id
getMembersDetail()
getStaking()
})
// 获取详情
const userDetail = ref({})
const tagTitle = ref('')
const successTagShow = ref(false)
const getMembersDetail = async() => {
const res = await getMembersDetailApi(userId.value)
userDetail.value = res.data
}
// 质押列表
const stakingList = ref([])
const getStaking = async() => {
const res = await getStakingListApi(userId.value)
stakingList.value = res.data.map(item => {
item.total_money = Number(item.odds) * Number(item.money)
return item
})
}
// 开始质押
const startStaking = async(e) => {
const edit = await window.confirm('스테이킹 추가/시작하시겠습니까?')
if(edit) {
const params = {
odds: e.odds,
money: e.money,
id: userId.value,
staking_product_id: e.id
}
const res = await startStakingApi(params)
if(res.code === 200) {
getStaking()
tagTitle.value = '스테이킹 성공.'
successTagShow.value = true
}
}
}
// 结束质押
const endStaking = async(e) => {
const edit = await window.confirm('종료하시겠습니까?')
if(edit) {
const params = {
id: userId.value,
staking_product_id: e.id
}
const res = await endStakingApi(params)
if(res.code === 200) {
getStaking()
tagTitle.value = 'Staking 종료가 완료되었습니다.'
successTagShow.value = true
}
}
}
const changeInput = e => {
e.money = 4000
e.total_money = (Number(e.odds) || 0) * 4000
}
</script>
<style lang="less" scoped>
.flex{
display: flex;
align-items: center;
}
input {
flex: 1;
margin-right: 3px;
width: 100%;
}
.delete {
padding: 4px 8px !important;
height: 32px !important;
}
.disabled {
cursor: no-drop;
}
</style>
<template>
<div class="container">
<nav>
<div class="title">주식 수정</div>
<div class="right">
<router-link to="" class="link">스테이킹 관리</router-link>
<router-link to="/staking/product" class="link label">스테이킹상품목록</router-link>
<div class="label">수정</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head"></div>
<div class="card">
<div class="row">
<div class="label">이미지</div>
<div class="flex">
<input type="text" v-model="userDetail.logo" disabled class="disabled2">
<input type="file" class="file" @change="chooseFile">
</div>
</div>
<div class="row">
<div class="label">이름</div>
<div class="flex">
<input type="text" v-model="userDetail.name" disabled class="disabled">
</div>
</div>
<div class="row">
<div class="label">심볼</div>
<div class="flex">
<input type="text" v-model="userDetail.symbol" disabled class="disabled">
</div>
</div>
<div class="row">
<div class="label">스테이킹 심볼</div>
<div class="flex">
<input type="text" v-model="userDetail.staking_symbol" @keypress.enter="editStakingProduct">
</div>
</div>
<div class="row">
<div class="label">네트워크</div>
<div class="flex">
<input type="text" v-model="userDetail.network" @keypress.enter="editStakingProduct">
</div>
</div>
<div class="row">
<div class="label">일 배당률(%)</div>
<div class="flex">
<input type="text" v-model="userDetail.daily_odds" @keypress.enter="editStakingProduct">
</div>
</div>
</div>
<div class="footer" @click="editStakingProduct">
<div class="button">
<i class="fa fa-dot-circle-o"></i>
<span> Submit</span>
</div>
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getStakingProductDetailApi, editStakingProductApi, uploadFileApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { useCounterStore } from '../../stores/counter'
const store = useCounterStore()
const userId = ref(null)
onMounted(() => {
userId.value = store.router.currentRoute.query?.id
getMembersDetail()
})
const params = ref({
search: '',
page: 1,
limit: 100
})
const userDetail = ref({})
const tagTitle = ref('')
const successTagShow = ref(false)
const getMembersDetail = async() => {
const res = await getStakingProductDetailApi(userId.value)
userDetail.value = res.data
}
// 上传图片
const chooseFile = async (files) => {
console.log(files)
const formData = new FormData()
formData.append('image', files.target.files[0])
const res = await uploadFileApi(formData)
console.log(res)
userDetail.value.logo = res.data.url
}
const editStakingProduct = async () => {
const res = await editStakingProductApi(userDetail.value)
console.log(res)
if(res.code === 200) {
tagTitle.value = res.msg
successTagShow.value = true
getMembersDetail()
}
}
</script>
<style lang="less" scoped>
.row {
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.label {
flex: 3
}
.text {
flex: 9
}
input {
flex: 9;
width: 100%;
display: block;
height: calc(2.25rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15sease-in-out, box-shadow .15sease-in-out;
margin-right: 8px;
&:focus {
color: #495057;
background-color: #fff;
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}
}
.disabled {
background: #e9ecef;
}
.disabled2{
background: rgba(239, 239, 239, 0.3);
color: #999;
width: 210px !important;
}
.flex {
flex: 9;
padding: 0.375rem 0.75rem;
display: flex;
input {
flex: none;
&:focus {
background-color: #fff;
border-color: transparent;
outline: 0;
box-shadow: none;
}
}
.file {
width: inherit;
border: none;
}
}
}
.footer {
.button {
cursor: pointer;
i, span {
color:#868e96;
font-size: 12px;
}
}
}
</style>
<template>
<div class="container">
<nav>
<div class="title">스테이킹 기록</div>
<div class="right">
<router-link to="" class="link">스테이킹 관리</router-link>
<div class="label">스테이킹 기록</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">스테이킹 기록</div>
<div class="card">
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>유저</th>
<th>상품</th>
<th>타입</th>
<th>금액</th>
<th>누적지급(종료)</th>
<th>상태</th>
<th>발생일</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in stakingHistoryList" :key="index">
<td>
<span class="text_btn" @click="goPage(`/members/detail?id=${item.user?.id}`)" v-if="item.user">{{ item.user?.name }}({{ item.user?.username }})</span>
</td>
<td>{{ item.symbol }}</td>
<td>{{ item.daily_odds }}</td>
<td>{{ item.money }}KRW</td>
<td>{{ item.reward_money }}KRW</td>
<td>{{ item.state == 1 ? '대기중' : item.state == 2 ? '승인' : '거절'}}</td>
<td>{{ item.created_at }}</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="pagination">
<div class="prev" :class="{ disabled: stakingHistoryData.current_page === 1 }" @click="switchPage(stakingHistoryData.current_page - 1)">« 이전</div>
<div class="page" :class="{ active: stakingHistoryData.current_page === item }" v-for="(item, index) in stakingHistoryData.last_page" :key="index" @click="switchPage(item)">{{ item }}</div>
<div class="next" :class="{ disabled: stakingHistoryData.current_page === stakingHistoryData.last_page }" @click="switchPage(stakingHistoryData.current_page + 1)">다음 »</div>
</div> -->
<el-pagination background layout="prev, pager, next" :total="stakingHistoryData.total" :current-page="params.page" @current-change="switchPage" prev-text="« 이전" next-text="다음 »" />
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getStakingHistoryListApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { goPage } from '../../utils/common'
defineOptions({
name: 'StakingHistory',
})
const stakingHistoryData = ref({})
const stakingHistoryList = ref([])
const params = ref({
page: 1,
limit: 10
})
const tagTitle = ref('')
const successTagShow = ref(false)
const getStakingHistoryList = async() => {
const res = await getStakingHistoryListApi(params.value)
stakingHistoryData.value = res.data
stakingHistoryList.value = res.data.data
}
onMounted(() => {
getStakingHistoryList()
})
// 切换页面
const switchPage = (page: number) => {
if(page < 1 || page === stakingHistoryData.value.current_page || page > stakingHistoryData.value.last_page) {
return
}
params.value.page = page
getStakingHistoryList()
}
</script>
<style lang="less" scoped>
.flex {
display: flex;
select {
flex: 1;
margin-right: 3px;
}
}
</style>
<style lang="less" scoped>
img {
width: 40px;
height: 40px;
display: block;
}
.delete {
height: 32px !important;
font-size: 14px !important;
}
</style>
<template>
<div class="container">
<nav>
<div class="title">스테이킹상품목록</div>
<div class="right">
<router-link to="" class="link">스테이킹 관리</router-link>
<div class="label">스테이킹상품목록</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">스테이킹상품목록</div>
<div class="card">
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>로고</th>
<th>이름</th>
<th>심볼</th>
<th>스테이킹 심볼</th>
<th>네트워크</th>
<th>일 배당률(%)</th>
<th>삭제</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in stakingProductList" :key="index">
<td>
<img :src="item.logo" alt="">
</td>
<td>
<div class="button" @click="goPage(`/staking/detail?id=${item.id}`)">
<i class="fa fa-link" style="margin-right: 2px"></i>
<span>{{ item.name }}</span>
</div>
</td>
<td>{{ item.symbol }}</td>
<td>{{ item.staking_symbol }}</td>
<td>{{ item.network }}</td>
<td>{{ item.daily_odds }}</td>
<td>
<div class="btn delete" @click="deleteStakingProduct(item)">삭제</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="pagination">
<div class="prev" :class="{ disabled: stakingProductData.current_page === 1 }" @click="switchPage(stakingProductData.current_page - 1)">« 이전</div>
<div class="page" :class="{ active: stakingProductData.current_page === item }" v-for="(item, index) in stakingProductData.last_page" :key="index" @click="switchPage(item)">{{ item }}</div>
<div class="next" :class="{ disabled: stakingProductData.current_page === stakingProductData.last_page }" @click="switchPage(stakingProductData.current_page + 1)">다음 »</div>
</div> -->
<el-pagination background layout="prev, pager, next" :total="stakingProductData.total" :current-page="params.page" @current-change="switchPage" prev-text="« 이전" next-text="다음 »" />
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getStakingProductListApi, deleteStakingProductApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { goPage } from '../../utils/common'
defineOptions({
name: 'product',
})
const stakingProductData = ref({})
const stakingProductList = ref([])
const params = ref({
search: '',
page: 1,
limit: 10
})
const tagTitle = ref('')
const successTagShow = ref(false)
const getStakingProductList = async() => {
const res = await getStakingProductListApi(params.value)
stakingProductData.value = res.data
stakingProductList.value = res.data.data
}
onMounted(() => {
getStakingProductList()
})
// 切换页面
const switchPage = (page: number) => {
if(page < 1 || page === stakingProductData.value.current_page || page > stakingProductData.value.last_page) {
return
}
params.value.page = page
getStakingProductList()
}
//删除产品
const deleteStakingProduct = async(item) => {
const del = await window.confirm('삭제하시겠습니까?')
if(del) {
const res = await deleteStakingProductApi(item.id)
if(res.code === 200) {
getStakingProductList()
tagTitle.value = '회원이 삭제되었습니다.'
successTagShow.value = true
}
}
}
</script>
<style lang="less" scoped>
.flex {
display: flex;
select {
flex: 1;
margin-right: 3px;
}
}
</style>
<style lang="less" scoped>
img {
width: 40px;
height: 40px;
display: block;
}
.delete {
height: 32px !important;
font-size: 14px !important;
}
</style>
<template>
<div class="container">
<nav>
<div class="title">스테이킹 지급 내역</div>
<div class="right">
<router-link to="" class="link">스테이킹 관리</router-link>
<div class="label">스테이킹 지급 내역</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">스테이킹 지급 내역</div>
<div class="card">
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>유저</th>
<th>토큰</th>
<th>보상</th>
<th>발생일</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in stakingRewardList" :key="index">
<td>
<span class="text_btn" @click="goPage(`/members/detail?id=${item.user?.id}`)" v-if="item.user">{{ item.user?.name }}({{ item.user?.username }})</span>
</td>
<td>{{ item.symbol }}</td>
<td>{{ item.money }}KRW</td>
<td>{{ item.created_at }}</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="pagination">
<div class="prev" :class="{ disabled: stakingRewardData.current_page === 1 }" @click="switchPage(stakingRewardData.current_page - 1)">« 이전</div>
<div class="page" :class="{ active: stakingRewardData.current_page === item }" v-for="(item, index) in stakingRewardData.last_page" :key="index" @click="switchPage(item)">{{ item }}</div>
<div class="next" :class="{ disabled: stakingRewardData.current_page === stakingRewardData.last_page }" @click="switchPage(stakingRewardData.current_page + 1)">다음 »</div>
</div> -->
<el-pagination background layout="prev, pager, next" :total="stakingRewardData.total" :current-page="params.page" @current-change="switchPage" prev-text="« 이전" next-text="다음 »" />
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getStakingRewardListApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { goPage } from '../../utils/common'
defineOptions({
name: 'StakingReward',
})
const stakingRewardData = ref({})
const stakingRewardList = ref([])
const params = ref({
page: 1,
limit: 10
})
const tagTitle = ref('')
const successTagShow = ref(false)
const getStakingRewardList = async() => {
const res = await getStakingRewardListApi(params.value)
stakingRewardData.value = res.data
stakingRewardList.value = res.data.data
}
onMounted(() => {
getStakingRewardList()
})
// 切换页面
const switchPage = (page: number) => {
if(page < 1 || page === stakingRewardData.value.current_page || page > stakingRewardData.value.last_page) {
return
}
params.value.page = page
getStakingRewardList()
}
</script>
<style lang="less" scoped>
.flex {
display: flex;
select {
flex: 1;
margin-right: 3px;
}
}
</style>
<style lang="less" scoped>
img {
width: 40px;
height: 40px;
display: block;
}
.delete {
height: 32px !important;
font-size: 14px !important;
}
</style>
<template>
<div class="container">
<nav>
<div class="title">스테이킹 진행목록</div>
<div class="right">
<router-link to="" class="link">스테이킹 관리</router-link>
<div class="label">스테이킹 진행목록</div>
</div>
</nav>
<div class="tag success-tag" v-if="successTagShow">
<div class="info">
<div class="btn">Success</div>
<div class="title">{{ tagTitle }}</div>
</div>
<div class="delete" @click="successTagShow = false">×</div>
</div>
<div class="content">
<div class="head">스테이킹 진행목록</div>
<div class="card">
<div class="table">
<table class="table-bordered">
<thead>
<tr>
<th>유저</th>
<th>상품</th>
<th>일 배당률(%)</th>
<th>누적보상</th>
<th>시작일</th>
<th>종료</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in stakingUserList" :key="index">
<td>
<div class="button" @click="goPage(`/staking/detail?id=${item.id}`)">
<i class="fa fa-link" style="margin-right: 2px"></i>
<span>{{ item.name }}</span>
</div>
</td>
<td>{{ item.symbol }}</td>
<td>{{ item.daily_odds }}%</td>
<td>{{ item.money }}</td>
<td>{{ item.created_at }}</td>
<td>
<div class="btn delete" @click="deleteStakingUser(item)">강제종료</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- <div class="pagination">
<div class="prev" :class="{ disabled: stakingUserData.current_page === 1 }" @click="switchPage(stakingUserData.current_page - 1)">« 이전</div>
<div class="page" :class="{ active: stakingUserData.current_page === item }" v-for="(item, index) in stakingUserData.last_page" :key="index" @click="switchPage(item)">{{ item }}</div>
<div class="next" :class="{ disabled: stakingUserData.current_page === stakingUserData.last_page }" @click="switchPage(stakingUserData.current_page + 1)">다음 »</div>
</div> -->
<el-pagination background layout="prev, pager, next" :total="stakingUserData.total" :current-page="params.page" @current-change="switchPage" prev-text="« 이전" next-text="다음 »" />
</div>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import Footer from '../../components/Footer/Footer.vue'
import { getStakingUserListApi, deleteStakingUserApi } from '../../api/api'
import { onMounted, ref } from 'vue'
import { goPage } from '../../utils/common'
defineOptions({
name: 'StakingUser',
})
const stakingUserData = ref({})
const stakingUserList = ref([])
const params = ref({
page: 1,
limit: 10
})
const tagTitle = ref('')
const successTagShow = ref(false)
const getStakingUserList = async() => {
const res = await getStakingUserListApi(params.value)
stakingUserData.value = res.data
stakingUserList.value = res.data.data
}
onMounted(() => {
getStakingUserList()
})
// 切换页面
const switchPage = (page: number) => {
if(page < 1 || page === stakingUserData.value.current_page || page > stakingUserData.value.last_page) {
return
}
params.value.page = page
getStakingUserList()
}
//删除进度
const deleteStakingUser = async(item) => {
const del = await window.confirm('종료하시겠습니까?')
console.log(item)
if(del) {
const res = await deleteStakingUserApi(item)
if(res.code === 200) {
getStakingUserList()
tagTitle.value = '회원이 삭제되었습니다.'
successTagShow.value = true
}
}
}
</script>
<style lang="less" scoped>
.flex {
display: flex;
select {
flex: 1;
margin-right: 3px;
}
}
</style>
<style lang="less" scoped>
img {
width: 40px;
height: 40px;
display: block;
}
.delete {
height: 32px !important;
font-size: 14px !important;
}
</style>
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}
{
"extends": "@tsconfig/node22/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
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