迁移 Flutter 端与训练脚本,模型/训练产物移出 git(遵循纯代码约定)
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import request from '../api/request'
|
||||
|
||||
const filter = reactive({ phoneNum: '' })
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const size = ref(20)
|
||||
|
||||
const grantVisible = ref(false)
|
||||
const grantForm = reactive({ phoneNum: '', planId: 'day' })
|
||||
const granting = ref(false)
|
||||
|
||||
const planOptions = [
|
||||
{ value: 'day', label: '1天' },
|
||||
{ value: 'week', label: '7天' },
|
||||
{ value: 'month', label: '30天' },
|
||||
]
|
||||
|
||||
function load() {
|
||||
loading.value = true
|
||||
request
|
||||
.get('/licenses', {
|
||||
params: {
|
||||
page: page.value,
|
||||
size: size.value,
|
||||
phoneNum: filter.phoneNum || undefined,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
list.value = data.list || []
|
||||
total.value = data.total || 0
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function search() {
|
||||
page.value = 1
|
||||
load()
|
||||
}
|
||||
|
||||
function openGrant(row) {
|
||||
grantForm.phoneNum = row.phoneNum
|
||||
grantForm.planId = 'day'
|
||||
grantVisible.value = true
|
||||
}
|
||||
|
||||
function submitGrant() {
|
||||
granting.value = true
|
||||
request
|
||||
.post('/licenses/grant', { phoneNum: grantForm.phoneNum.trim(), planId: grantForm.planId })
|
||||
.then((data) => {
|
||||
ElMessage.success(`授权成功,到期时间 ${data.expiresAt}`)
|
||||
grantVisible.value = false
|
||||
load()
|
||||
})
|
||||
.finally(() => {
|
||||
granting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function revoke(row) {
|
||||
ElMessageBox.confirm(`确认撤销 ${row.phoneNum} 的授权?撤销后该账号额度立即失效,账号保留可继续登录。`, '撤销授权', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认撤销',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => request.post('/licenses/revoke', { phoneNum: row.phoneNum }))
|
||||
.then(() => {
|
||||
ElMessage.success('已撤销')
|
||||
load()
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<div class="toolbar">
|
||||
<el-form :model="filter" inline @submit.prevent="search">
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="filter.phoneNum" placeholder="模糊匹配" clearable style="width: 220px" @keyup.enter="search" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">查询</el-button>
|
||||
<el-button @click="filter.phoneNum = ''; search()">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border stripe>
|
||||
<el-table-column prop="phoneNum" label="手机号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="授权状态" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.expiresAt" type="success" size="small">已授权</el-tag>
|
||||
<el-tag v-else type="info" size="small">未充值</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="expiresAt" label="到期时间" width="180">
|
||||
<template #default="{ row }">{{ row.expiresAt || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="注册时间" width="180">
|
||||
<template #default="{ row }">{{ row.createdAt || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updatedAt" label="更新时间" width="180">
|
||||
<template #default="{ row }">{{ row.updatedAt || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="openGrant(row)">授权</el-button>
|
||||
<el-button type="danger" link size="small" :disabled="!row.expiresAt" @click="revoke(row)">撤销</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
class="pager"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="size"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@change="load"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="grantVisible" title="手动授权" width="420px">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="手机号">
|
||||
<el-input :model-value="grantForm.phoneNum" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="套餐">
|
||||
<el-select v-model="grantForm.planId" style="width: 100%">
|
||||
<el-option v-for="p in planOptions" :key="p.value" :label="p.label" :value="p.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="grantVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="granting" @click="submitGrant">确认授权</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.pager {
|
||||
margin-top: 14px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Lock as LockIcon } from '@element-plus/icons-vue'
|
||||
import request from '../api/request'
|
||||
|
||||
const router = useRouter()
|
||||
const token = ref('')
|
||||
const loading = ref(false)
|
||||
const errorMsg = ref('')
|
||||
|
||||
// 登录 = 用输入的管理 token 调一个轻量接口校验,有效则存 localStorage 进入主页
|
||||
function login() {
|
||||
if (!token.value.trim()) {
|
||||
errorMsg.value = '请输入管理 token'
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
errorMsg.value = ''
|
||||
request
|
||||
.get('/orders', { authToken: token.value.trim(), skipAuthError: true })
|
||||
.then(() => {
|
||||
localStorage.setItem('adminToken', token.value.trim())
|
||||
ElMessage.success('登录成功')
|
||||
router.push('/orders')
|
||||
})
|
||||
.catch(() => {
|
||||
errorMsg.value = 'token 无效或服务端未配置(检查 config.yml admin.token)'
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<el-card class="login-card" shadow="always">
|
||||
<div class="login-title">视野管理端</div>
|
||||
<el-input
|
||||
v-model="token"
|
||||
type="password"
|
||||
placeholder="请输入管理 token"
|
||||
show-password
|
||||
size="large"
|
||||
:prefix-icon="LockIcon"
|
||||
@keyup.enter="login"
|
||||
/>
|
||||
<div v-if="errorMsg" class="login-error">{{ errorMsg }}</div>
|
||||
<el-button type="primary" size="large" class="login-btn" :loading="loading" @click="login">
|
||||
登录
|
||||
</el-button>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #001529;
|
||||
}
|
||||
.login-card {
|
||||
width: 380px;
|
||||
padding: 12px 8px;
|
||||
}
|
||||
.login-title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
color: #303133;
|
||||
}
|
||||
.login-error {
|
||||
color: #f56c6c;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,139 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import request from '../api/request'
|
||||
|
||||
const filter = reactive({
|
||||
phoneNum: '',
|
||||
status: '',
|
||||
timeRange: [],
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const size = ref(20)
|
||||
|
||||
const statusMap = {
|
||||
created: { text: '待支付', type: 'warning' },
|
||||
paid: { text: '已支付', type: 'success' },
|
||||
closed: { text: '已关闭', type: 'info' },
|
||||
}
|
||||
|
||||
const channelMap = {
|
||||
wechat: { text: '微信', type: 'success' },
|
||||
alipay: { text: '支付宝', type: 'primary' },
|
||||
}
|
||||
|
||||
function formatTime(t) {
|
||||
return t || '-'
|
||||
}
|
||||
|
||||
function load() {
|
||||
loading.value = true
|
||||
const params = {
|
||||
page: page.value,
|
||||
size: size.value,
|
||||
}
|
||||
if (filter.phoneNum) params.phoneNum = filter.phoneNum
|
||||
if (filter.status) params.status = filter.status
|
||||
if (filter.timeRange && filter.timeRange.length === 2) {
|
||||
params.startAt = filter.timeRange[0]
|
||||
params.endAt = filter.timeRange[1]
|
||||
}
|
||||
request
|
||||
.get('/orders', { params })
|
||||
.then((data) => {
|
||||
list.value = data.list || []
|
||||
total.value = data.total || 0
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function search() {
|
||||
page.value = 1
|
||||
load()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
filter.phoneNum = ''
|
||||
filter.status = ''
|
||||
filter.timeRange = []
|
||||
search()
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<el-form :model="filter" inline @submit.prevent="search">
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="filter.phoneNum" placeholder="精确匹配" clearable style="width: 200px" @keyup.enter="search" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filter.status" placeholder="全部" clearable style="width: 120px">
|
||||
<el-option v-for="(v, k) in statusMap" :key="k" :label="v.text" :value="k" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="下单时间">
|
||||
<el-date-picker
|
||||
v-model="filter.timeRange"
|
||||
type="datetimerange"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 340px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border stripe>
|
||||
<el-table-column prop="orderId" label="订单号" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="phoneNum" label="手机号" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="planId" label="套餐" width="90" />
|
||||
<el-table-column label="渠道" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="(channelMap[row.channel] || {}).type || 'info'" size="small">{{ (channelMap[row.channel] || {}).text || row.channel }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="100">
|
||||
<template #default="{ row }">¥{{ (row.amountCents / 100).toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="(statusMap[row.status] || {}).type || 'info'" size="small">{{ (statusMap[row.status] || {}).text || row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="170">
|
||||
<template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付时间" width="170">
|
||||
<template #default="{ row }">{{ formatTime(row.paidAt) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
class="pager"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="size"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@change="load"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pager {
|
||||
margin-top: 14px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user