提交 0bf8a3b6 作者: 施汉文

合并分支 'release' 到 'master'

Release

查看合并请求 !68
---
alwaysApply: true
---
必须简洁明了,一眼能看出完成或修改或优化的内容,不要写详细描述,不要超过50个字符。
......@@ -211,13 +211,13 @@ export default {
// 页面刷新时从 Cookie 恢复 token 到 store
// Cookies.set(
// "token",
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOjQwOTAsImRhdGEiOnsiY3Nlcl9pZCI6NDA5MCwiY3Nlcl9uYW1lIjoi5q-b57uG5LqaIn0sImlhdCI6MTc2ODU0Mjk0MiwiZXhwIjoxNzcxMTM0OTQyLCJuYmYiOjE3Njg1NDI5NDIsInN1YiI6InRva2Vu6K6k6K-BIiwianRpIjoiMjU0Zjg3OGQ3NzMyNWUyMzMyNDAwZTEwZWJkMjFkY2YifQ.3hHc6iQP-Xkz9Q5rMIOFENDdh5P-NSaRs4Y4ffbJcMg"
// ""
// );
// Cookies.set("corp_id", "wweaefe716636df3d1");
// Cookies.set("userid", "maoxiya");
// Cookies.set("cser_name", "毛细亚");
// Cookies.set("external_userid", "wm5rUgMgAAG_vfF4AHClsrS1S6MImVsQ");
// Cookies.set("cser_id", 4090);
// Cookies.set("userid", "ShanYi");
// Cookies.set("cser_name", "张妍妍");
// Cookies.set("external_userid", "wm5rUgMgAAj_YnkyHU1lVfYDT7QACqWg");
// Cookies.set("cser_id", 12386);
// Cookies.set("weixin_blongs_id", 2862);
const cookieToken = Cookies.get("token");
if (cookieToken && !this.token) {
......
......@@ -689,7 +689,20 @@ export function reportAdd(data) {
});
});
}
// 获取当前合服生态运营人员
export function currUserList(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/server/currUserList',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 申诉列表
export function appealList(data) {
data.zw_user_id = getZwUserId();
......
import Vue from 'vue'
import AsyncDialog from './index.vue'
let instance = null
let mountNode = null
function createInstance() {
if (instance && !instance._isDestroyed) {
return instance
}
const ComponentConstructor = Vue.extend(AsyncDialog)
mountNode = document.createElement('div')
document.body.appendChild(mountNode)
instance = new ComponentConstructor()
instance.$mount(mountNode)
return instance
}
export function getAsyncDialogInstance() {
return createInstance()
}
export function openAsyncDialog(options = {}) {
return createInstance().open(options)
}
export function closeAsyncDialog(action = 'close') {
if (!instance || instance._isDestroyed) {
return
}
instance.close(action)
}
export function destroyAsyncDialog() {
if (!instance) {
return
}
instance.$destroy()
if (instance.$el && instance.$el.parentNode) {
instance.$el.parentNode.removeChild(instance.$el)
}
if (mountNode && mountNode.parentNode) {
mountNode.parentNode.removeChild(mountNode)
}
instance = null
mountNode = null
}
export default openAsyncDialog
<template>
<el-dialog
v-bind="dialogAttrs"
:visible.sync="visible"
:before-close="handleBeforeClose"
@close="handleDialogClose"
@closed="handleDialogClosed"
custom-class="async-dialog-custom"
>
<slot v-if="hasDefaultSlot" v-bind="slotScope"></slot>
<div
v-else-if="dialogMessage"
class="async-dialog__body-content"
>
<i class="el-icon-warning-fill async-dialog__warning-icon"></i>
<div
class="async-dialog__message"
:class="{ 'is-html': dialogOptions.dangerouslyUseHTMLString }"
v-html="dialogContent"
></div>
</div>
<span v-if="showFooter" slot="footer" class="dialog-footer">
<slot name="footer" v-bind="slotScope">
<el-button
v-if="showCancelButton"
v-bind="cancelButtonProps"
:disabled="confirmLoading"
@click="handleCancel"
>
{{ cancelText }}
</el-button>
<el-button
v-if="showConfirmButton"
v-bind="confirmButtonProps"
:disabled="confirmDisabled"
:loading="confirmLoading"
@click="handleConfirm"
>
{{ confirmText }}
</el-button>
</slot>
</span>
</el-dialog>
</template>
<script>
const CUSTOM_OPTION_KEYS = [
'payload',
'message',
'dangerouslyUseHTMLString',
'countdown',
'confirmText',
'cancelText',
'showFooter',
'showCancelButton',
'showConfirmButton',
'confirmButtonProps',
'cancelButtonProps',
'onConfirm',
'onCancel',
'onClosed',
'beforeClose'
]
export default {
name: 'AsyncDialog',
inheritAttrs: false,
data() {
return {
visible: false,
confirmLoading: false,
confirmCountdown: 0,
countdownTimer: null,
dialogOptions: this.getDefaultOptions(),
dialogHooks: this.getDefaultHooks(),
promiseHandlers: null,
closingAction: '',
closingResult: undefined
}
},
computed: {
hasDefaultSlot() {
return Boolean(this.$scopedSlots.default || this.$slots.default)
},
dialogMessage() {
return this.dialogOptions.message
},
dialogContent() {
if (this.dialogOptions.dangerouslyUseHTMLString) {
return this.dialogMessage || ''
}
return this.escapeHtml(this.dialogMessage || '')
},
dialogAttrs() {
const dialogAttrs = {
...this.$attrs
}
Object.keys(this.dialogOptions).forEach((key) => {
if (!CUSTOM_OPTION_KEYS.includes(key)) {
dialogAttrs[key] = this.dialogOptions[key]
}
})
return {
title: '',
width: '335px',
appendToBody: true,
closeOnClickModal: false,
closeOnPressEscape: false,
destroyOnClose: true,
...dialogAttrs
}
},
showFooter() {
return this.dialogOptions.showFooter !== false
},
showCancelButton() {
return this.dialogOptions.showCancelButton !== false
},
showConfirmButton() {
return this.dialogOptions.showConfirmButton !== false
},
confirmText() {
const text = this.dialogOptions.confirmText || '确 定'
if (this.confirmCountdown > 0) {
return `${text}(${this.confirmCountdown}s)`
}
return text
},
cancelText() {
return this.dialogOptions.cancelText || '取 消'
},
confirmDisabled() {
return this.confirmLoading || this.confirmCountdown > 0
},
confirmButtonProps() {
return {
type: 'primary',
...(this.dialogOptions.confirmButtonProps || {})
}
},
cancelButtonProps() {
return {
...(this.dialogOptions.cancelButtonProps || {})
}
},
slotScope() {
return {
visible: this.visible,
loading: this.confirmLoading,
countdown: this.confirmCountdown,
payload: this.dialogOptions.payload,
options: this.dialogOptions,
confirm: this.handleConfirm,
cancel: this.handleCancel,
close: this.close
}
}
},
beforeDestroy() {
this.clearCountdown()
this.finishPromise('destroy')
},
methods: {
getDefaultOptions() {
return {
payload: undefined,
message: '',
dangerouslyUseHTMLString: false,
countdown: 0,
title: '',
width: '335px',
appendToBody: true,
closeOnClickModal: false,
closeOnPressEscape: false,
destroyOnClose: true,
showFooter: true,
showCancelButton: true,
showConfirmButton: true,
confirmText: '确 定',
cancelText: '取 消',
confirmButtonProps: null,
cancelButtonProps: null
}
},
getDefaultHooks() {
return {
onConfirm: null,
onCancel: null,
onClosed: null,
beforeClose: null
}
},
open(options = {}) {
if (this.promiseHandlers) {
return Promise.reject({
action: 'busy',
message: 'AsyncDialog 正在显示中'
})
}
const mergedOptions = {
...this.getDefaultOptions(),
...options
}
this.dialogOptions = mergedOptions
this.dialogHooks = {
onConfirm: mergedOptions.onConfirm,
onCancel: mergedOptions.onCancel,
onClosed: mergedOptions.onClosed,
beforeClose: mergedOptions.beforeClose
}
this.closingAction = ''
this.closingResult = undefined
this.confirmLoading = false
this.startCountdown(mergedOptions.countdown)
this.visible = true
return new Promise((resolve, reject) => {
this.promiseHandlers = {
resolve,
reject
}
})
},
async handleConfirm() {
if (this.confirmDisabled || !this.promiseHandlers) {
return
}
try {
this.confirmLoading = true
let result
if (typeof this.dialogHooks.onConfirm === 'function') {
result = await this.dialogHooks.onConfirm(this.getActionContext('confirm'))
}
if (result === false) {
return
}
this.closingAction = 'confirm'
this.closingResult = result
this.visible = false
} catch (error) {
return
} finally {
this.confirmLoading = false
}
},
async handleCancel() {
if (!this.promiseHandlers || this.confirmLoading) {
return
}
try {
if (typeof this.dialogHooks.onCancel === 'function') {
const result = await this.dialogHooks.onCancel(this.getActionContext('cancel'))
if (result === false) {
return
}
}
this.closingAction = 'cancel'
this.closingResult = undefined
this.visible = false
} catch (error) {
return
}
},
startCountdown(countdown) {
this.clearCountdown()
const countdownSeconds = Number(countdown) || 0
if (countdownSeconds <= 0) {
this.confirmCountdown = 0
return
}
this.confirmCountdown = Math.ceil(countdownSeconds)
this.countdownTimer = setInterval(() => {
if (this.confirmCountdown <= 1) {
this.confirmCountdown = 0
this.clearCountdown()
return
}
this.confirmCountdown -= 1
}, 1000)
},
clearCountdown() {
if (this.countdownTimer) {
clearInterval(this.countdownTimer)
this.countdownTimer = null
}
this.confirmCountdown = 0
},
close(action = 'close') {
if (!this.promiseHandlers) {
return
}
this.closingAction = this.closingAction || action
this.visible = false
},
handleClose() {
this.close('close')
},
handleDialogClose() {
if (!this.promiseHandlers) {
return
}
this.closingAction = this.closingAction || 'close'
},
async handleBeforeClose(done) {
const beforeClose = this.dialogHooks.beforeClose
if (typeof beforeClose !== 'function') {
done()
return
}
let doneCalled = false
const wrappedDone = () => {
doneCalled = true
done()
}
try {
const result = await beforeClose(wrappedDone, this.getActionContext(this.closingAction || 'close'))
if (result !== false && !doneCalled) {
done()
}
} catch (error) {
return
}
},
async handleDialogClosed() {
const action = this.closingAction || 'close'
const closeResult = this.buildResult(action)
try {
if (typeof this.dialogHooks.onClosed === 'function') {
await this.dialogHooks.onClosed(closeResult)
}
} finally {
this.finishPromise(action, closeResult)
this.resetState()
}
},
finishPromise(action, result) {
if (!this.promiseHandlers) {
return
}
const { resolve, reject } = this.promiseHandlers
this.promiseHandlers = null
if (action === 'confirm') {
resolve(result || this.buildResult(action))
return
}
reject(result || this.buildResult(action))
},
buildResult(action) {
return {
action,
payload: this.dialogOptions.payload,
result: this.closingResult
}
},
getActionContext(action) {
return {
action,
payload: this.dialogOptions.payload,
options: this.dialogOptions,
close: this.handleClose,
cancel: this.handleCancel,
confirm: this.handleConfirm
}
},
resetState() {
this.clearCountdown()
this.visible = false
this.confirmLoading = false
this.dialogOptions = this.getDefaultOptions()
this.dialogHooks = this.getDefaultHooks()
this.closingAction = ''
this.closingResult = undefined
},
escapeHtml(text) {
return String(text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
}
}
</script>
<style scoped>
.async-dialog__message {
line-height: 22px;
word-break: break-word;
white-space: pre-wrap;
}
.async-dialog__message.is-html {
white-space: normal;
}
/* 消息内容区:图标 + 文字横排 */
.async-dialog__body-content {
display: flex;
align-items: flex-start;
gap: 8px;
}
.async-dialog__warning-icon {
font-size: 20px;
color: #FF7D00;
flex-shrink: 0;
line-height: 22px;
}
</style>
<style>
/* 遮罩层样式 */
.el-overlay.is-dialog {
background-color: rgba(0, 0, 0, 0.5);
}
/* 弹窗整体 */
.async-dialog-custom {
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.async-dialog-custom.el-dialog {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* 头部 */
.async-dialog-custom .el-dialog__header {
padding: 12px 20px;
margin: 0;
display: flex;
justify-content: space-between;
align-items: center;
border: none;
}
.async-dialog-custom .el-dialog__title {
font-family: PingFang SC;
font-weight: 600;
font-size: 16px;
line-height: 24px;
color: #323335;
margin: 0;
}
.async-dialog-custom .el-dialog__headerbtn {
top: 12px;
right: 20px;
}
.async-dialog-custom .el-dialog__headerbtn .el-dialog__close {
font-size: 16px;
width: 16px;
height: 16px;
color: #86909C;
line-height: 16px;
}
.async-dialog-custom .el-dialog__headerbtn:hover .el-dialog__close {
color: #323335;
}
/* 内容区 */
.async-dialog-custom .el-dialog__body {
padding: 12px 20px;
color: #4E5969;
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
line-height: 22px;
border: none !important;
border-top: none !important;
border-bottom: none !important;
}
/* 底部 */
.async-dialog-custom .el-dialog__footer {
padding: 12px 20px;
border: none !important;
border-top: none !important;
}
/* 按钮样式 */
.async-dialog-custom .el-button {
padding: 4px 16px;
height: 32px;
border-radius: 4px;
font-family: PingFang SC;
font-weight: 400;
font-size: 14px;
line-height: 22px;
margin-left: 0 !important;
}
.async-dialog-custom .el-button--default {
border: 1px solid #D9D9D9;
color: #323335;
background: #FFFFFF;
}
.async-dialog-custom .el-button--default:hover {
color: #5A6675;
border-color: #BFBFBF;
}
.async-dialog-custom .el-button--primary {
background: #00BF8A;
border: 1px solid #00BF8A;
color: #FFFFFF;
}
.async-dialog-custom .el-button--primary:hover,
.async-dialog-custom .el-button--primary:focus {
background: #00a979;
border: 1px solid #00a979;
}
/* 倒计时期间按钮置灰,结束后恢复主题色 */
.async-dialog-custom .el-button--primary.is-disabled {
background: #6B7785;
border-color: #6B7785;
color: #FFFFFF;
opacity: 1;
cursor: not-allowed;
}
.async-dialog-custom .el-button.is-disabled {
opacity: 0.5;
}
/* 底部按钮容器 */
.async-dialog-custom .dialog-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
width: 100%;
border: none;
}
.async-dialog-custom .dialog-footer .el-button {
margin: 0;
}
</style>
<template>
<el-drawer
:visible="visible"
:title="title"
:size="size"
:append-to-body="true"
@close="$emit('close')"
:show-close="false"
>
<template #title>
<div class="flex items-center">
<svg-icon
@click="$emit('close')"
svgName="icon-fanhui"
class="mr-[8px] w-[20px] text-[20px] cursor-pointer"
></svg-icon>
<span class="text-[13px] text-[#131920] leading-[13px]">{{
title
}}</span>
</div>
</template>
<slot></slot>
<span class="dialog-footer rowFlex">
<slot name="footer">
<el-button class="btn" size="small" @click="$emit('close')">
取 消
</el-button>
<el-button
class="btn"
type="primary"
size="small"
:disabled="okDisabled"
@click="$emit('ok')"
>
确 定
</el-button>
</slot>
</span>
</el-drawer>
</template>
<script>
export default {
name: "Ui-Drawer",
props: ["visible", "size", "title", "okDisabled"],
};
</script>
<style scoped>
::v-deep .el-drawer__header {
padding: 12px 0px;
margin: 0 12px;
}
.dialog-footer {
width: 100%;
position: absolute;
bottom: 0;
padding-top: 20px;
padding-bottom: 20px;
border-top: 0;
justify-content: flex-end;
background: #fff;
z-index: 10;
.btn {
width: 60px;
height: 32px;
border-radius: 6px;
}
}
</style>
......@@ -2,6 +2,7 @@
<!-- 简单版的图文编辑器 可以上传 截图上传图片 能实现简单的图文编辑功能 -->
<div class="textEditor">
<div
v-if="showViewImage"
class="rowFlex columnCenter textEditorTitle"
@click.stop="watchImage"
>
......@@ -46,7 +47,24 @@
import { base64toFile } from '@/utils/index'
export default {
name: 'textEditor',
props: ['remark', 'contenteditable', 'domid'], // remark 原来的图文内容 contenteditable 是否可编辑 domid 编辑器的 DomId resultReamrk 方法吐出最后的编辑好的内容
props: {
remark: {
type: String,
default: ''
},
contenteditable: {
type: Boolean,
default: false
},
domid: {
type: String,
default: ''
},
showViewImage: {
type: Boolean,
default: true
}
}, // remark 原来的图文内容 contenteditable 是否可编辑 domid 编辑器的 DomId resultReamrk 方法吐出最后的编辑好的内容
data() {
return {
srcList: [],
......@@ -59,7 +77,9 @@
mounted() {
this.$nextTick(() => {
this.chatMessage = document.getElementById(`${this.domid}`)
this.remark && this.remark.length > 0 && this.contenteditable ? this.chatMessage.innerHTML = this.remark : ''
if (this.remark && this.remark.length > 0 && this.contenteditable && this.chatMessage) {
this.chatMessage.innerHTML = this.remark
}
})
},
methods: {
......@@ -71,9 +91,17 @@
},
async watchImage() {
if (this.remark.trim().length > 0) {
this.chatMessage.innerHTML = this.remark
if (!this.chatMessage) {
this.chatMessage = document.getElementById(`${this.domid}`)
}
if (this.chatMessage) {
this.chatMessage.innerHTML = this.remark
}
this.srcList = []
const imgList = this.chatMessage.querySelectorAll('img')
// 使用临时 div 来解析图片,而不依赖 DOM 元素
const tempDiv = document.createElement('div')
tempDiv.innerHTML = this.remark
const imgList = tempDiv.querySelectorAll('img')
if (imgList && imgList.length > 0) {
for (let index = 0; index < imgList.length; index++) {
this.srcList.push(imgList[index].src)
......
<template>
<div class="violationRecord">
<el-form v-loading="loading" class="violationRecordContent" label-width="90px">
<div class="flex justify-end">
<el-button type="primary" size="small" @click="reportPopupShow = true">举报</el-button>
</div>
<div v-if="violationList.length > 0">
<div v-for="(item, index) in violationList" :key="index" class="contentItem">
<el-form-item label="违规时间:">
......@@ -96,6 +99,7 @@
<el-dialog title="查看大图" :visible.sync="imageLayer" width="320px" center append-to-body @close="imageLayer = false">
<div v-html="imageSrc" class="layerImage"></div>
</el-dialog>
<reportPopup :show.sync="reportPopupShow" />
</div>
</template>
......@@ -105,11 +109,12 @@ import { violationList } from "@/api/game";
import noContent from "@/components/noContent.vue";
import { debounce } from '@/utils'
import watchMember from '@/mixins/watchMember'
import reportPopup from '@/views/popup/reportPopup/index.vue'
export default {
name: 'ViolationRecord',
components: {
noContent,
reportPopup,
},
mixins: [watchMember],
data() {
......@@ -118,6 +123,7 @@ export default {
violationList: [],
loading: false,
imageLayer: false,
reportPopupShow: false,
};
},
computed: {
......@@ -202,7 +208,7 @@ export default {
.violationRecordContent {
width: 100%;
padding: 20px;
// padding: 0 20px;
height: auto;
}
......
<template>
<el-drawer :lock-scroll="true" :title="title" :visible="show" size="300px" :append-to-body="true" @close="close">
<el-drawer :lock-scroll="true" :title="title" :visible="show" size="360px" :append-to-body="true" @close="close">
<div class="errorHandleContent">
<el-form
ref="ruleForm"
......@@ -27,6 +27,65 @@
</el-option>
</el-select>
</el-form-item>
<!-- 退回道具 -->
<el-form-item label="退回道具名称">
<div class="return-prop-container">
<!-- 道具行 -->
<div
v-for="(item, index) in ruleForm.return_prop"
:key="index"
class="prop-item"
>
<!-- 道具名称选择器 -->
<div>
<el-select
v-model="item.code"
filterable
remote
reserve-keyword
:remote-method="remoteMethod"
placeholder="请输入道具名称"
style="width: 120px;"
@change="returnPropChange(item, index)"
>
<el-option
v-for="prop in codeList"
:key="prop.value"
:label="prop.label"
:value="prop.value"
></el-option>
</el-select>
<!-- 数量输入框 -->
<el-input
v-model="item.num"
type="number"
min="1"
step="1"
placeholder="数量"
class="prop-num-input"
style="width: 80px; margin-left: 8px;"
@blur="normalizeReturnPropNum(item)"
/>
</div>
<!-- 删除按钮 -->
<i
class="el-icon-remove-outline icon"
@click="removeReturnProp(item, index)"
></i>
</div>
<!-- 添加按钮 -->
<el-button
size="small"
style="width: 80px;"
icon="el-icon-plus"
@click="addReturnProp"
>
添加
</el-button>
</div>
</el-form-item>
<el-form-item label="玩家反馈时间" prop="feedback_time">
<el-date-picker
v-model="ruleForm.feedback_time"
......@@ -58,8 +117,9 @@
</el-drawer>
</template>
<script type="text/javascript">
import { refundRequest } from '@/api/game'
import { refundRequest,selectSearch } from '@/api/game'
import textEditor from '@/components/textEditor.vue'
import { debounce } from '@/utils'
export default {
name: 'orderRefund',
components: { textEditor },
......@@ -67,9 +127,11 @@
data() {
return {
searchLoading: false,
number: 1,
reasonList: [
{ label: '充值未到账', value: 1 } // 掌微只有一个原因
],
codeList: [],
ruleForm: {
reason: 1,
feedback_time: '',
......@@ -80,7 +142,8 @@
remark: '',
extra_attribution: [],
user_name: '',
user_id: ''
user_id: '',
return_prop: []
},
rules: {
'orders.order_id': [
......@@ -112,7 +175,8 @@
remark: '',
extra_attribution: [],
user_name: '',
user_id: ''
user_id: '',
return_prop: []
}
this.ruleForm.remark = ''
this.ruleForm.orders.order_id = this.info.order_id
......@@ -121,8 +185,9 @@
}
}
},
mounted() {
created() {
// 创建防抖搜索函数
this.debouncedSearch = debounce(this.searchProps, 500)
},
methods: {
resultReamrk(html) {
......@@ -132,12 +197,90 @@
resultUpload(value) {
this.ruleForm.extra_attribution = value
},
// 添加退回道具
addReturnProp() {
if (this.ruleForm.return_prop.length >= 10) {
this.$message.warning('最多添加 10 个道具')
return
}
this.ruleForm.return_prop.push({
code: '',
num: 1
})
},
// 删除退回道具
removeReturnProp(item, index) {
this.ruleForm.return_prop.splice(index, 1)
},
// 道具选择变化
returnPropChange(item, index) {
const selectedProp = this.codeList.find(prop => prop.value === item.code)
// 使用 $set 确保 Vue 能检测到变化
this.$set(this.ruleForm.return_prop, index, {
...item,
name: selectedProp ? selectedProp.label : ''
})
},
// 搜索道具(实际执行的方法)
async searchProps(query) {
if (!this.info.main_game_id) {
this.$message.warning('缺少游戏信息,无法搜索道具')
return
}
const trimmedQuery = query.trim()
if (!trimmedQuery) {
this.codeList = []
return
}
try {
const data = {
type: 'misoperation_code',
value: trimmedQuery,
main_game_id: this.info.main_game_id
}
const res = await selectSearch(data)
if (res.status_code === 1) {
this.codeList = res.data.data || []
}
} catch (error) {
console.error('搜索道具失败:', error)
this.$message.error('搜索道具失败')
}
},
// 远程搜索方法(防抖后的方法)
remoteMethod(query) {
this.debouncedSearch(query)
},
// 数量输入框失焦后规范为整数且不小于 1(type=number 时 v-model 可能为字符串)
normalizeReturnPropNum(item) {
const raw = item.num
const n = parseInt(String(raw).trim(), 10)
if (raw === '' || raw === null || Number.isNaN(n) || n < 1) {
this.$set(item, 'num', 1)
} else {
this.$set(item, 'num', n)
}
},
close() {
this.$emit('update:show', false)
},
async submit() {
this.$refs.ruleForm.validate(async (valid) => {
if (valid) {
// 过滤掉未选择道具的项
const validReturnProp = this.ruleForm.return_prop.filter(
item => item.code && item.num > 0
)
// 验证退回道具数据
const invalidProp = this.ruleForm.return_prop.find(item =>
(item.code && !item.num) || (!item.code && item.num)
)
if (invalidProp) {
this.$message.warning('请完善退回道具信息')
return
}
this.ruleForm.orders.refund_amount = this.info.amount
this.searchLoading = true
const { reason, feedback_time, orders, remark, extra_attribution } = this.ruleForm
......@@ -149,18 +292,22 @@
remark,
extra_attribution,
user_name: username,
user_id: id
user_id: id,
return_prop: validReturnProp
}
try {
const res = await refundRequest(data)
if (res.status_code === 1) {
this.$message.success(res.msg)
this.close()
}
this.searchLoading = false
this.close()
} catch (error) {
}catch (error) {
console.error('提交退款申请失败:', error)
this.$message.error('提交失败,请重试')
} finally {
this.searchLoading = false
}
} else {
return false
}
......@@ -220,5 +367,80 @@
margin-bottom: 20px;
}
}
.return-prop-container {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.prop-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
background: #f7f8fa;
border-radius: 4px;
height: 48px;
.el-select {
::v-deep .el-input__inner {
height: 32px;
line-height: 32px;
font-size: 13px;
font-family: 'PingFang SC', sans-serif;
color: #323335;
background: white;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 4px 12px;
&::placeholder {
color: #c9cdd4;
font-size: 13px;
}
}
}
.prop-num-input {
::v-deep .el-input__inner {
height: 32px;
line-height: 32px;
font-size: 13px;
font-family: 'PingFang SC', sans-serif;
color: #323335;
text-align: center;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 4px 12px;
box-sizing: border-box;
appearance: textfield;
-moz-appearance: textfield;
}
}
.icon {
font-size: 18px;
color: #00BF8A;
cursor: pointer;
transition: color 0.2s;
&:hover {
color: #409eff;
}
}
}
.icon {
font-size: 18px;
color: #409eff;
cursor: pointer;
transition: color 0.2s;
&:hover {
color: #66b1ff;
}
}
</style>
\ No newline at end of file
......@@ -463,6 +463,7 @@ import {
getLandingPageConfig,
getMemberTransStatus,
memberRegGameCloneLink,
getMemberLabel,
} from '@/api/game';
import {
getRecentSendLog,
......@@ -483,6 +484,7 @@ import sendSelectChannel from './sendGame/sendSelectChannel.vue';
import gameLogMixin from '@/mixins/gameLogMixin';
import { sendChatMessage } from '@/utils/index';
import QrcodeVue from 'qrcode.vue';
import { openAsyncDialog } from '@/components/AsyncDialog';
export default {
name: 'sendGame',
mixins: [gameLogMixin],
......@@ -532,6 +534,11 @@ export default {
h5CloneGameInfo: {},
qrCodeValue: '', // 二维码内容
qrCodeSize: 200, // 二维码大小
transRiskMemberId: '',
transRiskInfo: {
isHighRisk: false,
isPhishing: false,
},
};
},
mounted() {
......@@ -553,6 +560,11 @@ export default {
accountSelect(newVal, oldVal) {
// 切换 w 账号的时候清空 conversionGameList 数据
this.conversionGameList = [];
this.transRiskMemberId = '';
this.transRiskInfo = {
isHighRisk: false,
isPhishing: false,
};
this.getMemberTransStatus();
if (newVal && newVal !== '' && this.bindGameUserList.length > 0) {
this.disabled = false;
......@@ -883,8 +895,9 @@ export default {
'2441',
'2442',
'2443',
'2595',
];
// 过滤掉 game_name 为"破日开天"的数据 英雄霸业 741:独步天龙 912:神权之战 繁华梦/2274 黑夜传奇/685 深海花园/2326
// 过滤掉 game_name 为"破日开天"的数据 英雄霸业 741:独步天龙 912:神权之战 繁华梦/2274 黑夜传奇/685 深海花园/2326 种花-内部直播专用/2595
const filteredChildren = item.children.filter((child) => {
return (
child.game_name !== '破日开天' &&
......@@ -1049,11 +1062,102 @@ export default {
return false;
}
},
sendLink: throttle(function (item, type) {
isTruthyFlag(value) {
return value === true || value === 1 || value === '1';
},
async getTransRiskInfo() {
const localRiskInfo = {
isHighRisk:
Boolean(this.gameUserInfo && this.gameUserInfo.exp_ip) ||
this.isTruthyFlag(this.gameUserInfo && this.gameUserInfo.change_risk),
isPhishing:
this.isTruthyFlag(
this.chatUserInfo && this.chatUserInfo.is_phishing_account,
) ||
this.isTruthyFlag(
this.chatUserInfo && this.chatUserInfo.change_appraisal,
) ||
this.isTruthyFlag(
this.gameUserInfo && this.gameUserInfo.change_appraisal,
),
};
if (!this.accountSelect) {
return localRiskInfo;
}
if (this.transRiskMemberId === this.accountSelect) {
return {
isHighRisk:
localRiskInfo.isHighRisk || this.transRiskInfo.isHighRisk,
isPhishing:
localRiskInfo.isPhishing || this.transRiskInfo.isPhishing,
};
}
try {
const res = await getMemberLabel({
member_id: this.accountSelect,
label_type: [4, 6],
});
const memberLabelList = res?.data?.data || [];
const riskLabel = memberLabelList.find(
(item) => item.label_type == 4,
);
const phishingLabel = memberLabelList.find(
(item) => item.label_type == 6,
);
this.transRiskMemberId = this.accountSelect;
this.transRiskInfo = {
isHighRisk: this.isTruthyFlag(riskLabel && riskLabel.label_value),
isPhishing: this.isTruthyFlag(
phishingLabel && phishingLabel.label_value,
),
};
} catch (error) {
console.log(error);
this.transRiskMemberId = this.accountSelect;
this.transRiskInfo = {
isHighRisk: false,
isPhishing: false,
};
}
return {
isHighRisk: localRiskInfo.isHighRisk || this.transRiskInfo.isHighRisk,
isPhishing: localRiskInfo.isPhishing || this.transRiskInfo.isPhishing,
};
},
async confirmTransRiskBeforeSend() {
const riskInfo = await this.getTransRiskInfo();
if (!riskInfo.isHighRisk && !riskInfo.isPhishing) {
return true;
}
try {
await openAsyncDialog({
title: '确认发送',
message:
'当前用户为钓鱼号/高风险用户,请与组长确认是否发送转端链接',
countdown: 3,
confirmText: '确 定',
cancelText: '取 消',
});
return true;
} catch (error) {
return false;
}
},
sendLink: throttle(async function (item, type) {
if (!this.transMemberStatus) {
this.$message.warning('当前w账号不满足转端要求,请联系组长处理');
return;
}
if (!(await this.confirmTransRiskBeforeSend())) {
return;
}
console.log(item, '转端发送仅发送链接');
const result = this.handleAccount();
if (!result) {
......@@ -1077,11 +1181,14 @@ export default {
item.type = 1;
this.sendGameLog(item);
}, 500),
sendPassword: throttle(function (item, type) {
sendPassword: throttle(async function (item, type) {
if (!this.transMemberStatus) {
this.$message.warning('当前w账号不满足转端要求,请联系组长处理');
return;
}
if (!(await this.confirmTransRiskBeforeSend())) {
return;
}
console.log(item, '转端仅发送账号密码');
const result = this.handleAccount();
if (!result) {
......@@ -1110,11 +1217,14 @@ export default {
console.log(err);
});
}, 500),
sendMessage: throttle(function (item, type) {
sendMessage: throttle(async function (item, type) {
if (!this.transMemberStatus) {
this.$message.warning('当前w账号不满足转端要求,请联系组长处理');
return;
}
if (!(await this.confirmTransRiskBeforeSend())) {
return;
}
const result = this.handleAccount();
if (!result) {
this.$message.warning('请稍后再试');
......@@ -1170,11 +1280,14 @@ export default {
this.getMediaId(value, 'image');
},
// 转端发送落地页面
sendDownLoadPage: throttleStart(function (items, type, index) {
sendDownLoadPage: throttleStart(async function (items, type, index) {
if (!this.transMemberStatus) {
this.$message.warning('当前w账号不满足转端要求,请联系组长处理');
return;
}
if (!(await this.confirmTransRiskBeforeSend())) {
return;
}
this.$set(
this.conversionGameList[index],
'send_trans_page_id',
......@@ -1184,6 +1297,13 @@ export default {
}, 500),
// 转端发送游戏分身包 h5 安卓游戏 IOS游戏 发送分身包
async sendTransferCloneGame(type, items) {
if (!this.transMemberStatus) {
this.$message.warning('当前w账号不满足转端要求,请联系组长处理');
return;
}
if (!(await this.confirmTransRiskBeforeSend())) {
return;
}
if (!this.h5CloneGameInfo?.data?.h5_download_url) {
this.h5CloneGameInfo =
(await memberRegGameCloneLink({
......@@ -1239,6 +1359,9 @@ export default {
this.$message.warning('当前w账号不满足转端要求,请联系组长处理');
return;
}
if (!(await this.confirmTransRiskBeforeSend())) {
return;
}
const result = this.handleAccount();
if (!result) {
this.$message.warning('请稍后再试');
......
......@@ -65,7 +65,9 @@
<div v-for="(item, index) in orderList" :key="index" class="orderDetails">
<div class="bridgeMain">
<p class="text">{{ item.pay_type_text || item.pay_type }}</p>
<img :src="sanjiaoxing" class="bridge" />
<span class="bridge">
<svg-icon icon-class="sanjiaoxing" />
</span>
</div>
<div class="orderDetailsTitle">
<!-- || item.pay_type=='抖音支付'去掉抖音支付补单操作 -->
......@@ -174,7 +176,6 @@ import searchSelect from './components/order/searchUser.vue'
import orderRefund from './components/order/orderRefund.vue'
import refundLog from './components/order/refundLog.vue'
import { throttle } from '@/utils'
import sanjiaoxing from '@/assets/icon/svg/sanjiaoxing.svg'
import noContent from '@/components/noContent.vue'
export default {
name: 'orderList',
......@@ -188,7 +189,6 @@ export default {
},
data() {
return {
sanjiaoxing,
loading: false,
activeType: 2,
activeTypeStr: '2',
......@@ -687,6 +687,7 @@ export default {
position: absolute;
top: 0;
right: 0;
color: #ffe59a;
}
}
......
<template>
<Drawer
class="report-popup-drawer"
title="举报申请"
:visible="show"
size="362px"
@close="close"
>
<div class="drawer-content-main">
<div class="drawer-content">
<el-form
ref="reportForm"
:model="reportForm"
:rules="reportRules"
:validate-on-rule-change="false"
label-position="top"
class="reportForm"
label-width="80px"
>
<el-form-item label="主游戏" prop="main_game_id">
<mainGameSelect
label=""
:default-value="reportForm.main_game_id"
style="width: 100%"
width="100%"
@result="mainGameResult"
/>
</el-form-item>
<div class="role-card">
<el-form-item label="" class="role-type-item">
<el-radio-group v-model="reportForm.role_id_type" @change="onReporteeTypeChange">
<el-radio label="cp_role_id">CP角色ID</el-radio>
<el-radio label="role_id">掌游角色ID</el-radio>
</el-radio-group>
</el-form-item>
<!--被举报人 -->
<inputTags
:input-select-list.sync="reporteeIds"
:label-text="reporteeLabelText"
:placeholder="reporteePlaceholder"
:disabled="reportForm.main_game_id === ''"
:rule-prop="reporteeIdField"
@inputChange="inputChange"
/>
<inputTags
:input-select-list.sync="reportForm.role_name"
label-text="被举报人角色名"
:placeholder="autoFillPlaceholder"
:disabled="true"
rule-prop="role_name"
/>
<inputTags
:input-select-list.sync="reportForm.server_name"
label-text="被举报人区服"
:placeholder="autoFillPlaceholder"
:disabled="true"
rule-prop="server_name"
/>
<!-- 仅 currUserList 有生态运营数据时展示 -->
<template v-if="hasEcoUserData">
<el-form-item label="被举报人生态运营" prop="eco_user">
<el-input
:value="ecoUserNamesDisplay"
style="width: 100%"
type="input"
disabled
/>
</el-form-item>
</template>
</div>
<div class="role-card">
<el-form-item label="" class="role-type-item">
<el-radio-group v-model="reportForm.report_role_id_type" @change="onReporterTypeChange">
<el-radio label="cp_role_id">CP角色ID</el-radio>
<el-radio label="role_id">掌游角色ID</el-radio>
</el-radio-group>
</el-form-item>
<!-- 举报人 -->
<el-form-item :label="reporterLabelText" :prop="reporterIdField">
<el-input
v-model="reporterId"
style="width: 100%"
:placeholder="reporterPlaceholder"
@change="change_report_role_id"
></el-input>
</el-form-item>
<el-form-item label="举报人角色名" prop="report_role_name">
<el-input
v-model="reportForm.report_role_name"
style="width: 100%"
:placeholder="reporterAutoFillPlaceholder"
disabled
></el-input>
</el-form-item>
<el-form-item label="举报人区服" prop="report_server_name">
<el-input
v-model="reportForm.report_server_name"
:placeholder="reporterAutoFillPlaceholder"
style="width: 100%"
disabled
></el-input>
</el-form-item>
</div>
<template v-if="hasEcoUserData">
<el-form-item label="是否有生态运营" prop="is_negotiation">
<el-radio-group v-model="reportForm.is_negotiation">
<el-radio :label="1"></el-radio>
<el-radio :label="2"></el-radio>
</el-radio-group>
</el-form-item>
</template>
<el-form-item label="违规操作" prop="violation_type">
<el-select
v-model="reportForm.violation_type"
style="width: 100%"
clearable
placeholder="请选择"
>
<el-option
v-for="item in violation_type_list"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item prop="remark" class="detail-form-item">
<div slot="label" class="detail-label">
<span>详情</span>
<span class="view-image-btn" @mousedown.stop.prevent="handleViewImage">
<i class="el-icon-view"></i>
<span>查看大图</span>
</span>
</div>
<textEditor
ref="textEditorRef"
:remark.sync="reportForm.remark"
domid="report_content"
class="report-editor"
:contenteditable="true"
:show-view-image="false"
@resultReamrk="resultReamrk"
/>
</el-form-item>
</el-form>
</div>
</div>
<template #footer>
<el-button class="btn" @click="close">取消</el-button>
<el-button
class="btn"
type="primary"
:loading="loading"
@click="submit"
>确定</el-button>
</template>
</Drawer>
</template>
<script type="text/javascript">
import mainGameSelect from '@/components/mainGame.vue'
import textEditor from '@/components/textEditor.vue'
import { searchcondition, reportAdd,currUserList } from '@/api/game'
import { mapState } from 'vuex'
import inputTags from '@/components/inputTags/index.vue'
import Drawer from '@/components/common/Drawer.vue'
export default {
components: {
mainGameSelect,
textEditor,
inputTags,
Drawer
},
computed: {
...mapState('game', ['accountSelect']),
...mapState('user', ['userInfo']),
/**
* 被举报人当前 type 对应的字段名(cp_role_id / role_id)
*/
reporteeIdField() {
return this.reportForm.role_id_type === 'role_id' ? 'role_id' : 'cp_role_id'
},
/**
* 举报人当前 type 对应的字段名(report_cp_role_id / report_role_id)
*/
reporterIdField() {
return this.reportForm.report_role_id_type === 'role_id' ? 'report_role_id' : 'report_cp_role_id'
},
/**
* inputTags 用:被举报人 ID 数组(按 type 路由读写,仅写入当前类型对应字段)
*/
reporteeIds: {
get() {
return this.reportForm[this.reporteeIdField] || []
},
set(val) {
this.reportForm[this.reporteeIdField] = val
}
},
/**
* 举报人 input v-model(按 type 路由读写,仅写入当前类型对应字段)
*/
reporterId: {
get() {
return this.reportForm[this.reporterIdField] || ''
},
set(val) {
this.reportForm[this.reporterIdField] = val
}
},
reporteeLabelText() {
return this.reportForm.role_id_type === 'role_id' ? '被举报人掌游角色ID' : '被举报人CP角色ID'
},
reporteePlaceholder() {
return this.reportForm.role_id_type === 'role_id' ? '请输入被举报人掌游角色ID' : '请输入被举报人CP角色ID'
},
reporterLabelText() {
return this.reportForm.report_role_id_type === 'role_id' ? '举报人掌游角色ID' : '举报人CP角色ID'
},
reporterPlaceholder() {
return this.reportForm.report_role_id_type === 'role_id' ? '请输入举报人掌游角色ID' : '请输入举报人CP角色ID'
},
autoFillPlaceholder() {
const label = this.reportForm.role_id_type === 'role_id' ? '掌游角色ID' : 'CP角色ID'
return `输入${label}后自动获取`
},
reporterAutoFillPlaceholder() {
const label = this.reportForm.report_role_id_type === 'role_id' ? '掌游角色ID' : 'CP角色ID'
return `输入${label}后自动获取`
},
/**
* 生态运营展示文案:有列表时拼接全部 user_name;无列表时回显表单已有值(如编辑态)
*/
ecoUserNamesDisplay() {
if (this.eco_user_list && this.eco_user_list.length > 0) {
return this.eco_user_list
.map((item) => item.user_name || item.name)
.filter(Boolean)
.join('、')
}
return this.reportForm.eco_user || ''
},
/**
* 生态运营接口有返回列表数据时才展示相关表单项并参与校验
*/
hasEcoUserData() {
return Array.isArray(this.eco_user_list) && this.eco_user_list.length > 0
},
/**
* 无生态运营数据时不校验 eco_user、is_negotiation
*/
reportRules() {
const reporteeField = this.reporteeIdField
const reporteeLabel = this.reportForm.role_id_type === 'role_id' ? '掌游角色ID' : 'CP角色ID'
const rules = {
main_game_id: [
{ required: true, message: '请选择主游戏', trigger: 'change' }
],
[reporteeField]: [
{
required: true,
message: `请选择被举报人${reporteeLabel}`,
trigger: 'change'
}
],
server_name: [
{ required: true, message: '请选择区服', trigger: 'change' }
],
violation_type: [
{ required: true, message: '请选择违规操作', trigger: 'change' }
],
remark: [{ required: true, message: '请填写详情', trigger: 'change' }]
}
if (this.hasEcoUserData) {
rules.eco_user = [
{
required: true,
message: '被举报人生态运营',
trigger: 'change'
}
]
rules.is_negotiation = [
{
required: true,
message: '请选择是否有生态运营',
trigger: 'change'
}
]
}
return rules
}
},
props: {
show: {
type: Boolean,
default: false
},
reportInfo: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
reportForm: {
main_game_id: '',
report_role_id_type: 'cp_role_id',
role_id_type: 'cp_role_id',
cp_role_id: [],
role_id: [],
role_name: [],
server_name: [],
violation_type: '',
report_cp_role_id: '',
report_role_id: '',
report_role_name: '',
report_server_name: '',
eco_user: '',
is_negotiation: '',
remark: ''
},
zyou_server_id_list: [], // 存储被举报人的区服ID列表
violation_type_list: [
{
label: '减轻处罚',
value: 1
},
{
label: '解除禁言',
value: 2
},
{
label: '解除封禁',
value: 3
}
],
loading: false,
cp_role_id_loading: false,
serverNameList: [],
searchUserOption: [],
eco_user_list: [] // 生态运营人员列表
}
},
watch: {
show(newVal, oldVal) {
if (newVal) {
console.log('显示弹窗')
}
// 重新提交:抽屉再次打开时重新拉取生态运营(组件未销毁、仅 show 切换的场景)
if (newVal === true && oldVal === false && this.reportInfo && this.reportInfo.id) {
this.$nextTick(() => {
this.refreshEcoUserForResubmit()
})
}
},
/**
* 生态运营区块隐藏时清除这两项校验状态,避免残留红字
*/
hasEcoUserData(val) {
if (!val && this.$refs.reportForm) {
this.$nextTick(() => {
this.$refs.reportForm.clearValidate(['eco_user', 'is_negotiation'])
})
}
}
},
mounted() {
this.searchcondition()
if (this.reportInfo && this.reportInfo.id) {
const info = this.reportInfo
const {
main_game_id = 1,
cp_role_id = [],
role_id = [],
role_name = [],
server_name = [],
violation_type = '',
report_cp_role_id = '',
report_role_id = '',
report_role_name = '',
report_server_name = '',
eco_user = '',
is_negotiation = '',
remark = ''
} = info
this.reportForm = {
main_game_id: Number(main_game_id),
cp_role_id: Array.isArray(cp_role_id) ? cp_role_id : cp_role_id != '' ? [cp_role_id] : [],
role_id: Array.isArray(role_id) ? role_id : role_id !== '' ? [role_id] : [],
role_name: Array.isArray(role_name) ? role_name : role_name !== '' ? [role_name] : [],
server_name: Array.isArray(server_name) ? server_name : server_name !== '' ? [server_name] : [],
violation_type: Number(violation_type),
report_cp_role_id,
report_role_id_type: this.normalizeRoleIdType(
this.pickReportInfoField(info, 'report_role_id_type', 'reportRoleIdType')
),
role_id_type: this.normalizeRoleIdType(
this.pickReportInfoField(info, 'role_id_type', 'roleIdType')
),
report_role_id,
report_role_name,
report_server_name,
eco_user,
is_negotiation: Number(is_negotiation) || '',
remark
}
this.$nextTick(() => {
this.refreshEcoUserForResubmit()
})
}
},
methods: {
/**
* 从举报详情中取字段,兼容下划线 / 驼峰(列表接口字段名可能不一致)
*/
pickReportInfoField(info, snakeKey, camelKey) {
const a = info[snakeKey]
if (a !== undefined && a !== null && String(a).trim() !== '') return a
const b = info[camelKey]
if (b !== undefined && b !== null && String(b).trim() !== '') return b
return undefined
},
/**
* 与 el-radio 的 label 对齐,仅允许 cp_role_id / role_id,缺省或非法时默认 CP 角色 ID
*/
normalizeRoleIdType(val) {
if (val === undefined || val === null) return 'cp_role_id'
if (val === 1 || val === '1') return 'cp_role_id'
if (val === 2 || val === '2') return 'role_id'
const s = String(val).trim()
if (s === '') return 'cp_role_id'
if (s === 'cp_role_id' || s === 'role_id') return s
const lower = s.toLowerCase()
if (lower === 'cp_role_id' || lower === 'role_id') return lower
return 'cp_role_id'
},
// inputTag 返回的值
inputChange(value) {
this.reportForm[this.reporteeIdField] = value
this.change_reportee_role()
},
/**
* 切换被举报人 ID 类型:清空两侧输入与回填,避免数据残留与校验红字
*/
onReporteeTypeChange() {
this.reportForm.cp_role_id = []
this.reportForm.role_id = []
this.reportForm.role_name = []
this.reportForm.server_name = []
this.zyou_server_id_list = []
this.eco_user_list = []
this.reportForm.eco_user = ''
this.$nextTick(() => {
if (this.$refs.reportForm) {
this.$refs.reportForm.clearValidate([
'cp_role_id',
'role_id',
'role_name',
'server_name',
'eco_user',
'is_negotiation'
])
}
})
},
/**
* 切换举报人 ID 类型:清空举报人输入与回填
*/
onReporterTypeChange() {
this.reportForm.report_cp_role_id = ''
this.reportForm.report_role_id = ''
this.reportForm.report_role_name = ''
this.reportForm.report_server_name = ''
this.$nextTick(() => {
if (this.$refs.reportForm) {
this.$refs.reportForm.clearValidate([
'report_cp_role_id',
'report_role_id',
'report_role_name',
'report_server_name'
])
}
})
},
async change_reportee_role() {
if (this.reportForm.main_game_id == '') {
this.$message.warning('请先选择主游戏')
return
}
const inputIds = this.reportForm[this.reporteeIdField]
if (!inputIds || inputIds.length == 0) {
this.$message.warning('请按回车键确定输入')
return
}
this.cp_role_id_loading = true
this.$message.warning('查询中,请稍等')
// 按 type 用对应字段名作为查询参数
const res = await searchcondition({
type: 'role',
[this.reporteeIdField]: inputIds,
main_game_id: this.reportForm.main_game_id
})
this.cp_role_id_loading = false
if (res.status_code == 1 && res.data.data.length > 0) {
// 当前类型字段保留用户输入,另一类型字段从接口返回回填
if (this.reporteeIdField === 'cp_role_id') {
this.reportForm.role_id = res.data.data.map((item) => item.value)
} else {
this.reportForm.cp_role_id = res.data.data.map((item) => item.cp_role_id)
}
this.reportForm.role_name = res.data.data.map((item) => item.label)
this.reportForm.server_name = res.data.data.map(
(item) => item.server_name
)
// 收集被举报人的区服ID(优先使用被举报人的区服ID)
this.zyou_server_id_list = res.data.data
.map((item) => item.zyou_server_id)
.filter((id) => id !== undefined && id !== null && id !== '')
// 加载生态运营人员列表
this.loadEcoUserList()
} else {
this.$message.warning('角色信息不存在')
this.zyou_server_id_list = []
this.eco_user_list = []
this.reportForm.eco_user = ''
}
},
async change_report_role_id() {
const inputId = this.reportForm[this.reporterIdField]
if (!!inputId && !!this.reportForm.main_game_id) {
const res = await searchcondition({
type: 'role',
[this.reporterIdField === 'report_role_id' ? 'role_id' : 'cp_role_id']: inputId,
main_game_id: this.reportForm.main_game_id
})
if (res.status_code == 1 && res.data.data.length > 0) {
// 当前类型字段保留用户输入,另一类型字段从接口返回回填
if (this.reporterIdField === 'report_cp_role_id') {
this.reportForm.report_role_id = res.data.data[0]
? res.data.data[0].value
: ''
} else {
this.reportForm.report_cp_role_id = res.data.data[0]
? res.data.data[0].cp_role_id
: ''
}
this.reportForm.report_role_name = res.data.data[0]
? res.data.data[0].label
: ''
this.reportForm.report_server_name = res.data.data[0]
? res.data.data[0].server_name
: ''
// 收集所有的 zyou_server_id
this.zyou_server_id_list = res.data.data
.map((item) => item.zyou_server_id)
.filter((id) => id !== undefined && id !== null && id !== '')
// 加载生态运营人员列表
this.loadEcoUserList()
} else {
this.$message.warning('角色信息不存在')
this.zyou_server_id_list = []
this.eco_user_list = []
this.reportForm.eco_user = ''
}
} else {
this.$message.warning('请先选择主游戏')
this.zyou_server_id_list = []
this.eco_user_list = []
this.reportForm.eco_user = ''
}
},
searchcondition() {
const data = {
type: 'dictionaries',
table_name: 'zs_violation',
field_name: 'violation_type'
}
searchcondition(data).then((res) => {
this.violation_type_list = res.data.data
})
},
resultReamrk(text) {
// console.log(text, '最后编辑器的内容')
},
handleViewImage() {
console.log('handleViewImage clicked')
console.log('textEditorRef:', this.$refs.textEditorRef)
if (this.$refs.textEditorRef && this.$refs.textEditorRef.watchImage) {
this.$refs.textEditorRef.watchImage()
} else {
console.log('textEditorRef or watchImage not found')
}
},
/**
* 根据 eco_user_list 同步提交字段 eco_user(全部姓名用顿号拼接)
*/
syncEcoUserFromList() {
if (!this.eco_user_list || this.eco_user_list.length === 0) {
this.reportForm.eco_user = ''
return
}
this.reportForm.eco_user = this.eco_user_list
.map((item) => item.user_name || item.name)
.filter(Boolean)
.join('、')
},
/**
* 重新提交(reportInfo.id 存在)时:用表单里的角色信息补全 zyou_server_id,再请求生态运营列表
*/
async refreshEcoUserForResubmit() {
if (!(this.reportInfo && this.reportInfo.id)) return
const mainGameId = this.reportForm.main_game_id
if (!mainGameId) return
try {
const reporteeRaw = this.reportForm[this.reporteeIdField]
const reporteeIds = Array.isArray(reporteeRaw)
? reporteeRaw.filter(Boolean)
: reporteeRaw
? [reporteeRaw]
: []
if (reporteeIds.length > 0) {
const res = await searchcondition({
type: 'role',
[this.reporteeIdField]: reporteeIds,
main_game_id: mainGameId
})
if (res.status_code == 1 && res.data.data && res.data.data.length > 0) {
this.zyou_server_id_list = res.data.data
.map((item) => item.zyou_server_id)
.filter((id) => id !== undefined && id !== null && id !== '')
await this.loadEcoUserList()
return
}
}
const reporterRaw = this.reportForm[this.reporterIdField]
if (reporterRaw) {
const reporterQueryField = this.reporterIdField === 'report_role_id' ? 'role_id' : 'cp_role_id'
const res = await searchcondition({
type: 'role',
[reporterQueryField]: reporterRaw,
main_game_id: mainGameId
})
if (res.status_code == 1 && res.data.data && res.data.data.length > 0) {
this.zyou_server_id_list = res.data.data
.map((item) => item.zyou_server_id)
.filter((id) => id !== undefined && id !== null && id !== '')
await this.loadEcoUserList()
return
}
}
this.zyou_server_id_list = []
this.eco_user_list = []
this.syncEcoUserFromList()
} catch (e) {
console.error('重新提交态刷新生态运营失败:', e)
}
},
/**
* 加载生态运营人员列表(currUserList)
*/
async loadEcoUserList() {
// 检查是否有区服ID列表
if (!this.zyou_server_id_list || this.zyou_server_id_list.length === 0) {
this.eco_user_list = []
this.syncEcoUserFromList()
return
}
try {
const res = await currUserList({
zyou_server_id: this.zyou_server_id_list,
settle_state: 1
})
console.log('currUserList 接口返回数据:', res)
if (res.status_code === 1 && res.data) {
// 处理返回的数据结构:{ "29_22_7q_73528": [...] },key 是动态的
// 将动态 key 替换成 data,统一处理
let ecoUserList = []
// 遍历 res.data 的所有 key,提取数组数据
Object.keys(res.data).forEach((key) => {
if (Array.isArray(res.data[key])) {
// 将动态 key 的值提取出来,统一处理
const items = res.data[key].map((item) => ({
...item,
eco_user: key, // 使用动态 key 作为 eco_user(用于提交)
data: res.data[key] // 添加 data 字段,值为该 key 对应的数组
}))
ecoUserList = ecoUserList.concat(items)
}
})
// 将数据结构转换为 { data: [...] } 格式
const transformedData = {
data: ecoUserList
}
console.log('转换后的数据结构(key 替换为 data):', transformedData)
// 如果 res.data.data 存在,优先使用(兼容其他数据结构)
if (res.data.data && Array.isArray(res.data.data) && res.data.data.length > 0) {
this.eco_user_list = res.data.data
} else if (ecoUserList.length > 0) {
this.eco_user_list = ecoUserList
} else {
this.eco_user_list = []
}
console.log('处理后的生态运营人员列表:', this.eco_user_list)
this.syncEcoUserFromList()
} else {
this.eco_user_list = []
this.syncEcoUserFromList()
}
} catch (error) {
console.error('查询生态运营人员失败:', error)
this.eco_user_list = []
this.syncEcoUserFromList()
this.$message.error('查询生态运营人员失败,请稍后重试')
}
},
/**
* 主游戏变更:清空角色与生态运营相关数据
*/
mainGameResult(data) {
this.reportForm.main_game_id = data
this.reportForm.cp_role_id = []
this.reportForm.role_id = []
this.reportForm.role_name = []
this.reportForm.server_name = []
this.zyou_server_id_list = []
this.eco_user_list = []
this.reportForm.eco_user = ''
// 规则变化或 model 清空时避免误触全表/其它项校验;并清掉已重置字段的红字
this.$nextTick(() => {
if (this.$refs.reportForm) {
this.$refs.reportForm.clearValidate([
'main_game_id',
'cp_role_id',
'role_name',
'server_name',
'eco_user',
'is_negotiation',
'violation_type',
'remark'
])
}
})
},
close() {
this.$emit('update:show', false)
},
async submit() {
console.log(this.userInfo, 'reportForm')
if (this.reportForm.role_id === '') {
this.$message.error('请选择角色')
return
}
if (this.reportForm.violation_type === '') {
this.$message.error('请选择违规操作')
return
}
if (
this.hasEcoUserData &&
(!this.reportForm.eco_user || this.reportForm.eco_user.trim() === '')
) {
this.$message.error('请匹配角色后自动获取生态运营信息')
return
}
if (this.reportForm.remark === '') {
this.$message.error('请填写详情')
return
}
this.loading = true
const data = {
main_game_id: this.reportForm.main_game_id,
role_id: this.reportForm.role_id,
cp_role_id: this.reportForm.cp_role_id,
report_cp_role_id: this.reportForm.report_cp_role_id,
role_id_type: this.reportForm.role_id_type,
report_role_id_type: this.reportForm.report_role_id_type,
report_role_id: this.reportForm.report_role_id || 0,
violation_type: this.reportForm.violation_type,
remark: this.reportForm.remark,
eco_user: this.hasEcoUserData ? this.reportForm.eco_user || '' : '',
is_negotiation: this.hasEcoUserData
? this.reportForm.is_negotiation || 1
: 2,
user_id: this.userInfo.id,
user_name: this.userInfo.username
}
setTimeout(() => {
this.loading = false
}, 2000)
try {
const res = await reportAdd(data)
this.loading = false
if (res.status_code === 1) {
this.$message.success('提交成功')
this.close()
if (this.reportInfo && this.reportInfo.id) {
this.$emit('updateReportList')
}
}
} catch (error) {
this.loading = false
}
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .report-popup-drawer {
max-width: 100vw;
.el-drawer__header {
height: 46px;
padding: 12px 0;
margin: 0 12px;
border-bottom: 1px solid #ebedf0;
box-sizing: border-box;
.flex {
width: 100%;
}
span {
color: #131920;
font-family: PingFang SC, PingFangSC-Medium, sans-serif;
font-size: 13px;
font-weight: 500;
line-height: 22px;
}
}
.el-drawer__body {
position: relative;
height: calc(100% - 46px);
overflow: hidden;
background: #fff;
box-sizing: border-box;
}
.dialog-footer {
height: 56px;
padding: 12px;
border-top: 1px solid #ebedf0;
box-sizing: border-box;
gap: 12px;
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: auto;
min-width: 60px;
padding: 0 16px;
border-radius: 6px;
font-size: 14px;
line-height: 22px;
span {
line-height: 22px;
}
}
}
}
.drawer-content-main {
width: 100%;
height: calc(100% - 56px);
overflow-y: auto;
overflow-x: hidden;
}
.drawer-content {
width: 100%;
height: auto;
padding: 12px 12px 24px;
box-sizing: border-box;
}
.reportForm {
display: flex;
flex-direction: column;
gap: 20px;
}
.role-card {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
padding: 12px;
border-radius: 4px;
background: #f6f8f9;
box-sizing: border-box;
}
.report-editor {
display: block;
width: 100%;
}
::v-deep .reportForm {
.el-form-item {
margin-bottom: 0;
}
.el-form-item__label {
display: flex;
align-items: center;
padding: 0 0 8px;
color: #6d7176;
font-family: PingFang SC, PingFangSC-Regular, sans-serif;
font-size: 13px;
font-weight: 400;
line-height: 22px;
}
.el-form-item.is-required:not(.is-no-asterisk)
> .el-form-item__label::before {
color: #f53f3f;
margin-right: 4px;
}
.el-input__inner,
.el-select .el-input__inner {
height: 34px;
padding: 5px 12px;
border-color: #d6d9e0;
border-radius: 6px;
color: #323335;
font-size: 14px;
line-height: 22px;
}
.el-input.is-disabled .el-input__inner {
border-color: #d6d9e0;
background-color: #f7f8fa;
color: #c9cdd4;
cursor: not-allowed;
}
.el-input__inner::placeholder,
.el-textarea__inner::placeholder {
color: #c9cdd4;
}
.role-type-item .el-form-item__content {
line-height: 22px;
}
.el-radio-group {
display: flex;
align-items: center;
gap: 40px;
line-height: 22px;
}
.el-radio {
margin-right: 0;
color: #323335;
font-size: 14px;
line-height: 22px;
}
.el-radio__label {
padding-left: 8px;
color: #323335;
font-size: 14px;
line-height: 22px;
}
.el-radio__input.is-checked .el-radio__inner {
border-color: #00bf8a;
background: #00bf8a;
}
.el-radio__input.is-checked + .el-radio__label {
color: #323335;
}
.el-select .el-input .el-select__caret {
color: #86909c;
font-size: 14px;
}
}
::v-deep .role-card {
.inputItem {
margin-bottom: 0;
}
.alias_list {
min-height: 34px;
padding: 0 6px;
border-color: #d6d9e0;
border-radius: 6px;
background: #fff;
box-sizing: border-box;
}
.alias_list_disabled {
background: #f7f8fa;
}
.alias_input {
height: 32px;
padding-left: 6px;
color: #323335;
font-size: 14px;
line-height: 22px;
}
.alias_input::placeholder {
color: #c9cdd4;
}
.alias_item {
margin: 3px 4px 3px 0;
}
}
.detail-form-item {
position: relative;
}
::v-deep .detail-form-item {
.el-form-item__label {
padding-bottom: 8px;
width: 100%;
}
}
.detail-label {
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
gap: 12px;
}
.view-image-btn {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
color: #00bf8a;
font-family: PingFang SC, sans-serif;
font-size: 13px;
font-weight: 400;
line-height: 22px;
user-select: none;
.el-icon-view {
font-size: 14px;
}
}
.selectItem {
height: 50px;
}
.infoSpan {
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
max-width: 250px;
height: 50px;
p {
font-size: 12px;
max-width: 100%;
line-height: 20px;
}
span {
color: #ffa81d;
}
}
.tableImage {
width: 30px;
height: 30px;
border-radius: 30px;
margin-right: 10px;
}
</style>
\ No newline at end of file
......@@ -68,30 +68,69 @@ export default {
height: 100%;
.el-tabs__header {
margin: 0;
padding: 0 15px;
// margin: 0px 15px 10px 15px !important;
padding: 2px !important;
background: #F5F6F7 !important;
border-radius: 4px !important;
box-sizing: border-box !important;
height: auto !important;
overflow: visible !important;
border: none !important;
}
.el-tabs__nav-wrap {
overflow: visible !important;
height: auto !important;
padding: 0 !important;
margin: 0 !important;
&::after {
display: none !important;
}
}
.el-tabs__nav {
width: 100% !important;
display: flex !important;
border: none !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
gap: 2px !important;
}
.el-tabs__item {
height: 40px;
line-height: 40px;
font-size: 16px;
color: #333333;
flex: 1 !important;
height: 22px !important;
line-height: 22px !important;
padding: 0 !important;
margin: 0 !important;
font-family: 'PingFang SC', sans-serif !important;
font-size: 13px !important;
font-weight: 400 !important;
color: #6D7176 !important;
text-align: center !important;
border-radius: 2px !important;
border: none !important;
transition: all 0.2s !important;
white-space: nowrap !important;
overflow: visible !important;
text-overflow: unset !important;
&.is-active {
color: #3491fa;
background: #FFFFFF !important;
color: #131920 !important;
box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.05) !important;
}
}
.el-tabs__active-bar {
background-color: #3491fa;
height: 3px;
border-radius: 1.5px;
&:first-child,
&:last-child {
margin: 0 !important;
}
}
.el-tabs__nav-wrap::after {
height: 1px;
background-color: #e4e7ed;
.el-tabs__active-bar {
display: none !important;
}
.el-tab-pane {
......@@ -100,8 +139,8 @@ export default {
}
.el-tabs__content {
height: calc(100% - 40px);
padding: 0 5px 5px;
height: calc(100% - 46px);
padding: 0;
}
}
}
......
......@@ -96,7 +96,7 @@ export default {
::v-deep .el-tabs__header {
margin-bottom: 15px;
padding: 0 10px;
padding: 0;
}
::v-deep .el-tabs__nav-wrap::after {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论