3
This commit is contained in:
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
import Shell from '../views/Shell.vue'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{ path: '/admin/login', component: () => import('../views/admin/Login.vue') },
|
||||
{
|
||||
path: '/',
|
||||
component: Shell,
|
||||
@@ -15,8 +16,9 @@ const routes: RouteRecordRaw[] = [
|
||||
{ path: 'parts/templates', component: () => import('../views/parts/Templates.vue') },
|
||||
{ path: 'consult', component: () => import('../views/consult/ConsultList.vue') }
|
||||
,{ path: 'notice/list', component: () => import('../views/notice/NoticeList.vue') }
|
||||
,{ path: 'dict/units', component: () => import('../views/dict/Units.vue') }
|
||||
|
||||
,{ path: 'dict/categories', component: () => import('../views/dict/Categories.vue') }
|
||||
,{ path: 'normal-admin/applications', component: () => import('../views/normal-admin/Applications.vue') }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<el-menu-item index="/parts/templates"><i class="el-icon-collection"></i><span>模板管理</span></el-menu-item>
|
||||
<el-menu-item index="/consult"><i class="el-icon-message"></i><span>咨询回复</span></el-menu-item>
|
||||
<el-menu-item index="/notice/list"><i class="el-icon-notebook-1"></i><span>公告管理</span></el-menu-item>
|
||||
<el-menu-item index="/dict/units"><i class="el-icon-collection"></i><span>主单位</span></el-menu-item>
|
||||
<el-menu-item index="/dict/categories"><i class="el-icon-collection-tag"></i><span>主类别</span></el-menu-item>
|
||||
<el-menu-item index="/normal-admin/applications"><i class="el-icon-user"></i><span>普通管理员审批</span></el-menu-item>
|
||||
</el-menu>
|
||||
</aside>
|
||||
<main class="main">
|
||||
|
||||
59
admin/src/views/admin/Login.vue
Normal file
59
admin/src/views/admin/Login.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="login-wrap">
|
||||
<el-card class="login-card">
|
||||
<div class="title">平台管理员登录</div>
|
||||
<el-form :model="form" @keyup.enter.native="onLogin">
|
||||
<el-form-item label="用户名/手机号">
|
||||
<el-input v-model="form.account" placeholder="用户名或手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.password" type="password" placeholder="密码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" @click="onLogin">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="tips">说明:登录成功后将把 ADMIN_ID/TOKEN 写入本地用于授权。</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { http } from '../../api/http'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const form = reactive({ account: '', password: '' })
|
||||
const loading = ref(false)
|
||||
|
||||
async function onLogin(){
|
||||
if (!form.account.trim() || !form.password) return ElMessage.warning('请输入账号与密码')
|
||||
loading.value = true
|
||||
try {
|
||||
const body:any = form.account.includes('@') || /^\d{5,}$/.test(form.account)
|
||||
? { phone: form.account, password: form.password }
|
||||
: { username: form.account, password: form.password }
|
||||
const resp = await http.post('/api/admin/auth/login', body)
|
||||
const admin = resp?.admin || {}
|
||||
try {
|
||||
if (admin?.adminId) localStorage.setItem('ADMIN_ID', String(admin.adminId))
|
||||
if (resp?.token) localStorage.setItem('ADMIN_TOKEN', String(resp.token))
|
||||
} catch {}
|
||||
ElMessage.success('登录成功')
|
||||
router.replace('/vip/system')
|
||||
} catch (e:any) {
|
||||
ElMessage.error(e.message || '登录失败')
|
||||
} finally { loading.value=false }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-wrap { height: 100vh; display:flex; align-items:center; justify-content:center; padding: 16px; }
|
||||
.login-card { width: 380px; }
|
||||
.title { font-weight: 800; font-size: 18px; margin-bottom: 12px; }
|
||||
.tips { color:#888; font-size:12px; margin-top:8px; }
|
||||
</style>
|
||||
|
||||
|
||||
96
admin/src/views/normal-admin/Applications.vue
Normal file
96
admin/src/views/normal-admin/Applications.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<h2>普通管理员申请</h2>
|
||||
</div>
|
||||
<div class="panel" style="padding:12px; margin-bottom:12px;">
|
||||
<el-form :inline="true" :model="query">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="query.kw" placeholder="姓名/手机号/邮箱" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" style="width:100%" :loading="loading" stripe>
|
||||
<el-table-column type="index" width="60" label="#" />
|
||||
<el-table-column prop="name" label="姓名" width="140" />
|
||||
<el-table-column prop="phone" label="手机号" width="140" />
|
||||
<el-table-column prop="email" label="邮箱" width="200" />
|
||||
<el-table-column prop="shopId" label="店铺ID" width="100" />
|
||||
<el-table-column prop="remark" label="备注" />
|
||||
<el-table-column prop="createdAt" label="申请时间" width="180">
|
||||
<template #default="{row}">{{ formatDate(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button type="primary" size="small" @click="approve(row.userId)">通过</el-button>
|
||||
<el-button type="danger" size="small" @click="openReject(row.userId)">驳回</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pager">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, jumper, ->, total"
|
||||
:total="total"
|
||||
:page-size="query.size"
|
||||
:current-page="query.page"
|
||||
@current-change="onPage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="reject.visible" title="驳回原因" width="420px">
|
||||
<el-input type="textarea" v-model="reject.reason" placeholder="请输入驳回原因" :rows="4" />
|
||||
<template #footer>
|
||||
<el-button @click="reject.visible=false">取消</el-button>
|
||||
<el-button type="danger" @click="doReject" :loading="reject.loading">确认驳回</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { get, post } from '../../api/http'
|
||||
|
||||
const query = reactive({ kw: '', page: 1, size: 20 })
|
||||
const rows = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const reject = reactive({ visible: false, userId: 0, reason: '', loading: false })
|
||||
|
||||
function formatDate(v?: string) { if (!v) return '-'; try { const d = new Date(v); if (Number.isNaN(d.getTime())) return '-'; return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}` } catch { return '-' } }
|
||||
|
||||
async function load(){
|
||||
loading.value = true
|
||||
try {
|
||||
const res:any = await get('/api/admin/normal-admin/applications', { kw: query.kw, page: query.page, size: query.size })
|
||||
rows.value = Array.isArray(res?.list) ? res.list : []
|
||||
total.value = Number(res?.total || rows.value.length)
|
||||
} catch (e:any) { ElMessage.error(e.message || '加载失败') } finally { loading.value=false }
|
||||
}
|
||||
function reset(){ query.kw=''; query.page=1; load() }
|
||||
function onPage(p:number){ query.page=p; load() }
|
||||
|
||||
async function approve(userId:number){
|
||||
try { await post(`/api/admin/normal-admin/applications/${userId}/approve`, {}) ; ElMessage.success('已通过'); load() } catch(e:any){ ElMessage.error(e.message||'操作失败') }
|
||||
}
|
||||
function openReject(userId:number){ reject.visible=true; reject.userId=userId; reject.reason='' }
|
||||
async function doReject(){ if (!reject.reason.trim()) return ElMessage.warning('请输入驳回原因'); reject.loading=true; try { await post(`/api/admin/normal-admin/applications/${reject.userId}/reject`, { remark: reject.reason }); reject.visible=false; ElMessage.success('已驳回'); load() } catch(e:any){ ElMessage.error(e.message||'操作失败') } finally { reject.loading=false } }
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { padding: 16px; }
|
||||
.header { display:flex; align-items:center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.pager { padding: 12px; display:flex; justify-content:flex-end; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -23,31 +23,37 @@
|
||||
<el-tag :type="row.status===1?'success':'info'">{{ row.status===1?'启用':'停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
<el-table-column label="操作" width="220">
|
||||
<template #default="{row}">
|
||||
<el-button type="primary" text @click="openEdit(row)">编辑</el-button>
|
||||
<el-button type="primary" text @click="openView(row)">查看</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-popconfirm title="确认删除该模板?此操作不可恢复" @confirm="doDelete(row)">
|
||||
<template #reference>
|
||||
<el-button type="danger" text>删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="dlg.visible" :title="dlg.id? '编辑模板':'新建模板'" width="720">
|
||||
<el-dialog v-model="dlg.visible" :title="dlg.id? '查看模板' : '新建模板'" width="720">
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-form-item label="分类">
|
||||
<el-select v-model="form.categoryId" placeholder="选择分类">
|
||||
<el-select v-model="form.categoryId" placeholder="选择分类" :disabled="!!dlg.id">
|
||||
<el-option v-for="c in categories" :key="c.id" :label="c.name" :value="c.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="模板名">
|
||||
<el-input v-model="form.name" maxlength="120" />
|
||||
<el-input v-model="form.name" maxlength="120" :disabled="!!dlg.id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="型号规则">
|
||||
<el-input v-model="form.modelRule" placeholder="可填备注或正则" />
|
||||
<el-input v-model="form.modelRule" placeholder="可填备注或正则" :disabled="!!dlg.id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
|
||||
<el-switch v-model="form.status" :active-value="1" :inactive-value="0" :disabled="!!dlg.id" />
|
||||
</el-form-item>
|
||||
<el-divider>参数字段</el-divider>
|
||||
<div>
|
||||
<div v-if="!dlg.id">
|
||||
<el-button size="small" @click="addParam()">新增字段</el-button>
|
||||
</div>
|
||||
<el-table :data="form.params" size="small" style="width:100%;margin-top:10px">
|
||||
@@ -55,14 +61,14 @@
|
||||
<template #default="{ $index }">{{ $index+1 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="键">
|
||||
<template #default="{row}"><el-input v-model="row.fieldKey" placeholder="英文字母/下划线" /></template>
|
||||
<template #default="{row}"><el-input v-model="row.fieldKey" placeholder="英文字母/下划线" :disabled="!!dlg.id" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="名称">
|
||||
<template #default="{row}"><el-input v-model="row.fieldLabel" /></template>
|
||||
<template #default="{row}"><el-input v-model="row.fieldLabel" :disabled="!!dlg.id" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="120">
|
||||
<template #default="{row}">
|
||||
<el-select v-model="row.type" style="width:110px">
|
||||
<el-select v-model="row.type" style="width:110px" :disabled="!!dlg.id">
|
||||
<el-option label="string" value="string" />
|
||||
<el-option label="number" value="number" />
|
||||
<el-option label="boolean" value="boolean" />
|
||||
@@ -72,28 +78,31 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="必填" width="80">
|
||||
<template #default="{row}"><el-switch v-model="row.required" /></template>
|
||||
<template #default="{row}"><el-switch v-model="row.required" :disabled="!!dlg.id" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" width="120">
|
||||
<template #default="{row}"><el-input v-model="row.unit" /></template>
|
||||
<template #default="{row}"><el-input v-model="row.unit" :disabled="!!dlg.id" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="枚举项">
|
||||
<template #default="{row}"><el-input v-model="row.enumOptionsText" placeholder="逗号分隔(type=enum)" /></template>
|
||||
<template #default="{row}"><el-input v-model="row.enumOptionsText" placeholder="逗号分隔(type=enum)" :disabled="!!dlg.id" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="检索" width="80">
|
||||
<template #default="{row}"><el-switch v-model="row.searchable" /></template>
|
||||
<template #default="{row}"><el-switch v-model="row.searchable" :disabled="!!dlg.id" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="去重" width="80">
|
||||
<template #default="{row}"><el-switch v-model="row.dedupeParticipate" /></template>
|
||||
<template #default="{row}"><el-switch v-model="row.dedupeParticipate" :disabled="!!dlg.id" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90">
|
||||
<template #default="{ $index }"><el-button text type="danger" @click="removeParam($index)">删除</el-button></template>
|
||||
<template #default="{ $index }">
|
||||
<el-button v-if="!dlg.id" text type="danger" @click="removeParam($index)">删除</el-button>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dlg.visible=false">取消</el-button>
|
||||
<el-button type="primary" @click="save()">保存</el-button>
|
||||
<el-button @click="dlg.visible=false">{{ dlg.id? '关闭':'取消' }}</el-button>
|
||||
<el-button v-if="!dlg.id" type="primary" @click="save()">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -122,11 +131,7 @@ function loadCategories() {
|
||||
})
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
dlg.visible = true; dlg.id = 0
|
||||
Object.assign(form, { id:0, categoryId: undefined, name:'', modelRule:'', status:1, params:[] })
|
||||
}
|
||||
function openEdit(row:any) {
|
||||
function openView(row:any) {
|
||||
dlg.visible = true; dlg.id = row.id
|
||||
http.get(`/api/admin/part-templates/${row.id}`).then(res => {
|
||||
const d = res.data
|
||||
@@ -135,14 +140,19 @@ function openEdit(row:any) {
|
||||
})
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
dlg.visible = true; dlg.id = 0
|
||||
Object.assign(form, { id: 0, categoryId: undefined, name: '', modelRule: '', status: 1, params: [] })
|
||||
}
|
||||
|
||||
function addParam() {
|
||||
form.params.push({ fieldKey:'', fieldLabel:'', type:'string', required:false, unit:'', enumOptionsText:'', searchable:false, dedupeParticipate:false, sortOrder:0 })
|
||||
}
|
||||
function removeParam(i:number) {
|
||||
form.params.splice(i,1)
|
||||
}
|
||||
|
||||
function removeParam(i:number) { form.params.splice(i,1) }
|
||||
|
||||
function save() {
|
||||
// 前端校验:fieldKey/fieldLabel/类型/重复
|
||||
// 校验
|
||||
const seen = new Set<string>()
|
||||
for (const p of form.params) {
|
||||
const key = String(p.fieldKey||'').trim()
|
||||
@@ -157,9 +167,19 @@ function save() {
|
||||
const payload:any = { categoryId: form.categoryId, name: form.name, modelRule: form.modelRule, status: form.status,
|
||||
params: form.params.map((p:any)=>({ fieldKey:p.fieldKey, fieldLabel:p.fieldLabel, type:p.type, required:p.required, unit:p.unit,
|
||||
enumOptions: (p.enumOptionsText||'').split(',').map((s:string)=>s.trim()).filter((s:string)=>s), searchable:p.searchable, dedupeParticipate:p.dedupeParticipate, sortOrder:p.sortOrder })) }
|
||||
const req = dlg.id ? http.put(`/api/admin/part-templates/${dlg.id}`, { ...payload, deleteAllRelatedProductsAndSubmissions: true })
|
||||
: http.post('/api/admin/part-templates', payload)
|
||||
req.then(()=>{ ElMessage.success('保存成功'); dlg.visible=false; load() })
|
||||
http.post('/api/admin/part-templates', payload).then(()=>{
|
||||
ElMessage.success('创建成功'); dlg.visible=false; load()
|
||||
})
|
||||
}
|
||||
|
||||
function doDelete(row:any) {
|
||||
// 默认非强制删除;如需强制,可加 { params: { force: true } }
|
||||
http.delete(`/api/admin/part-templates/${row.id}`).then(()=>{
|
||||
ElMessage.success('已隐藏(可通过后台强制删除彻底清理)')
|
||||
load()
|
||||
}).catch((err:any)=>{
|
||||
ElMessage.error(err?.message || '删除失败')
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{ loadCategories(); load() })
|
||||
|
||||
Reference in New Issue
Block a user