@@ -0,0 +1,339 @@
< template >
< div class = "xieyi-manager" >
< div v-if = "!hasPermission" class="no-permission" >
< div class = "no-permission-icon" > 🔒 < / div >
< div > 您无权访问此页面 < / div >
< / div >
< div v-else >
< div class = "header-bar" >
< h2 > 用户协议管理 < / h2 >
< el-text type = "info" > 支持按身份创建文字 / 图片协议 , 用户手写签署后可在下方查询记录 < / el-text >
< / div >
< div class = "toolbar" >
< el-select v-model = "filterShenfen" placeholder="筛选身份" clearable style="width: 160px" @change="loadList" >
< el -option v-for = "s in shenfenChoices" :key="s.key" :label="s.label" :value="s.key" / >
< / el-select >
< el-button type = "primary" @click ="openEdit()" > + 新建协议 < / el -button >
< el-button @click ="loadList" > 刷新 < / el -button >
< / div >
< el-table :data = "items" border stripe v-loading = "loading" >
< el -table -column prop = "title" label = "后台标题" min -width = " 160 " / >
< el-table-column prop = "shenfen_label" label = "适用身份" width = "100" / >
< el-table-column label = "模式" width = "90" >
< template # default = "{ row }" > { { row . mode === 1 ? '图片' : '文字' } } < / template >
< / el-table-column >
< el-table-column prop = "version" label = "版本" width = "70" / >
< el-table-column label = "状态" width = "80" >
< template # default = "{ row }" >
< el-tag : type = "row.is_active ? 'success' : 'info'" size = "small" >
{ { row . is _active ? '启用' : '禁用' } }
< / el-tag >
< / template >
< / el-table-column >
< el-table-column label = "操作" width = "220" fixed = "right" >
< template # default = "{ row }" >
< el-button link type = "primary" @click ="openEdit(row)" > 编辑 < / el -button >
< el-button link type = "danger" @click ="removeItem(row)" > 删除 < / el -button >
< / template >
< / el-table-column >
< / el-table >
< el-divider > 签署记录查询 < / el-divider >
< div class = "sign-toolbar" >
< el-input v-model = "signYonghuid" placeholder="用户ID" style="width: 160px" clearable / >
< el-button type = "primary" @click ="loadSignRecords" > 查询签署 < / el -button >
< / div >
< el-table :data = "signRecords" border size = "small" v-loading = "signLoading" >
< el -table -column prop = "yonghuid" label = "用户ID" width = "100" / >
< el-table-column prop = "xieyi_title" label = "协议" min -width = " 140 " / >
< el-table-column prop = "xieyi_version" label = "版本" width = "70" / >
< el-table-column prop = "shenfen" label = "签署身份" width = "90" / >
< el-table-column prop = "signed_at" label = "签署时间" width = "170" / >
< el-table-column label = "签名图" width = "100" >
< template # default = "{ row }" >
< el-image v-if = "row.signature_path" :src="fullUrl(row.signature_path)" :preview-src-list="[fullUrl(row.signature_path)]" class="sign-thumb" / >
< / template >
< / el-table-column >
< / el-table >
< / div >
< el-dialog v-model = "dialogVisible" :title="form.id ? '编辑协议' : '新建协议'" width="720px" destroy -on -close >
< el-form :model = "form" label -width = " 120px " >
< el-form-item label = "后台标题" required >
< el-input v-model = "form.title" / >
< / el-form-item >
< el-form-item label = "适用身份" required >
< el-select v-model = "form.shenfen" >
< el -option v-for = "s in shenfenChoices" :key="s.key" :label="s.label" :value="s.key" / >
< / el-select >
< / el-form-item >
< el-form-item label = "展示模式" >
< el-radio-group v-model = "form.mode" >
< el -radio :value = "2" > 文字模式 < / el-radio >
< el-radio :value = "1" > 纯图片模式 < / el-radio >
< / el-radio-group >
< / el-form-item >
< el-form-item label = "前端标题" >
< el-input v-model = "form.content_title" placeholder="可选" / >
< / el-form-item >
< el-form-item v-if = "form.mode === 2" label="文字内容" >
< el -input v-model = "form.content_text" type="textarea" :rows="10" placeholder="支持换行" / >
< / el-form-item >
< el-form-item v-if = "form.mode === 1 && form.id" label="协议图片" >
< div class = "img-list" >
< div v-for = "img in form.images" :key="img.id" class="img-item" >
< el -image :src = "fullUrl(img.image_path)" fit = "cover" class = "preview" / >
< el-button link type = "danger" @click ="deleteImage(img)" > 删除 < / el -button >
< / div >
< el-upload :show-file-list = "false" accept = "image/*" :http-request = "uploadImage" >
< el-button type = "primary" link > + 上传图片 < / el-button >
< / el-upload >
< / div >
< / el-form-item >
< el-form-item label = "排序" >
< el-input-number v-model = "form.sort_order" :min="0" / >
< / el-form-item >
< el-form-item label = "启用" >
< el-switch v-model = "form.is_active" / >
< / el-form-item >
< el-form-item v-if = "form.id" label="升级版本" >
< el -switch v-model = "form.bump_version" / >
< el-text type = "info" size = "small" > 修改内容后开启 , 用户需重新签署 < / el-text >
< / el-form-item >
< / el-form >
< template # footer >
< el-button @click ="dialogVisible = false" > 取消 < / el -button >
< el-button type = "primary" :loading = "submitting" @click ="saveForm" > 保存 < / el -button >
< / template >
< / el-dialog >
< / div >
< / template >
< script setup >
import { ref , reactive , onMounted } from 'vue'
import { ElMessage , ElMessageBox } from 'element-plus'
import request from '@/utils/request'
const username = ref ( localStorage . getItem ( 'username' ) || '' )
const hasPermission = ref ( true )
const loading = ref ( false )
const items = ref ( [ ] )
const shenfenChoices = ref ( [ ] )
const filterShenfen = ref ( '' )
const dialogVisible = ref ( false )
const submitting = ref ( false )
const ossBaseUrl = ref ( window . $ossURL || '' )
const form = reactive ( {
id : null ,
title : '' ,
shenfen : 'dashou' ,
mode : 2 ,
content _title : '' ,
content _text : '' ,
sort _order : 0 ,
is _active : true ,
bump _version : false ,
images : [ ] ,
} )
const signYonghuid = ref ( '' )
const signRecords = ref ( [ ] )
const signLoading = ref ( false )
const fullUrl = ( path ) => {
if ( ! path ) return ''
if ( path . startsWith ( 'http' ) ) return path
const base = ossBaseUrl . value . replace ( /\/$/ , '' )
return ` ${ base } / ${ String ( path ) . replace ( /^\// , '' ) } `
}
const loadList = async ( ) => {
loading . value = true
try {
const res = await request . post ( '/houtai/hthqxy' , {
username : username . value ,
shenfen : filterShenfen . value || undefined ,
} )
if ( res . code === 403 ) {
hasPermission . value = false
return
}
if ( res . code === 0 ) {
items . value = res . data ? . items || [ ]
shenfenChoices . value = res . data ? . shenfen _choices || [ ]
} else {
ElMessage . error ( res . msg || '加载失败' )
}
} catch {
ElMessage . error ( '网络错误' )
} finally {
loading . value = false
}
}
const openEdit = ( row ) => {
if ( row ) {
Object . assign ( form , {
id : row . id ,
title : row . title ,
shenfen : row . shenfen ,
mode : row . mode ,
content _title : row . content _title || '' ,
content _text : row . content _text || '' ,
sort _order : row . sort _order || 0 ,
is _active : row . is _active ,
bump _version : false ,
images : row . images || [ ] ,
} )
} else {
Object . assign ( form , {
id : null ,
title : '' ,
shenfen : 'dashou' ,
mode : 2 ,
content _title : '' ,
content _text : '' ,
sort _order : 0 ,
is _active : true ,
bump _version : false ,
images : [ ] ,
} )
}
dialogVisible . value = true
}
const saveForm = async ( ) => {
if ( ! form . title || ! form . shenfen ) {
ElMessage . warning ( '请填写标题和身份' )
return
}
submitting . value = true
try {
const payload = {
username : username . value ,
action : form . id ? 'update' : 'create' ,
id : form . id ,
title : form . title ,
shenfen : form . shenfen ,
mode : form . mode ,
content _title : form . content _title ,
content _text : form . content _text ,
sort _order : form . sort _order ,
is _active : form . is _active ,
bump _version : form . bump _version ,
}
const res = await request . post ( '/houtai/htxgxy' , payload )
if ( res . code === 0 ) {
ElMessage . success ( res . msg || '保存成功' )
dialogVisible . value = false
loadList ( )
} else {
ElMessage . error ( res . msg || '保存失败' )
}
} catch {
ElMessage . error ( '网络错误' )
} finally {
submitting . value = false
}
}
const removeItem = async ( row ) => {
try {
await ElMessageBox . confirm ( ` 确定删除「 ${ row . title } 」? ` , '确认' )
const res = await request . post ( '/houtai/htxgxy' , {
username : username . value ,
action : 'delete' ,
id : row . id ,
} )
if ( res . code === 0 ) {
ElMessage . success ( '已删除' )
loadList ( )
} else {
ElMessage . error ( res . msg || '删除失败' )
}
} catch { }
}
const uploadImage = async ( { file } ) => {
if ( ! form . id ) {
ElMessage . warning ( '请先保存协议再上传图片' )
return
}
const fd = new FormData ( )
fd . append ( 'username' , username . value )
fd . append ( 'xieyi_id' , form . id )
fd . append ( 'image' , file )
try {
const res = await request . post ( '/houtai/htxgytp' , fd , {
headers : { 'Content-Type' : 'multipart/form-data' } ,
} )
if ( res . code === 0 ) {
ElMessage . success ( '上传成功' )
form . images . push ( { id : res . data . id , image _path : res . data . image _path } )
loadList ( )
} else {
ElMessage . error ( res . msg || '上传失败' )
}
} catch {
ElMessage . error ( '上传失败' )
}
}
const deleteImage = async ( img ) => {
try {
const res = await request . post ( '/houtai/htscxytp' , {
username : username . value ,
image _id : img . id ,
} )
if ( res . code === 0 ) {
form . images = form . images . filter ( ( i ) => i . id !== img . id )
ElMessage . success ( '已删除' )
loadList ( )
} else {
ElMessage . error ( res . msg || '删除失败' )
}
} catch {
ElMessage . error ( '网络错误' )
}
}
const loadSignRecords = async ( ) => {
signLoading . value = true
try {
const res = await request . post ( '/houtai/hthqxyqs' , {
username : username . value ,
yonghuid : signYonghuid . value || undefined ,
} )
if ( res . code === 0 ) {
signRecords . value = res . data ? . items || [ ]
} else {
ElMessage . error ( res . msg || '查询失败' )
}
} catch {
ElMessage . error ( '网络错误' )
} finally {
signLoading . value = false
}
}
onMounted ( ( ) => {
loadList ( )
} )
< / script >
< style scoped >
. xieyi - manager { padding : 8 px 4 px ; }
. header - bar { margin - bottom : 16 px ; }
. header - bar h2 { margin : 0 0 8 px ; color : # 00 f2ff ; }
. toolbar , . sign - toolbar { display : flex ; gap : 12 px ; margin - bottom : 16 px ; align - items : center ; }
. no - permission { text - align : center ; padding : 80 px ; color : rgba ( 255 , 255 , 255 , 0.6 ) ; }
. no - permission - icon { font - size : 48 px ; margin - bottom : 12 px ; }
. img - list { display : flex ; flex - wrap : wrap ; gap : 12 px ; }
. img - item { text - align : center ; }
. preview { width : 120 px ; height : 120 px ; border - radius : 8 px ; }
. sign - thumb { width : 56 px ; height : 56 px ; border - radius : 4 px ; }
< / style >