first commit

This commit is contained in:
yyx 2025-07-08 18:17:14 +08:00
commit 57ce531be5
40 changed files with 7093 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# 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

6
.prettierrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}

6
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"recommendations": [
"Vue.volar",
"esbenp.prettier-vscode"
]
}

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# tg_user_h5
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).
## 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
```
### Compile and Minify for Production
```sh
npm run build
```

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!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>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

4428
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "tg_user_h5",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write src/"
},
"dependencies": {
"@vueuse/core": "^13.5.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.10.0",
"dayjs": "^1.11.13",
"pinia": "^3.0.3",
"vant": "^4.9.20",
"vue": "^3.5.17",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.0",
"prettier": "3.5.3",
"sass": "^1.89.2",
"sass-loader": "^16.0.5",
"unplugin-vue-components": "^28.8.0",
"vite": "npm:rolldown-vite@latest",
"vite-plugin-vue-devtools": "^7.7.7"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

11
src/App.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style></style>

86
src/assets/base.css Normal file
View File

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
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;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

1
src/assets/logo.svg Normal file
View File

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 276 B

35
src/assets/main.css Normal file
View File

@ -0,0 +1,35 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 0;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

BIN
src/assets/pay-code.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

67
src/axios/api.js Normal file
View File

@ -0,0 +1,67 @@
import axios from 'axios'
import { showSuccessToast, showFailToast } from 'vant'
import { useRouter } from 'vue-router'
const router = useRouter()
const instance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 1000 * 30,
headers: { 'Content-Type': 'application/json' },
})
const handleGetParams = (params) => {
let result = { ...params }
Object.entries(params || {}).map(([key, value]) => {
if (value instanceof Date) {
result[key] = getTimestamp(value)
}
})
return result
}
instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('customeraccessToken')
if (config.method?.toLowerCase() === 'get') {
if (config.params) {
config.params = handleGetParams(config.params)
}
}
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
console.log('config', config)
return config
},
(error) => {
console.log(error)
return Promise.reject(error)
},
)
instance.interceptors.response.use(
(response) => {
const data = response.data
if (data.code === 200) {
return data.data
} else {
showFailToast(data.msg)
return Promise.reject(data.msg)
}
},
(error) => {
console.log('errror---------->', error)
if (error.status == 401) {
showFailToast('登录已过期,请重新登录')
localStorage.removeItem('customeraccessToken')
window.location.href = '/h5/login'
console.log('router')
// router.replace({
// path: '/login',
// })
}
return Promise.reject(error)
},
)
export default instance

189
src/axios/request.js Normal file
View File

@ -0,0 +1,189 @@
import request from './api'
export const getTaskList = (data) => {
return request({
url: '/api/tasks/list',
method: 'get',
params: data,
})
}
// 添加任务
export const addTask = (data) => {
return request({
url: '/api/tasks/add',
method: 'post',
data: data,
})
}
export const changeStatus = (data) => {
return request({
url: '/api/tasks/changeStatus',
method: 'post',
data: data,
})
}
// 修改任务
export const editTask = (data) => {
return request({
url: '/api/tasks/edit',
method: 'post',
data: data,
})
}
// 删除任务
export const delTask = (data) => {
return request({
url: '/api/tasks/delete',
method: 'post',
data: data,
})
}
// 获取群组
export const getGroupsList = (data) => {
return request({
url: '/api/groups/list',
method: 'get',
params: data,
})
}
// 获取模板列表
export const getTemplatesList = (data) => {
return request({
url: '/api/templates/list',
method: 'get',
params: data,
})
}
export const getLoginToken = (data) => {
return request({
url: '/api/robot/get-login-token',
method: 'get',
params: data,
})
}
export const checkLoginToken = (data) => {
return request({
url: '/api/robot/check-login-token',
method: 'post',
data: data,
})
}
export const userLogin = (data) => {
return request({
url: '/api/login',
method: 'post',
data: data,
})
}
export const uploadFileApi = (file) => {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/api/uploadImg',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
})
}
// 获取收款账号
export const getUsdtInfo = (data) => {
return request({
url: '/api/tasks/get-usd-info',
method: 'get',
params: data,
})
}
// 上传交易hash
export const updatePayHash = (data) => {
return request({
url: '/api/tasks/updatePayHash',
method: 'post',
data: data,
})
}
// 获取订单列表
export const getOrderList = (data) => {
return request({
url: '/api/order/list',
method: 'get',
params: data,
})
}
// 获取订单状态列表
export const getOrderStatusList = (data) => {
return request({
url: '/api/order/get-status-list',
method: 'get',
params: data,
})
}
// 申请退款
export const confirmConversionRefundRequest = (data) => {
return request({
url: '/api/order/ConfirmConversionRefundRequest',
method: 'post',
data: data,
})
}
// 获取用户信息
export const getUserinfo = (data) => {
return request({
url: '/api/getUserinfo',
method: 'post',
data: data,
})
}
// 获取统计信息
export const getDashboard = (data) => {
return request({
url: '/api/dashboard/index',
method: 'get',
params: data,
})
}
// 更新用户信息
export const UpdateInfo = (data) => {
return request({
url: '/api/updateinfo',
method: 'post',
data: data,
})
}
// 保存密码
export const ChangePassword = (data) => {
return request({
url: '/api/changePassword',
method: 'post',
data: data,
})
}
// 群发记录
export const getMassSend = (data) => {
return request({
url: '/api/send_log/list',
method: 'get',
params: data,
})
}
//消息记录
export const getMessageLog = (data) => {
return request({
url: '/api/message_log/list',
method: 'get',
params: data,
})
}
// 敏感词校验
export const checkWord = (data) => {
return request({
url: '/api/sensitiveWords/detect',
method: 'post',
data: data,
})
}

220
src/components/DateTime.vue Normal file
View File

@ -0,0 +1,220 @@
<template>
<!-- 弹出层 -->
<van-popup v-model:show="data.isPicker" position="bottom" round @close="confirmOn">
<van-picker ref="picker" title="请选择时间" :columns="data.columns" @change="onChange" @cancel="cancelOn"
@confirm="onConfirm" v-model="data.selectedValues" />
</van-popup>
</template>
<script setup>
import { reactive, watch, getCurrentInstance } from "vue";
const customFieldName = {
text: "value",
value: "values",
children: ""
};
const data = reactive({
isPicker: false, //
columns: [], //
selectedValues: [] //
});
const props = defineProps({
//
showPicker: {
type: Boolean
},
//
values: {
type: String
}
});
//
const emit = defineEmits(["changeValue", "confirm"]);
watch(
() => props.showPicker,
val => {
data.isPicker = val;
data.columns = [];
getcolumns();
},
{
immediate: true//--
}
);
function onChange() {
//
}
function getcolumns() {
let strtime = props.values; //
//console.log(strtime); 2023-09-05 19:28:00
let date = new Date(strtime.replace(/-/g, "/"));
// console.log(date); Wed Aug 09 2023 14:53:15 GMT+0800 ()
let timeVaules = date.getTime();
let dateVaules;
if (props.values != "") {
dateVaules = new Date(timeVaules);
} else {
dateVaules = new Date(); //
}
let Y = dateVaules.getFullYear();
let M = dateVaules.getMonth();
let D = dateVaules.getDate();
let h = dateVaules.getHours();
let m = dateVaules.getMinutes();
let s = dateVaules.getSeconds();
let year = []; //
year.values = [];
let Currentday = new Date().getFullYear();
for (let i = Currentday - 10; i < Currentday + 10; i++) {
year.push({ text: i.toString(), value: i });
}
year.defaultIndex = year.values.indexOf(Y); //
// 0
const _M = M < 10 ? `0${M + 1}` : M.toString(); //11
const _D = D < 10 ? `0${D}` : D.toString();
const _h = h < 10 ? `0${h}` : h.toString();
const _m = m < 10 ? `0${m}` : m.toString();
const _s = s < 10 ? `0${s}` : s.toString();
//
data.selectedValues.push(Y);
data.selectedValues.push(_M);
data.selectedValues.push(_D);
data.selectedValues.push(_h);
data.selectedValues.push(_m);
data.selectedValues.push(_s);
data.columns.push(year); //
let month = []; //12
month = Object.keys(Array.apply(null, { length: 13 })).map(function (item) {
if (+item + 1 <= 10) {
return { text: "0" + item, value: "0" + item };
} else if (+item + 1 == 11) {
return { text: +item, value: +item };
} else {
return {
text: (+item + 0).toString(),
value: (+item + 0).toString()
};
}
});
month.splice(0, 1);
data.columns.push(month); //
//
let days = getCountDays(Y, M + 1);
let day = []; //
day = Object.keys(Array.apply(null, { length: days + 1 })).map(function (
item
) {
if (+item + 1 <= 10) {
return { text: "0" + item, value: "0" + item };
} else if (+item + 1 == 11) {
return { text: +item, value: +item };
} else {
return {
text: (+item + 0).toString(),
value: (+item + 0).toString()
};
}
});
day.splice(0, 1);
data.columns.push(day); //
let hour = []; //
hour = Object.keys(Array.apply(null, { length: 24 })).map(function (item) {
if (+item + 1 <= 10) {
return { text: "0" + item, value: "0" + item };
} else if (+item + 1 == 11) {
return { text: +item, value: +item };
} else {
return {
text: (+item + 0).toString(),
value: (+item + 0).toString()
};
}
});
data.columns.push(hour); //
let mi = []; //
mi = Object.keys(Array.apply(null, { length: 60 })).map(function (item) {
if (+item + 1 <= 10) {
return { text: "0" + item, value: "0" + item };
} else if (+item + 1 == 11) {
return { text: +item, value: +item };
} else {
return {
text: (+item + 0).toString(),
value: (+item + 0).toString()
};
}
});
data.columns.push(mi);//
let ss = []; //
ss = Object.keys(Array.apply(null, { length: 60 })).map(function (item) {
if (+item + 1 <= 10) {
return { text: "0" + item, value: "0" + item };
} else if (+item + 1 == 11) {
return { text: +item, value: +item };
} else {
return {
text: (+item + 0).toString(),
value: (+item + 0).toString()
};
}
});
data.columns.push(ss);//
}
function getCountDays(year, month) {
//
let day = new Date(year, month, 0);
return day.getDate();
}
//
function confirmOn() {
emit("changeValue");
}
//
function cancelOn({ selectedValues }) {
confirmOn()
}
//
function onConfirm({ selectedValues }) {
let endval =
selectedValues[0] +
"-" +
selectedValues[1] +
"-" +
selectedValues[2] +
" " +
selectedValues[3] +
":" +
selectedValues[4] +
":" +
selectedValues[5];
confirmOn()
emit("confirm", endval);
}
</script>

172
src/components/Pay.vue Normal file
View File

@ -0,0 +1,172 @@
<template>
<van-dialog v-model:show="show" title="标题" show-cancel-button>
<template #title>
<div class="my-header">
<div>充值网络USDT-TRC20</div>
<div style="font-size: 12px;color: #999;">请选择TRC20网络充值否则可能造成资产丢失</div>
</div>
</template>
<div class="pay-code">
<div style="margin:10px 0;">支付金额<span type="danger" style="font-size: 16px;font-weight: 600;"> ${{
current_order.pay_usdt_amount }}</span>
</div>
<div>
<img v-if="showImg" :src="data.payCode" class="pay-code-img"></img>
<span v-if="!showImg" type="warning">订单已过期</span>
</div>
<div v-if="showImg" style="margin: 10px 0;">
<span>订单有效期</span><span type="primary" style="font-size: 16px;font-weight: 600;"> {{ get_time
}}</span>
<div>
<span type="danger">超过有效期订单自动取消请尽快完成支付</span>
</div>
</div>
<div v-if="showImg" @click="copy(data.payHash)">
<div style="color: #999;font-size: 12px;">钱包地址(点击复制): </div>
<span style="margin: 0 10px; color: aqua;">{{ data.payHash }} </span>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<van-button @click="closeFun" size="small" style="margin-right: 8px;">关闭</van-button>
<van-button type="primary" size="small" @click="confirmPay" v-if="showImg">
支付完成
</van-button>
</div>
</template>
</van-dialog>
</template>
<script setup>
import { reactive, ref, watch, } from 'vue'
import payCodeImg from '@/assets/pay-code.jpeg';
import useClipboard from 'vue-clipboard3'
import { getUsdtInfo } from '@/axios/request';
import { showSuccessToast, showFailToast } from 'vant';
const props = defineProps({
showDialog: {
type: Boolean,
default: false,
},
current_order: {
type: Object,
default: {},
},
});
console.log('props', props)
const show = ref(false);
const emit = defineEmits(["update:showDialog", "handleConfrimPay", 'closePay']);
const data = reactive({
payCode: payCodeImg,
payHash: '',
});;
const copy = async (msg) => {
const { toClipboard } = useClipboard()
try {
//
console.log('复制', msg)
await toClipboard(msg)
showSuccessToast('复制成功');
//
} catch (e) {
console.log(e)
//
}
}
watch(() => props.showDialog, (val) => {
console.log('showDialog', props.showDialog)
show.value = props.showDialog;
if (props.showDialog) {
setTime()
getInfo()
}
});
const get_time = ref('')
const showImg = ref(false)
const updateCountdown = (creacteTime, targetTime) => {
const target = new Date(targetTime).getTime();
const now = new Date().getTime();
const diff = target - now;
if (diff <= 0) {
clearInterval(timer)
showImg.value = false
get_time.value = ''
}
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
get_time.value = ` ${minutes}分钟 ${seconds}`;
}
//
var timer = null
const setTime = () => {
clearInterval(timer)
let cureateTime = props.current_order.order_created_at
let targetTime = props.current_order.expired_at
if (new Date(cureateTime).getTime() < new Date(targetTime).getTime()) {
showImg.value = true
timer = setInterval(() => updateCountdown(cureateTime, targetTime), 1000);
} else {
showImg.value = false
clearInterval(timer)
}
}
const closeFun = () => {
data.hash = ''
emit("update:showDialog", false);
emit("closePay");
};
const confirmPay = () => {
emit("handleConfrimPay");
emit("update:showDialog", false);
};
const getInfo = () => {
getUsdtInfo().then((res) => {
data.payHash = res.USDT_ACCOUNT;
});
}
</script>
<style lang="scss" scoped>
.pay-code {
text-align: center;
.pay-code-img {
width: 200px;
height: 200px;
margin: 20px 0;
}
.pay-hash {
display: flex;
justify-content: center;
align-items: center;
}
}
span {
color: #999;
font-size: 12px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
margin: 12px;
}
</style>

View File

@ -0,0 +1,94 @@
<template>
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
<Editor style="height: 500px; overflow-y: hidden;" v-model="modelValue" :defaultConfig="editorConfig" :mode="mode"
@onCreated="handleCreated" />
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css' // css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { useVModel } from '@vueuse/core'
import { uploadFileApi } from "@/axios/request";
const props = defineProps({
modelValue: {
type: [String],
default: "",
},
disabled: {
type: Boolean,
default: false,
},
});
// shallowRef
const editorRef = shallowRef()
const emit = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emit);
const mode = ref("default");
const toolbarConfig = ref({
excludeKeys: [
'uploadVideo', //
'insertVideo', //
'group-video', // '
'insertTable',
'blockquote',
'header1',
'header2',
'header3',
'bulletedList',
'numberedList',
'todo',
'codeBlock',
'fontSize',
'fontFamily',
'lineHeight',
'group-indent',
'divider',
'headerSelect'
]
}); //
// HTML
const valueHtml = ref('<p>hello</p>')
// ajax
onMounted(() => {
})
const editorConfig = ref({
placeholder: "请输入内容...",
MENU_CONF: {
uploadImage: {
//
async customUpload(file, insertFn) {
uploadFileApi(file).then((response) => {
const url = response.data.url;
insertFn(url);
});
},
},
},
});
//
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor) => {
editorRef.value = editor // editor
if (props.disabled) {
editor.disable();
} else {
editor.enable();
}
}
</script>
<style scoped></style>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

View File

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

18
src/main.js Normal file
View File

@ -0,0 +1,18 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// import { DateTime } from 'vant'
import 'vant/lib/index.css'
import 'vant/es/toast/style'
const app = createApp(App)
app.use(createPinia())
app.use(router)
// app.use(DateTime)
app.mount('#app')

44
src/router/index.js Normal file
View File

@ -0,0 +1,44 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/h5',
name: 'home',
component: () => import('../views/HomeView.vue'),
},
{
path: '/h5/login',
name: 'login',
component: () => import('../views/login.vue'),
},
{
path: '/h5/task',
name: 'task',
component: () => import('../views/task/index.vue'),
},
{
path: '/h5/addtask',
name: 'addtask',
component: () => import('../views/task/add.vue'),
},
{
path: '/h5/detail',
name: 'detail',
component: () => import('../views/user/detail.vue'),
},
{
path: '/h5/mass_send',
name: 'mass_send',
component: () => import('../views/user/mass_send.vue'),
},
{
path: '/h5/notice',
name: 'notice',
component: () => import('../views/user/notice.vue'),
},
],
})
export default router

12
src/stores/counter.js Normal file
View File

@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

57
src/views/HomeView.vue Normal file
View File

@ -0,0 +1,57 @@
<template>
<div class="container">
<div>
<Task v-if="activeName == 'a'"></Task>
<PayRecord v-if="activeName == 'b'"></PayRecord>
<User v-if="activeName == 'c'"></User>
<div class="footer">
<van-tabs v-model:active="activeName" @change="changeTab">
<van-tab title="任务管理" name="a"></van-tab>
<van-tab title="支付管理" name="b"></van-tab>
<van-tab title="用户管理" name="c"></van-tab>
</van-tabs>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import Task from './task/index.vue'
import PayRecord from './pay/index.vue';
import User from './user/index.vue';
import { useRouter } from 'vue-router';
let activeName = ref('a');
let token = ref('');
const router = useRouter();
const changeTab = (name) => {
console.log(name);
activeName.value = name;
localStorage.setItem('activeName', name);
};
onMounted(() => {
console.log('HomeView', localStorage.getItem('activeName'));
token.value = localStorage.getItem('customeraccessToken')
if (localStorage.getItem('activeName')) {
activeName.value = localStorage.getItem('activeName');
}
if (!token.value) {
router.push('/h5/login');
}
});
</script>
<style scoped lang="scss">
.footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 50px;
background-color: #fff;
border-top: 1px solid #eee;
z-index: 999;
}
</style>

188
src/views/login.vue Normal file
View File

@ -0,0 +1,188 @@
<template>
<div>
<van-nav-bar title="用户登录" />
<div class="login_box">
<van-tabs v-model:active="activeName" @change="changeTab">
<van-tab title="TG登录/注册" name="a">
<div class="type_login">
<span class="tips">复制信息跳转八方官方机器人粘贴完成登录</span>
<div class="tg-btn" @click="copyTg()">
<div class="tg-btn-span"> {{ `/login ${tg_login}` }}</div>
<van-icon name="notes-o" />
</div>
<van-button round block type="primary" @click="goPage" style="margin-top: 12px;">
跳转TG机器人
</van-button>
</div>
</van-tab>
<van-tab title="账号密码登录" name="b">
<div class="type_login">
<van-form>
<van-cell-group inset>
<van-field v-model="username" name="usernmae" label="用户名" placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]" />
<van-field v-model="password" type="password" name="password" label="密码"
placeholder="密码" :rules="[{ required: true, message: '请填写密码' }]" />
</van-cell-group>
<div style="margin: 16px;">
<van-button round block type="primary" @click="onSubmit">
提交
</van-button>
</div>
</van-form>
</div>
</van-tab>
</van-tabs>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getLoginToken, checkLoginToken, userLogin } from '@/axios/request';
//
import useClipboard from 'vue-clipboard3'
import { showSuccessToast, showFailToast } from 'vant';
import { useRouter } from 'vue-router';
const username = ref('');
const password = ref('');
const activeName = ref('a');
const tg_login = ref('');
const tg_expires_at = ref('')
const onSubmit = () => {
console.log('submit');
userLogin({ username: username.value, password: password.value }).then(res => {
console.log(res);
localStorage.setItem("customeraccessToken", res.token);
successPageSkip();
})
};
const getTgToken = () => {
getLoginToken().then((res) => {
console.log(res)
tg_login.value = res.token
tg_expires_at.value = res.expires_at
checkLoginTokenTimer()
})
};
let tgTimer = null
const checkLoginTokenTimer = () => {
let time = new Date(tg_expires_at.value).getTime() - Date.now();
if (time <= 0) {
clearInterval(tgTimer)
return
}
clearInterval(tgTimer)
tgTimer = setInterval(() => {
checkLoginToken({
token: tg_login.value
}).then((res) => {
console.log(res);
if (res.logged_in) {
localStorage.setItem("customeraccessToken", res.loginToken); // eyJhbGciOiJIUzI1NiJ9.xxx.xxx
successPageSkip();
clearInterval(tgTimer)
} else if (!res.valid) {
console.log('未登录');
showFailToast("授权认证过期,请刷新界面重新获取认证");
clearInterval(tgTimer)
}
}).catch((err) => {
clearInterval(tgTimer)
})
}, 3000)
}
const goPage = () => {
window.open('https://t.me/bfbf')
}
const copyTg = () => {
let data = `/login ${tg_login.value}`
copy(data)
}
const router = useRouter();
const successPageSkip = () => {
router.push('/h5')
}
const copy = async (msg) => {
const { toClipboard } = useClipboard()
try {
//
console.log('复制', msg)
await toClipboard(msg)
showSuccessToast('复制成功');
//
} catch (e) {
console.log(e)
//
}
}
const changeTab = (tab) => {
console.log(tab)
if (tab == 'a') {
getTgToken()
} else {
clearInterval(tgTimer)
}
};
onMounted(() => {
console.log('login mounted');
if (activeName.value === 'a') {
getTgToken()
}
});
</script>
<style scoped lang="scss">
.login_box {
width: 100vw;
height: 80vh;
display: flex;
justify-content: center;
align-items: center;
.type_login {
height: 200px;
text-align: center;
.tips {
font-size: 12px;
color: #999;
display: block;
margin: 12px 0;
}
.tg-btn {
//
padding: 12px;
border: 1px solid #1989fa;
border-radius: 24px;
display: flex;
align-items: center;
justify-content: space-between;
width: 75vw;
.tg-btn-span {
display: block;
width: 60vw;
font-size: 12px;
color: #1989fa;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.van-button {
width: 300px;
}
}
}
</style>

View File

@ -0,0 +1,94 @@
<template>
<div class="container">
<van-search v-model="search" placeholder="请输入模板名称进行搜索" @search="onSearch" @clear="onSearch" />
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<div v-for="item in list" :key="item.id" class="item">
<div>模板名称{{ item.name }}</div>
<div>模板类型{{ item.type === 1 ? '预设模板' : '定制模板' }}</div>
<div>
<span>群组</span>
<van-tag plain type="primary" style="margin-right: 4px;"
@click="goPage(group_options?.find(i => i.value == citem)?.url)"
v-for="(citem, index) in JSON.parse(item.group_ids)" :key="index">
{{group_options?.find(i => i.value == citem)?.label}}
</van-tag>
</div>
<div>频率{{ item.times }}小时一次</div>
<div>周期{{ item.cycle }}</div>
<div>utsd金额{{ item.pay_usdt_amount }}</div>
<div>创建时间{{ item.created_at }}</div>
</div>
</van-list>
</div>
</template>
<script setup>
import { ref, onBeforeMount } from 'vue';
import { useRouter } from 'vue-router';
import { getGroupsList, getTemplatesList } from '@/axios/request';
const router = useRouter();
const task_title = ref('费用标准')
const search = ref('')
const group_options = ref([])
const list = ref([])
const finished = ref(false)
const loading = ref(false)
let currentPage = 1
const onLoad = async () => {
let res = await getTemplatesList({
page: currentPage,
per_page: 10,
name: search.value
})
console.log('list', res)
if (currentPage == 1) {
list.value = res.data
} else {
list.value = list.value.concat(res.data)
}
finished.value = currentPage * 10 >= res.total
currentPage = currentPage + 1
loading.value = false
console.log('finished', finished, currentPage)
}
const onSearch = () => {
currentPage = 1
onLoad()
}
onBeforeMount(async () => {
let group = await getGroupsList({})
console.log('onMounted', group)
group_options.value = []
group.data.map((item) => {
group_options.value.push({
label: item.tg_name,
value: item.id,
url: item.tg_url
})
});
})
const goPage = (url) => {
console.log('goPage', url)
window.open(url)
}
</script>
<style scoped lang="scss">
.container {
padding-bottom: 10px;
}
.item {
margin: 12px;
background-color: #f7f8fa;
border-radius: 24px;
padding: 12px;
color: #666;
}
</style>

27
src/views/pay/index.vue Normal file
View File

@ -0,0 +1,27 @@
<template>
<div>
<van-nav-bar title="支付管理" />
<van-tabs v-model:active="activeName">
<van-tab title="支付记录" name="a">
<payRecord></payRecord>
</van-tab>
<van-tab title="收费标准" name="b">
<feeStandards></feeStandards>
</van-tab>
</van-tabs>
</div>
</template>
<script setup>
import { ref } from 'vue';
import feeStandards from './fee-standards.vue';
import payRecord from './pay-records.vue';
const activeName = ref('a');
import { useRouter } from 'vue-router';
const router = useRouter();
const onClickLeft = () => {
router.back()
}
</script>
<style scoped></style>

View File

@ -0,0 +1,147 @@
<template>
<div class="container">
<van-search v-model="search" placeholder="请输入订单ID进行搜索" @search="onSearch" @clear="onSearch" />
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<div v-for="item in list" :key="item.id" class="item">
<div>订单ID{{ item.order_no }}</div>
<div>任务名称{{ item?.task?.title }}</div>
<div>支付金额{{ item.pay_usdt_amount }}</div>
<div> <span>订单状态</span>
<van-tag plain :type="getStatusType(item.status)">
{{order_status_options?.find(s => s.value == item.status)?.label}}</van-tag>
</div>
<div>创建时间{{ item.created_at }}</div>
<div class="item-btns">
<van-button type="primary" size="small" style="margin-left: 12px;"
v-if="item.status == 0 || item.status == 8" @click="payTask(item)">支付</van-button>
<!-- <van-button type="primary" size="small" style="margin-left: 12px;"
v-if="item.status == 2 || item.status == 7" @click="handleRefund(item)">退款</van-button> -->
</div>
</div>
</van-list>
<Pay v-model:showDialog="showPayCodeDialog" v-model:current_order="current_order"
@handleConfrimPay="handleConfrimPay"></Pay>
<van-dialog v-model:show="show" title="退款申请" show-cancel-button @confirm="handleRefundPay">
<van-form>
<van-cell-group inset>
<van-field v-model="order_info.order_no" name="订单ID" label="订单ID" placeholder="订单ID" disabled />
<van-field v-model="order_info.pay_usdt_amount" name="订单金额" label="订单金额" placeholder="订单金额"
disabled />
<van-field v-model="order_info.refund_request_remark" name="退款原因" label="退款原因" placeholder="退款原因" />
</van-cell-group>
</van-form>
</van-dialog>
</div>
</template>
<script setup>
import { ref, onBeforeMount } from 'vue';
import { getOrderList, getOrderStatusList, updatePayHash, confirmConversionRefundRequest } from '@/axios/request';
import Pay from '@/components/Pay.vue';
const task_title = ref('费用标准')
const search = ref('')
const order_status_options = ref([])
const list = ref([])
const finished = ref(false)
const loading = ref(false)
const showPayCodeDialog = ref(false)
const current_order = ref({})
const show = ref(false)
const order_info = ref({})
let currentPage = 1
const onLoad = async () => {
let res = await getOrderList({
page: currentPage,
per_page: 10,
order_no: search.value
})
if (currentPage == 1) {
list.value = res.data
} else {
list.value = list.value.concat(res.data)
}
finished.value = currentPage * 10 >= res.total
currentPage = currentPage + 1
loading.value = false
}
const onSearch = () => {
currentPage = 1
onLoad()
}
onBeforeMount(async () => {
let order_status = await getOrderStatusList({})
order_status_options.value = []
order_status.map((item, index) => {
order_status_options.value.push({
label: item,
value: index
})
});
console.log('onMounted', order_status_options.value)
})
const getStatusType = (val) => {
if (val == 0 || val == 6) {
return 'primary'
} else if (val == 2 || val == 5) {
return 'success'
} else if (val == 3 || val == 4 || val == 7 || val == 8) {
return 'danger'
} else if (val == 1) {
return 'warning'
}
}
const handleConfrimPay = (val = '', type) => {
console.log('handleConfrimPay', current_order)
updatePayHash({
order_id: current_order.value.id.toString(),
hash: val
}).then(res => {
currentPage = 1
onLoad()
showPayCodeDialog.value = false;
})
}
const payTask = (row) => {
console.log('payTask', row)
current_order.value = row
showPayCodeDialog.value = true
}
const handleRefund = (row) => {
order_info.value = row
show.value = true
}
const handleRefundPay = () => {
const data = {
id: order_info.value.id.toString(),
refund_request_remark: order_info.value.refund_request_remark
}
confirmConversionRefundRequest(data).then(res => {
show.value = false
currentPage = 1
onLoad()
})
}
</script>
<style scoped lang="scss">
.container {
padding-bottom: 10px;
}
.item {
margin: 12px;
background-color: #f7f8fa;
border-radius: 24px;
padding: 12px;
}
</style>

262
src/views/task/add.vue Normal file
View File

@ -0,0 +1,262 @@
<template>
<div>
<van-nav-bar :title="task_title" left-text="返回" left-arrow @click-left="onClickLeft" />
<van-form @submit="onSubmit">
<van-cell-group>
<van-field v-model="form.title" name="title" label="任务名称" :disabled="isEdit" placeholder="请输入任务名称"
:rules="[{ required: true, message: '请输入任务名称' }]" />
</van-cell-group>
<van-cell-group>
<van-field v-model="form.template" is-link readonly name="template" :disabled="isEdit" label="选择模板"
:rules="[{ required: true, message: '请选择模板' }]" placeholder="请选择模板" @click="showPicker = true" />
<van-popup v-model:show="showPicker" position="bottom">
<van-picker :columns="template_options" @confirm="onConfirm" @cancel="showPicker = false" />
</van-popup>
</van-cell-group>
<van-cell-group>
<van-field v-model="form.group" disabled label="群组" />
</van-cell-group>
<van-cell-group>
<van-field v-model="form.times" disabled label="评率" />
</van-cell-group>
<van-cell-group>
<van-field v-model="form.cycle" disabled label="周期" />
</van-cell-group>
<van-cell-group>
<van-field v-model="form.price" disabled label="USDT价格" />
</van-cell-group>
<van-cell-group>
<van-field v-model="form.type" is-link readonly name="picker" :disabled="isEdit" label="执行方式"
:rules="[{ required: true, message: '请选择执行方式' }]" placeholder="请选择执行方式"
@click="showTypePicker = true" />
<van-popup v-model:show="showTypePicker" position="bottom">
<van-picker :columns="start_type_options" @confirm="onTypeConfirm"
@cancel="showTypePicker = false" />
</van-popup>
</van-cell-group>
<van-cell-group v-if="form.exec_type == 0">
<van-field v-model="form.next_run_time" is-link readonly name="picker" :disabled="isEdit" label="执行时间"
placeholder="点击选择执行时间" @click="showDatePicker = true" />
<DataTime :values="startTime" @changeValue="showDatePicker = false" :showPicker="showDatePicker"
@confirm="onDateConfirm" />
</van-cell-group>
<van-cell-group>
<van-field v-model="form.tg_title" label="发送标题" placeholder="请输入标题" />
</van-cell-group>
<van-cell-group>
<WangEditor v-model:modelValue="form.content"></WangEditor>
</van-cell-group>
</van-form>
<div class="footer">
<van-button type="primary" class="footer-button" @click="onSubmit">提交</van-button>
</div>
<Pay v-model:showDialog="showPayCodeDialog" v-model:current_order="current_order"
@handleConfrimPay="handleConfrimPay" @closePay="closePay"></Pay>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onBeforeMount, watch } from 'vue'
import { useRouter } from 'vue-router'
import { getTemplatesList, getGroupsList, addTask, editTask, checkWord, updatePayHash } from '@/axios/request';
import DataTime from "@/components/DateTime.vue";
import WangEditor from '@/components/WangEditor.vue';
import Pay from "@/components/Pay.vue";
import { showSuccessToast, showFailToast } from 'vant';
const router = useRouter()
const form = reactive({
title: '',
content: '',
exec_type: 1,
type: '立即执行',
next_run_time: '',
id: '',
})
let template_options = reactive([])
const showPicker = ref(false)
const showTypePicker = ref(false)
const showDatePicker = ref(false)
const startTime = ref(""); //
const showPayCodeDialog = ref(false)
let current_order = reactive({})
let start_type_options = [
{
text: "立即执行",
value: 1,
},
{
text: "定时执行",
value: 0,
},
]
let task_title = ref("添加任务")
let template_info = {}
let group_info = {}
const isEdit = ref(false)
const onClickLeft = () => {
router.back()
}
const onConfirm = ({ selectedOptions }) => {
console.log(selectedOptions);
form.template = selectedOptions[0]?.text;
form.temp_id = selectedOptions[0]?.value;
showPicker.value = false;
let template = template_info.find(item => item.id == selectedOptions[0]?.value)
console.log(template);
let group_id = JSON.parse(template?.group_ids)
form.times = template?.times + '小时1次'
form.cycle = template?.cycle + '天'
form.price = template?.price
let filter_groups = group_id.map(id => {
return group_info.find(item => item.id == id).tg_name
})
form.group = filter_groups.join(',');
};
const onTypeConfirm = ({ selectedOptions }) => {
console.log('onTypeConfirm', selectedOptions);
form.type = selectedOptions[0]?.text;
form.exec_type = selectedOptions[0]?.value;
console.log(form.exec_type);
showTypePicker.value = false;
};
const onDateConfirm = selectedValues => {
console.log(selectedValues);
startTime.value = selectedValues;
form.next_run_time = selectedValues
showPicker.value = false;
};
const clearFrom = () => {
form.title = '';
form.content = '';
form.next_run_time = ''
form.exec_type = ''
form.type = ''
startTime.value = ''
form.id = ''
form.tg_title = ''
};
const onSubmit = async () => {
let params = {
title: form.title,
content: form.content,
exec_type: form.exec_type,
next_run_time: form.next_run_time,
temp_id: form.temp_id,
tg_title: form.tg_title,
id: form.id || '',
}
let tg_title_check = checkWord({ content: params.tg_title })
let tg_content_check = checkWord({ content: params.content })
console.log('onSubmit', tg_title_check, tg_content_check)
Promise.all([tg_title_check, tg_content_check]).then((values) => {
if (params.id) {
editTask(params).then(res => {
showSuccessToast('提交成功')
setTimeout(() => {
clearFrom()
router.push('/h5')
}, 1000)
})
} else {
if (params.exec_type == 0 && params.next_run_time != "") {
let msg = checkTime(params.next_run_time)
if (msg) {
return showFailToast(msg)
}
}
addTask(params).then(res => {
console.log(res)
showSuccessToast('提交成功')
current_order = res
setTimeout(() => {
clearFrom()
showPayCodeDialog.value = true
// router.push('/')
}, 1000)
})
}
}).catch(err => {
console.log('err', err);
})
}
const checkTime = (value) => {
let msg = ''
if (!value) {
msg = '请选择时间'
}
if (new Date(value).getTime() <= Date.now()) {
msg = '必须选择当前时间之后的时间'
}
return msg
}
onBeforeMount(async () => {
let group = await getGroupsList({ pagination: 'off' })
// console.log('onMounted', group)
group_info = group
let templates = await getTemplatesList({ pagination: 'off' })
console.log('templates', templates)
template_options = []
template_info = templates
templates.map((item) => {
template_options.push({
text: item.name,
value: item.id,
})
});
let current_task_info = localStorage.getItem('current_task_info') != '' ? JSON.parse(localStorage.getItem('current_task_info')) : {}
console.log('current_task_info', current_task_info)
if (current_task_info != null) {
form.id = current_task_info.id
form.title = current_task_info.title
form.temp_id = current_task_info.temp_id
form.template = template_options?.find(item => item.value == current_task_info.temp_id)?.text
onConfirm({ selectedOptions: [{ text: template_options.find(item => item.value == current_task_info.temp_id)?.text, value: current_task_info.temp_id }] })
console.log('form', start_type_options?.find(item => item.value == current_task_info.exec_type))
onTypeConfirm({ selectedOptions: [{ text: start_type_options?.find(item => item.value == current_task_info.exec_type)?.text, value: current_task_info.exec_type }] })
form.content = current_task_info.content
form.next_run_time = current_task_info.next_run_time
form.tg_title = current_task_info.tg_title
isEdit.value = true
}
})
const handleConfrimPay = (val = '', type) => {
console.log('handleConfrimPay', current_order)
updatePayHash({
order_id: current_order.id.toString(),
hash: val
}).then(res => {
router.push('/h5')
}).finally(() => {
router.push('/h5')
})
}
const closePay = () => {
router.push('/h5')
}
onMounted(() => {
})
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 10px;
left: 12px;
width: calc(100vw - 24px);
.footer-button {
width: 100%;
}
}
</style>

283
src/views/task/index.vue Normal file
View File

@ -0,0 +1,283 @@
<template>
<van-nav-bar title="任务管理" />
<div class="container">
<van-search v-model="search" placeholder="请输入任务名称进行搜索" @search="onSearch" @clear="onSearch" />
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<div v-for="item in list" :key="item.id" class="item">
<div>任务名称{{ item.title }}</div>
<div>模板名称{{ item.temp_name }}</div>
<div>模板类型{{ item.temp_type === 1 ? '预设模板' : '定制模板' }}</div>
<div>
<span>群组</span>
<van-tag plain type="primary" style="margin-right: 4px;"
@click="goPage(group_options?.find(i => i.value == citem)?.url)"
v-for="(citem, index) in JSON.parse(item.temp_groups)" :key="index">
{{group_options?.find(i => i.value == citem)?.label}}
</van-tag>
</div>
<div>频率{{ item.times }}小时一次</div>
<div>周期{{ item.cycle }}</div>
<div>utsd金额{{ item.pay_usdt_amount }}</div>
<div> <span>任务状态</span>
<van-tag plain type="warning" v-if="item.status === 0 || item.status == 5">
{{status_options?.find(s => s.value == item.status)?.label}}</van-tag>
<van-tag plain type="danger" v-if="item.status == 3 || item.status == 6">
{{status_options?.find(s => s.value == item.status)?.label}}</van-tag>
<van-tag plain type="primary" v-if="item.status == 1 || item.status == 2">
{{status_options?.find(s => s.value == item.status)?.label}}</van-tag>
<van-tag plaing type="success" v-if="item.status == 4">
{{status_options?.find(s => s.value == item.status)?.label}}</van-tag>
</div>
<div>创建时间{{ item.created_at }}</div>
<div>
<span>
支付状态
</span>
<van-tag type="primary" v-if="item.order_status == 0 || item.order_status == 6">
{{order_status?.find(o => o.value == item.order_status)?.label}}</van-tag>
<van-tag type="danger"
v-if="item.order_status == 3 || item.order_status == 4 || item.order_status == 7">
{{order_status?.find(o => o.value == item.order_status)?.label}}</van-tag>
<van-tag type="warning" v-if="item.order_status == 1">
{{order_status?.find(o => o.value == item.order_status)?.label}}</van-tag>
<van-tag type="success" v-if="item.order_status == 2 || item.order_status == 5">
{{order_status?.find(o => o.value == item.order_status)?.label}}</van-tag>
</div>
<div class="item-btns">
<van-button type="primary"
v-if="item.status == 0 || item.status == 1 || item.status == 3 || item.status == 5" size="small"
style="margin-left: 12px;" @click="addTask(item)">编辑</van-button>
<van-button type="primary" size="small" style="margin-left: 12px;"
v-if="item.order_status == 0 || item.order_status == 8" @click="payTask(item)">支付</van-button>
<van-button type="primary" size="small" style="margin-left: 12px;" v-if="item.status == 2"
@click="handleTask(item.id, '2')">执行</van-button>
<van-button type="primary" size="small" style="margin-left: 12px;"
@click="handleTask(item.id, '5')">暂停</van-button>
<van-button type="danger" size="small" style="margin-left: 12px;"
v-if="item.status == 6 || item.status == 0" @click="deleteTask(item.id)">删除</van-button>
</div>
</div>
</van-list>
<div class="footer">
<van-button type="primary" size="small" class="footer-button" @click="addTask('')">创建任务</van-button>
</div>
<Pay v-model:showDialog="showPayCodeDialog" v-model:current_order="current_order"
@handleConfrimPay="handleConfrimPay"></Pay>
</div>
</template>
<script setup>
import { onBeforeMount, ref } from 'vue'
import { getTaskList, getGroupsList, getTemplatesList, updatePayHash, delTask, changeStatus } from '@/axios/request';
import { useRouter } from 'vue-router'
import Pay from '@/components/Pay.vue';
import { showSuccessToast, showConfirmDialog } from 'vant';
const router = useRouter()
let search = ref('')
let list = ref([])
let loading = ref(false)
let finished = ref(false)
let group_options = ref([])
let template_info = ref([])
let showPayCodeDialog = ref(false)
let current_order = ref({})
let order_status = [{
label: "未支付",
value: 0,
}, {
label: "待确认",
value: 1,
}, {
label: "已支付",
value: 2,
}, {
label: "已关闭",
value: 3,
}, {
label: "超时关闭",
value: 4,
}, {
label: "已退款",
value: 5,
}, {
label: "用户申请退款",
value: 6,
}, {
label: "退款被拒绝",
value: 7,
}]
const status_options = [ //012()3456
{
label: "未开始",
value: "0",
},
{
label: "待审核",
value: "1",
},
{
label: "进行中",
value: "2",
},
{
label: "审核拒绝",
value: "3",
},
{
label: "已完成",
value: "4",
},
{
label: "暂停",
value: "5",
},
{
label: "已取消",
value: "6",
},
]
let currentPage = 1
const onLoad = async () => {
let res = await getTaskList({
page: currentPage,
per_page: 10,
task_name: search.value
})
console.log('list', res)
if (currentPage == 1) {
list.value = res.data
} else {
list.value = list.value.concat(res.data)
}
finished.value = currentPage * 10 >= res.total
currentPage = currentPage + 1
loading.value = false
console.log('finished', finished, currentPage)
}
const onSearch = () => {
currentPage = 1
onLoad()
}
onBeforeMount(async () => {
let group = await getGroupsList({})
let templates = await getTemplatesList({})
console.log('onMounted', group, templates)
group_options.value = []
group.data.map((item) => {
group_options.value.push({
label: item.tg_name,
value: item.id,
url: item.tg_url
})
});
console.log('group_options', group_options)
template_info.value = templates.data
})
const goPage = (url) => {
console.log('goPage', url)
window.open(url)
}
const addTask = (row = '') => {
console.log('addTask', row)
if (row.id) {
localStorage.setItem('current_task_info', JSON.stringify(row))
} else {
localStorage.removeItem('current_task_info')
}
router.push({
path: '/h5/addTask',
})
}
const payTask = (row) => {
console.log('payTask', row)
current_order.value = row
showPayCodeDialog.value = true
}
const handleConfrimPay = (val = '', type) => {
console.log('handleConfrimPay', current_order)
updatePayHash({
order_id: current_order.value.order_id.toString(),
hash: val
}).then(res => {
currentPage = 1
onLoad()
showPayCodeDialog.value = false;
})
}
const handleTask = ((id, status) => {
let title = status == 2 ? '执行任务' : '暂停任务'
let message = status == 2 ? '确定执行此任务' : '确定暂停此任务'
showConfirmDialog({
title: title,
message: message
})
.then(() => {
changeTask(id, status)
})
.catch(() => {
// on cancel
});
})
const changeTask = (id, status) => {
changeStatus({ id: id, status: parseInt(status) }).then(res => {
currentPage = 1
onLoad()
}).finally(() => {
})
}
const deleteTask = (id) => {
showConfirmDialog({
title: '删除任务',
message: '确定删除此任务吗?'
})
.then(() => {
delTask({ id: id }).then((res) => {
currentPage = 1
onLoad()
});
})
.catch(() => {
// on cancel
});
}
</script>
<style lang="scss">
.container {
padding-bottom: 60px;
}
.item {
margin: 12px;
background-color: #f7f8fa;
border-radius: 24px;
padding: 12px;
.item-btns {
display: flex;
align-items: flex-end;
justify-content: flex-end;
margin-top: 8px;
}
}
.footer {
position: fixed;
bottom: 60px;
left: 12px;
width: calc(100vw - 24px);
.footer-button {
width: 100%;
}
}
</style>

142
src/views/user/detail.vue Normal file
View File

@ -0,0 +1,142 @@
<template>
<div class="detail">
<van-nav-bar title="用户信息" left-text="返回" left-arrow @click-left="onClickLeft" />
<van-cell-group>
<van-field v-model="userinfo.telegram_id" name="Telegram ID" label="Telegram ID" placeholder=" Telegram ID"
label-width="120px" disabled />
<van-field v-model="userinfo.nikename" name="用户昵称" label="用户昵称" placeholder="用户昵称" label-width="120px" />
<van-field v-model="userinfo.telegram_tel" name="Telegram联系方式" label="Telegram联系方式" label-width="120px"
placeholder="Telegram联系方式" />
<van-field v-model="userinfo.email" name="Email" label="Email" placeholder="Email" label-width="120px" />
<van-field v-model="userinfo.usdt_address" name="USFT钱包地址" label="USFT钱包地址" placeholder="USFT钱包地址"
label-width="120px" />
<van-field v-model="userinfo.username" name="登录账号" label="登录账号" placeholder="登录账号" disabled
label-width="120px" />
</van-cell-group>
<div class="tips">TG首次登录默认密码<span class="default">{{ defaultPassword }}</span><span class="view"
@click="viewPassword">查看</span>,请尽快修改</div>
<div class="btns">
<van-button type="primary" size="small" @click="saveInfo">保存用户信息</van-button>
<van-button type="warning" size="small" @click="changePassword">修改密码</van-button>
</div>
<div class="footer">
<van-button class="footer-button" type="primary" size="small" @click="logout">登出用户</van-button>
</div>
<van-dialog v-model:show="showModify" title="修改密码" show-cancel-button @confirm="modifyPassword">
<van-cell-group>
<van-field v-model="password.oldPassword" name="旧密码" label="旧密码" placeholder="旧密码" type="password"
label-width="120px" />
<van-field v-model="password.newPassword" name="新密码" label="新密码" placeholder="新密码" type="password"
label-width="120px" />
<van-field v-model="password.confirmPassword" name="确认密码" label="确认密码" placeholder="确认密码"
type="password" label-width="120px" />
</van-cell-group>
</van-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { getUserinfo, UpdateInfo, ChangePassword } from '@/axios/request'
import { showSuccessToast, showFailToast } from 'vant'
const router = useRouter()
const defaultPassword = ref('******')
const userinfo = ref({})
const showModify = ref(false)
const password = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: '',
})
onMounted(async () => {
userinfo.value = await getUserinfo()
})
const onClickLeft = () => {
router.back()
}
const viewPassword = () => {
defaultPassword.value = '123456'
setTimeout(() => {
defaultPassword.value = '******'
}, 5000)
}
const saveInfo = async () => {
let param = {
nikename: userinfo.value.nikename,
telegram_tel: userinfo.value.telegram_tel,
email: userinfo.value.email,
usdt_address: userinfo.value.usdt_address
}
UpdateInfo(param).then(res => {
showSuccessToast('保存成功')
})
}
const changePassword = async () => {
showModify.value = true
}
const modifyPassword = async () => {
if (password.oldPassword === password.newPassword) {
showFailToast("新密码不能与旧密码相同")
return
}
if (password.newPassword !== password.confirmPassword) {
showFailToast('密码不一致');
return
} else {
let param = {
old_password: password.oldPassword,
new_password: password.newPassword,
}
changePassword(param).then(res => {
showSuccessToast('修改成功');
showModify = false;
}).finally(() => {
password.oldPassword = ''
password.newPassword = ''
password.confirmPassword = ''
})
}
}
const logout = () => {
localStorage.removeItem('token')
router.push('/h5/login')
}
</script>
<style scoped lang="scss">
.tips {
color: #999;
margin: 10px 0;
text-align: center;
}
.default {
color: red;
margin-right: 4px;
}
.view {
color: #1998fb;
}
.btns {
display: flex;
justify-content: space-around;
margin-top: 40px;
}
.footer {
position: fixed;
bottom: 10px;
left: 0;
width: 100%;
margin: 0 20px;
.footer-button {
width: calc(100% - 40px);
}
}
</style>

136
src/views/user/index.vue Normal file
View File

@ -0,0 +1,136 @@
<template>
<van-nav-bar title="用户信息" />
<div class="container">
<div class="userinfo">
<div class="title">用户昵称<span>{{ userInfo.nikename }}</span></div>
<div class="title">Telegram ID<span>{{ userInfo.telegram_id }}</span></div>
<div class="title">Telegram联系方式<span>{{ userInfo.telegram_tel }}</span></div>
<div class="title">Email<span>{{ userInfo.email }}</span></div>
<div class="edit" @click="editUserInfo()"><van-icon name="edit" /></div>
</div>
<div class="dashboard">
<div class="box">
<div class="account">{{ dashboard.taskCount }}</div>
<div class="title">总任务数</div>
</div>
<div class="shu"></div>
<div class="box">
<div class="account">{{ dashboard.orderMoney }}</div>
<div class="title">USDT付款总额</div>
</div>
<div class="shu"></div>
<div class="box">
<div class="account">{{ dashboard.taskUnpayCount }}</div>
<div class="title">待支付任务数</div>
</div>
</div>
<div class="tool">
<div class="tool-box" @click="goPage('/h5/mass_send')">
<div><van-icon name="notes-o" class="icon" /></div>
<div class="title">群发记录</div>
</div>
<div class="tool-box" @click="goPage('/h5/notice')">
<div><van-icon name="other-pay" class="icon" /></div>
<div class="title">通知历史</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import { getUserinfo, getDashboard } from '@/axios/request'
import { useRouter } from 'vue-router'
const router = useRouter()
const userInfo = ref({})
const dashboard = ref({})
onMounted(async () => {
userInfo.value = await getUserinfo()
dashboard.value = await getDashboard()
console.log('userInfo', userInfo.value, dashboard.value)
})
const editUserInfo = () => {
router.push('/h5/detail')
}
const goPage = (url) => {
router.push(url)
}
</script>
<style scoped lang="scss">
.container {
background-color: #f5f8fa !important;
height: 100vh;
padding-top: 12px;
}
.userinfo {
margin: 12px;
border-radius: 12px;
background-color: #ffffff;
padding: 12px;
color: #666;
position: relative;
margin-top: 0;
span {
color: #999;
}
.edit {
position: absolute;
top: 45%;
right: 12px;
}
}
.dashboard {
display: flex;
justify-content: space-around;
align-items: center;
text-align: center;
margin: 12px;
padding: 12px;
background-color: #ffffff;
border-radius: 8px;
.shu {
width: 1px;
height: 40px;
background: #eee;
}
.account {
font-weight: 600;
color: #666;
}
.title {
color: #333;
}
}
.tool {
margin: 12px;
padding: 12px;
background-color: #ffffff;
border-radius: 8px;
display: flex;
align-items: center;
flex-wrap: wrap;
text-align: center;
.tool-box {
width: 30%;
.icon {
font-size: 20px;
}
.title {
font-size: 12px;
color: #999;
}
}
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<van-nav-bar :title="task_title" left-text="返回" left-arrow @click-left="onClickLeft" />
<div class="container">
<van-search v-model="search" placeholder="请输入任务名称进行搜索" @search="onSearch" @clear="onSearch" />
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<div v-for="item in list" :key="item.id" class="item">
<div><span> 任务ID</span>{{ item.task_id }}</div>
<div> <span>任务名称</span>{{ item.task_name }}</div>
<div><span>任务状态</span> <van-tag plain :type="item.status == 1 ? 'success' : 'danger'">{{ item.status ==
1 ? '成功' : '失败'
}}</van-tag></div>
<div><span>发送时间</span>{{ dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss') }}</div>
<div><span>发送群组</span>{{ item.group_name }}</div>
<div @click="goPage(item.send_url)"><span>发送链接</span>{{ item.send_url }}</div>
<div><span>失败原因</span> {{ showReason(item.reason) }}</div>
</div>
</van-list>
</div>
</template>
<script setup>
import { ref, onBeforeMount } from 'vue';
import { useRouter } from 'vue-router';
import { getMassSend } from '@/axios/request';
import dayjs from 'dayjs';
const router = useRouter();
const task_title = ref('群发记录')
const search = ref('')
const group_options = ref([])
const list = ref([])
const finished = ref(false)
const loading = ref(false)
let currentPage = 1
const onLoad = async () => {
let res = await getMassSend({
page: currentPage,
per_page: 10,
task_id: search.value
})
console.log('list', res)
if (currentPage == 1) {
list.value = res.data
} else {
list.value = list.value.concat(res.data)
}
finished.value = currentPage * 10 >= res.total
currentPage = currentPage + 1
loading.value = false
console.log('finished', finished, currentPage)
}
const onSearch = () => {
currentPage = 1
onLoad()
}
const goPage = (url) => {
console.log('goPage', url)
if (!url) return
window.open(url)
}
const onClickLeft = () => {
router.back()
}
const showReason = (reason) => {
try {
return JSON.parse(reason)?.code == 400 ? JSON.parse(reason)?.msg : ''
} catch (error) {
return reason || ''
}
}
</script>
<style scoped lang="scss">
.container {
padding-bottom: 10px;
}
.item {
margin: 12px;
background-color: #f7f8fa;
border-radius: 24px;
padding: 12px;
color: #333;
span {
font-weight: 600;
}
}
</style>

80
src/views/user/notice.vue Normal file
View File

@ -0,0 +1,80 @@
<template>
<van-nav-bar :title="task_title" left-text="返回" left-arrow @click-left="onClickLeft" />
<div class="container">
<van-search v-model="search" placeholder="请输入模板名称进行搜索" @search="onSearch" @clear="onSearch" />
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<div v-for="item in list" :key="item.id" class="item">
<div><span> 标题</span>{{ item.title }}</div>
<div> <span>内容</span>{{ item.content }}</div>
<div><span>发送地址</span> {{ item.addr }}</div>
<div><span>发送时间</span>{{ item.updated_at }}</div>
<div><span>状态</span>{{ item.status == 1 ? '成功' : '失败' }}</div>
</div>
</van-list>
</div>
</template>
<script setup>
import { ref, onBeforeMount } from 'vue';
import { useRouter } from 'vue-router';
import { getMessageLog } from '@/axios/request';
const router = useRouter();
const task_title = ref('通知历史')
const search = ref('')
const group_options = ref([])
const list = ref([])
const finished = ref(false)
const loading = ref(false)
let currentPage = 1
const onLoad = async () => {
let res = await getMessageLog({
page: currentPage,
per_page: 10,
name: search.value
})
console.log('list', res)
if (currentPage == 1) {
list.value = res.data
} else {
list.value = list.value.concat(res.data)
}
finished.value = currentPage * 10 >= res.total
currentPage = currentPage + 1
loading.value = false
console.log('finished', finished, currentPage)
}
const onSearch = () => {
currentPage = 1
onLoad()
}
const goPage = (url) => {
console.log('goPage', url)
window.open(url)
}
const onClickLeft = () => {
router.back()
}
</script>
<style scoped lang="scss">
.container {
padding-bottom: 10px;
}
.item {
margin: 12px;
background-color: #f7f8fa;
border-radius: 24px;
padding: 12px;
color: #333;
span {
font-weight: 600;
}
}
</style>

32
vite.config.js Normal file
View File

@ -0,0 +1,32 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
// import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
// vueDevTools(),
Components({
resolvers: [VantResolver()],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
proxy: {
'/api': {
target: 'http://192.168.2.79',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '/api'),
},
},
},
})