初始化

This commit is contained in:
2026-03-02 13:44:38 +08:00
commit 05b785083c
677 changed files with 58662 additions and 0 deletions

View File

@@ -0,0 +1,386 @@
<template>
<a-form layout="vertical" ref="formRef" @finish="onFinish" @finishFailed="onFinishFailed" :model="activity"
:label-col="labelCol" :wrapper-col="wrapperCol">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="activeKey_content" :tab="activity_content">
<a-form-item :label="activity_type" name="type">
<a-radio-group v-model:value="activity.type">
<a-radio-button value="1">{{ activity_cycle }}</a-radio-button>
<a-radio-button value="2">{{ activity_custom }}</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item v-if="activity.type === '2'" v-model:label="RangePicker.showTime" name="range_time"
v-bind="rangeConfig">
<a-range-picker v-model:value="activity.range_time" format="YYYY-MM-DD HH:mm:ss" show-time
value-format="YYYY-MM-DD HH:mm:ss"/>
</a-form-item>
<a-form-item v-if="activity.type === '1'" :label="cycle_type"
:rules="[{ required: true, message: cycle_type_required }]"
name="cycle_type">
<a-select v-model:value="activity.cycle_type" :placeholder="placeholder_cycle_type"
style="width: 200px"
@change="handleProvinceChange">
<a-select-option v-for="(cycle_type, index) in cycleTypes" :key="cycle_type" :value="cycle_type">
{{ cycle_type }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="activity.type === '1'" :label="cycle_data"
:rules="[{ required: true, message: cycle_data_required }]"
name="cycle_data">
<a-select v-model:value="activity.cycle_data" :placeholder="placeholder_cycle_data"
style="width: 200px">
<a-select-option v-for="(cycle_data, index) in cycleDatas" :key="cycle_data" :value="index">
{{ cycle_data }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item hidden name="id">
<a-input v-model:value="activity.id" name="id" type="hidden"/>
</a-form-item>
<a-tabs v-model:activeKey="activeKey_lang_content">
<a-tab-pane v-for="(lang, lang_index) in langs" :key="lang_index" :tab="lang.value" forceRender="true">
<a-form-item :label="activity_picture" :name="['activity_content', lang.key, 'picture']"
:rules="[{ required: true, message: picture_required }]">
<a-upload v-model:file-list="activity.activity_content[lang.key].picture"
:before-upload="beforeUpload" :headers="headers"
:max-count="1" action="/ex-admin/addons-webman-controller-IndexController/activityUpload"
list-type="picture-card">
<div>
<PlusOutlined/>
<div style="margin-top: 8px">Upload</div>
</div>
</a-upload>
</a-form-item>
<a-form-item :name="['activity_content', lang.key, 'id']" hidden>
<a-input v-model:value="activity.activity_content[lang.key].id" type="hidden"/>
</a-form-item>
<a-form-item :label="activity_name" :name="['activity_content', lang.key, 'name']"
:rules="[{ required: true, message: name_required }]">
<a-input v-model:value="activity.activity_content[lang.key].name" :maxlength="100"
:rules="[{ required: true, message: activity_name_required }]"
show-count/>
</a-form-item>
<a-form-item :label="recharge_setting" :rules="[{ required: true, message: recharge_setting_required }]"
name="recharge_id">
<a-select v-model:value="activity.recharge_id" allowClear="true">
<a-select-option v-for="option in options" :key="option.id" :value="option.id">
{{ option.title }}
</a-select-option>
</a-select>
</a-form-item>
</a-tab-pane>
</a-tabs>
</a-tab-pane>
</a-tabs>
<a-form-item>
<a-button type="primary" html-type="submit">{{ Submit }}</a-button>
<a-button style="margin-left: 10px" @click="resetForm">{{ reset }}</a-button>
</a-form-item>
</a-form>
</template>
<script>
const cycleType = ['Week', 'Month'];
const cycleData = {
Week: ['星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
Month: ['1', '2', '3', '4', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31'],
};
const messages = {
//简体中文
'zh-CN': {
rangTime: '请选择开放时间',
showTime: '开放时间',
submit: '提交',
reset: '重置',
activity_content: '活动内容',
activity_picture: '活动图片',
activity_type: '活动时间模式',
activity_cycle: '周期模式',
activity_custom: '自定义模式',
activity_cycle_data: '周期数据',
activity_picture_required: '请上传活动图片',
activity_name: '活动名称',
activity_name_required: '请填写活动名称',
activity_link: '活动链接',
upload_type: '支持png, jpeg, png图片格式',
picture_required: '请上传活动图片',
name_required: '请填写活动名称',
recharge_setting: '充值配置',
recharge_setting_required: '请选择充值配置',
placeholder_cycle_type: '请选择周期模式',
placeholder_cycle_data: '请选择周期配置数据',
cycle_type_help: '周期模式下,周期类型以及周期配置为必选项',
cycle_type: '周期类型',
cycle_data: '周期配置',
cycle_type_required: '请选择周期类型',
cycle_data_required: '请选择周期配置',
},
//英文
en: {
rangTime: '请选择开放时间',
showTime: '开放时间',
submit: '提交',
reset: '重置',
activity_content: '活动内容',
activity_picture: '活动图片',
activity_type: '活动时间模式',
activity_cycle: '周期模式',
activity_custom: '自定义模式',
activity_cycle_data: '周期数据',
activity_picture_required: '请上传活动图片',
activity_name: '活动名称',
activity_name_required: '请填写活动名称',
activity_link: '活动链接',
upload_type: '支持png, jpeg, png图片格式',
upload_size: '图片最大不得超过5M',
picture_required: '请上传活动图片',
name_required: '请填写活动名称',
recharge_setting: '充值配置',
recharge_setting_required: '请选择充值配置',
placeholder_cycle_type: '请选择周期模式',
placeholder_cycle_data: '请选择周期配置数据',
cycle_type_help: '周期模式下,模式类型以及相关配置为必选项',
cycle_type: '周期类型',
cycle_data: '周期配置',
cycle_type_required: '请选择周期类型',
cycle_data_required: '请选择周期配置',
},
'Ma-my': {
rangTime: '请选择开放时间',
showTime: '开放时间',
submit: '提交',
reset: '重置',
activity_content: '活动内容',
activity_picture: '活动图片',
activity_type: '活动时间模式',
activity_cycle: '周期模式',
activity_custom: '自定义模式',
activity_cycle_data: '周期数据',
activity_picture_required: '请上传活动图片',
activity_name: '活动名称',
activity_name_required: '请填写活动名称',
activity_link: '活动链接',
upload_type: '支持png, jpeg, png图片格式',
upload_size: '图片最大不得超过5M',
picture_required: '请上传活动图片',
name_required: '请填写活动名称',
recharge_setting: '充值配置',
recharge_setting_required: '请选择充值配置',
placeholder_cycle_type: '请选择周期模式',
placeholder_cycle_data: '请选择周期配置数据',
cycle_type_help: '周期模式下,模式类型以及相关配置为必选项',
cycle_type: '周期类型',
cycle_data: '周期配置',
cycle_type_required: '请选择周期类型',
cycle_data_required: '请选择周期配置',
},
// 繁体中文
'cam_dia': {
rangTime: '请选择开放时间',
showTime: '开放时间',
submit: '提交',
reset: '重置',
activity_content: '活动内容',
activity_picture: '活动图片',
activity_type: '活动时间模式',
activity_cycle: '周期模式',
activity_custom: '自定义模式',
activity_cycle_data: '周期数据',
activity_picture_required: '请上传活动图片',
activity_name: '活动名称',
activity_name_required: '请填写活动名称',
activity_link: '活动链接',
upload_type: '支持png, jpeg, png图片格式',
upload_size: '图片最大不得超过5M',
picture_required: '请上传活动图片',
name_required: '请填写活动名称',
recharge_setting: '充值配置',
recharge_setting_required: '请选择充值配置',
placeholder_cycle_type: '请选择周期模式',
placeholder_cycle_data: '请选择周期配置数据',
cycle_type_help: '周期模式下,模式类型以及相关配置为必选项',
cycle_type: '周期类型',
cycle_data: '周期配置',
cycle_type_required: '请选择周期类型',
cycle_data_required: '请选择周期配置',
}
}
export default {
name: "socket.vue",
//可传参数
props: {
activityModel: {},
rechargeSetting: [],
langs: {},
showTime: String,
langLocale: String,
},
data() {
return {
cycleTypes: cycleType,
cycleDatas: cycleData[cycleType[1]],
activity: this.activityModel,
options: this.rechargeSetting,
activeKey: 'activeKey_content',
newTabIndex: '',
activeKey_lang_content: 0,
activeKey_lang_phase: 0,
rangeConfig: {
rules: [{
type: 'array',
required: true,
message: messages[this.langLocale]['rangTime'],
}],
},
RangePicker: Vue.reactive({
showTime: messages[this.langLocale]['showTime']
}),
labelCol: {
style: {
width: '150px',
},
},
wrapperCol: {
span: 24,
},
headers: {
authorization: localStorage.getItem("agent_ex-admin-token"),
},
Submit: messages[this.langLocale]['submit'],
reset: messages[this.langLocale]['reset'],
activity_content: messages[this.langLocale]['activity_content'],
activity_picture: messages[this.langLocale]['activity_picture'],
activity_type: messages[this.langLocale]['activity_type'],
activity_custom: messages[this.langLocale]['activity_custom'],
activity_cycle: messages[this.langLocale]['activity_cycle'],
activity_cycle_data: messages[this.langLocale]['activity_cycle_data'],
activity_name: messages[this.langLocale]['activity_name'],
activity_name_required: messages[this.langLocale]['activity_name_required'],
activity_link: messages[this.langLocale]['activity_link'],
activity_notice: messages[this.langLocale]['activity_notice'],
upload_type: messages[this.langLocale]['upload_type'],
upload_size: messages[this.langLocale]['upload_size'],
picture_required: messages[this.langLocale]['picture_required'],
name_required: messages[this.langLocale]['name_required'],
recharge_setting: messages[this.langLocale]['recharge_setting'],
recharge_setting_required: messages[this.langLocale]['recharge_setting_required'],
placeholder_cycle_type: messages[this.langLocale]['placeholder_cycle_type'],
placeholder_cycle_data: messages[this.langLocale]['placeholder_cycle_data'],
cycle_type_help: messages[this.langLocale]['cycle_type_help'],
cycle_type: messages[this.langLocale]['cycle_type'],
cycle_data: messages[this.langLocale]['cycle_data'],
cycle_type_required: messages[this.langLocale]['cycle_type_required'],
cycle_data_required: messages[this.langLocale]['cycle_data_required'],
selectedItems: Vue.ref([]),
};
},
//生命周期渲染完执行
created() {
this.newTabIndex = Vue.ref(0);
if (this.activity.id === null || this.activity.id === undefined || this.activity.id === '') {
this.activity = Vue.reactive({
id: '',
type: '2',
cycle_type: null,
cycle_data: null,
range_time: {
start_time: '',
end_time: '',
},
dateRange: ['', ''],
activity_content: {
'zh-CN': {
name: '',
lang: 'zh-CN',
picture: [],
id: '',
},
en: {
name: '',
lang: 'en',
picture: [],
id: '',
},
'Ma-my': {
name: '',
lang: 'Ma-my',
picture: [],
id: '',
},
'cam_dia': {
name: '',
lang: 'cam_dia',
picture: [],
id: '',
}
}
})
} else {
if (this.activity.type === '1') {
this.cycleDatas = cycleData[this.activity.cycle_type];
} else {
this.cycleTypes = cycleType;
this.cycleDatas = cycleData['Week'];
this.activity.cycle_type = null;
this.activity.cycle_data = null;
}
}
},
//定义函数方法
methods: {
beforeUpload(file) {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg';
if (!isJpgOrPng) {
this.$message.error(this.upload_type);
}
const isLt2M = file.size / 1024 / 1024 < 5;
if (!isLt2M) {
this.$message.error(this.upload_size);
}
return isJpgOrPng && isLt2M;
},
resetForm() {
this.$refs.formRef.resetFields();
},
onFinish(values) {
this.$request({
url: '/ex-admin/addons-webman-controller-ActivityController/activityOperate',
method: 'post',
data: values,
header: this.headers
}).then(res => {
if (res.code === 200) {
// location.reload();
}
})
},
onFinishFailed(errorInfo) {
let name = errorInfo.errorFields[0]['name'];
if (name.length > 0) {
switch (name[0]) {
case 'range_time':
this.activeKey = 'activeKey_content';
break;
case 'activity_content':
this.activeKey = 'activeKey_content';
if (name[1] !== 'undefined' && name[1] != null && name[1] !== '') {
let ac = 0;
for (let key in this.activity.activity_content) {
if (key === name[1]) {
this.activeKey_lang_content = ac;
}
ac++;
}
}
break;
}
}
},
handleProvinceChange(value) {
this.cycleDatas = cycleData[value];
},
}
}
</script>

View File

@@ -0,0 +1,322 @@
<template>
<div class="container">
<div class="login-layout">
<div class="left">
<div class="logo-container">
<img v-if="webLogo" class="logo" src="/exadmin/img/login_logo.png"/>
</div>
<div class="left-container">
<img class="ad" src="/exadmin/img/login-box-bg.9027741f.svg">
<div class="text-block">
{{ webName }}
</div>
</div>
</div>
<div class="right">
<div class="login-container">
<a-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-container">
<h3 class="title">
<span>{{ agent_login }}</span>
</h3>
</div>
<a-form-item name="username">
<a-input
v-model:value="loginForm.username"
auto-complete="on"
placeholder:enter_account
size="large"
tabindex="1"
>
<template #prefix>
<UserOutlined/>
</template>
</a-input>
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
auto-complete="on"
placeholder:enter_password
size="large"
tabindex="2"
@keyup.enter.native="handleLogin"
>
<template #prefix>
<LockOutlined/>
</template>
</a-input-password>
</a-form-item>
<div v-if="verification" style="display: flex;justify-content: space-between;">
<a-form-item name="verify" style="flex:1;margin-right: 10px">
<a-input
v-model:value="loginForm.verify"
auto-complete="on"
maxlength="4"
placeholder:enter_verify
size="large"
tabindex="3"
@keyup.enter.native="handleLogin"
>
<template #prefix>
<SafetyCertificateOutlined/>
</template>
</a-input>
</a-form-item>
<img :height="40" :src="verifyImage" class="verify" @click="getVerify"/>
</div>
<a-button :loading="loading" block size="large" type="primary" @click="handleLogin">{{ loginBtnText }}
</a-button>
</a-form>
</div>
<div class="icp"><a href="http://beian.miit.gov.cn" target="_blank">{{ webMiitbeian }}</a> | {{ webCopyright }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Agent',
props: {
webLogo: String,
webName: String,
webCopyright: String,
webMiitbeian: String,
deBug: Boolean,
agent_login: String,
admin_login: String,
enter_account: String,
enter_password: String,
enter_verify: String,
password_verify: String,
login: String,
},
data() {
const validatePassword = (rule, value, callback) => {
if (value.length < 5) {
return Promise.reject(this.password_verify)
} else {
return Promise.resolve()
}
}
return {
verification: false,
loginForm: {
username: '',
password: '',
verify: '',
hash: '',
source: 'agent',
},
loginRules: {
username: [{required: true, trigger: 'change', message: this.enter_account}],
verify: [{required: true, message: this.enter_verify}],
password: [{required: true, trigger: 'change', validator: validatePassword}]
},
loading: false,
verifyImage: '',
loginBtnText: '登录',
redirect: null,
}
},
watch: {
$route: {
handler: function (route) {
if (route.query && route.query.redirect) {
const index = route.fullPath.indexOf('?redirect=')
if (index > -1) {
this.redirect = route.fullPath.substr(index + 10)
}
}
},
immediate: true
}
},
created() {
if (this.deBug) {
this.loginForm.username = '';
this.loginForm.password = '';
}
this.getVerify()
},
methods: {
getVerify() {
this.$request({
url: 'ex-admin/login/captcha'
}).then(res => {
this.verifyImage = res.data.image
this.loginForm.hash = res.data.hash
this.verification = res.data.verification
})
},
handleLogin(data) {
this.$refs.loginForm.validate().then(() => {
this.loading = true
this.$action.login(this.loginForm).then(res => {
this.$router.push(this.redirect || '/')
}).finally(() => {
this.loading = false
}).catch(() => {
this.getVerify()
})
})
}
}
}
</script>
<style scoped>
.logo {
}
.login-layout .left {
position: relative;
width: 50%;
height: 100%;
margin-left: 150px;
}
.login-layout .left .ad {
width: 45%;
}
.login-layout .right {
position: relative;
width: 50%;
height: 100%;
}
.icp {
position: absolute;
bottom: 10px;
width: 100%;
color: #000;
opacity: .5;
font-size: 12px;
}
.icp a {
color: #000;
text-decoration: none;
}
@keyframes bg-run {
0% {
background-position-x: 0;
}
to {
background-position-x: -1920px;
}
}
.container {
position: relative;
width: 100%;
height: 100%;
min-height: 100%;
overflow: hidden;
background-color: #FFFFFF;
}
.container:before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin-left: -48%;
background-image: url("/exadmin/img/login-bg.b9f5c736.svg");
background-position: 100%;
background-repeat: no-repeat;
background-size: auto 100%;
content: "";
}
.text-block {
margin-top: 30px;
font-size: 32px;
color: #FFFFFF;
}
.logo-container {
font-size: 24px;
color: #fff;
font-weight: 700;
position: relative;
top: 50px;
margin-left: 20px;
}
.logo-container img {
width: 100px;
height: 100px;
}
.login-layout {
height: 100%;
display: flex;
position: relative;
}
.left-container {
position: absolute;
top: calc(50% - 100px);
left: 0;
right: 0;
bottom: 0;
}
.login-container {
width: 400px;
position: absolute;
top: calc(50% - 250px);
left: 0;
right: 0;
bottom: 0;
}
.login-container .login-form {
}
.login-container .tips {
font-size: 14px;
color: #fff;
}
.login-container .svg-container {
padding: 6px 5px 6px 15px;
color: #889aa4;
vertical-align: middle;
display: inline-block;
}
.login-container .title-container .title {
font-size: 26px;
font-weight: bold;
}
.login-container .show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: #889aa4;
cursor: pointer;
user-select: none;
}
.verify {
height: 40px;
cursor: pointer;
border: 1px solid #ccc;
}
</style>

View File

@@ -0,0 +1,322 @@
<template>
<div class="container">
<div class="login-layout">
<div class="left">
<div class="logo-container">
<img v-if="webLogo" class="logo" src="/exadmin/img/login_logo.png"/>
</div>
<div class="left-container">
<img class="ad" src="/exadmin/img/login-box-bg.9027741f.svg">
<div class="text-block">
{{ webName }}
</div>
</div>
</div>
<div class="right">
<div class="login-container">
<a-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-container">
<h3 class="title">
<span>{{ admin_login }}</span>
</h3>
</div>
<a-form-item name="username">
<a-input
v-model:value="loginForm.username"
auto-complete="on"
placeholder:enter_account
size="large"
tabindex="1"
>
<template #prefix>
<UserOutlined/>
</template>
</a-input>
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
auto-complete="on"
placeholder:enter_password
size="large"
tabindex="2"
@keyup.enter.native="handleLogin"
>
<template #prefix>
<LockOutlined/>
</template>
</a-input-password>
</a-form-item>
<div v-if="verification" style="display: flex;justify-content: space-between;">
<a-form-item name="verify" style="flex:1;margin-right: 10px">
<a-input
v-model:value="loginForm.verify"
auto-complete="on"
maxlength="4"
placeholder:enter_verify
size="large"
tabindex="3"
@keyup.enter.native="handleLogin"
>
<template #prefix>
<SafetyCertificateOutlined/>
</template>
</a-input>
</a-form-item>
<img :height="40" :src="verifyImage" class="verify" @click="getVerify"/>
</div>
<a-button :loading="loading" block size="large" type="primary" @click="handleLogin">{{ loginBtnText }}
</a-button>
</a-form>
</div>
<div class="icp"><a href="http://beian.miit.gov.cn" target="_blank">{{ webMiitbeian }}</a> | {{ webCopyright }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Login',
props: {
webLogo: String,
webName: String,
webCopyright: String,
webMiitbeian: String,
deBug: Boolean,
agent_login: String,
admin_login: String,
enter_account: String,
enter_password: String,
enter_verify: String,
password_verify: String,
login: String,
},
data() {
const validatePassword = (rule, value, callback) => {
if (value.length < 5) {
return Promise.reject(this.password_verify)
} else {
return Promise.resolve()
}
}
return {
verification: false,
loginForm: {
username: '',
password: '',
verify: '',
hash: '',
source: 'admin',
},
loginRules: {
username: [{required: true, trigger: 'change', message: this.enter_account}],
verify: [{required: true, message: this.enter_verify}],
password: [{required: true, trigger: 'change', validator: validatePassword}]
},
loading: false,
verifyImage: '',
loginBtnText: this.login,
redirect: null,
}
},
watch: {
$route: {
handler: function (route) {
if (route.query && route.query.redirect) {
const index = route.fullPath.indexOf('?redirect=')
if (index > -1) {
this.redirect = route.fullPath.substr(index + 10)
}
}
},
immediate: true
}
},
created() {
if (this.deBug) {
this.loginForm.username = '';
this.loginForm.password = '';
}
this.getVerify()
},
methods: {
getVerify() {
this.$request({
url: 'ex-admin/login/captcha'
}).then(res => {
this.verifyImage = res.data.image
this.loginForm.hash = res.data.hash
this.verification = res.data.verification
})
},
handleLogin(data) {
this.$refs.loginForm.validate().then(() => {
this.loading = true
this.$action.login(this.loginForm).then(res => {
this.$router.push(this.redirect || '/')
}).finally(() => {
this.loading = false
}).catch(() => {
this.getVerify()
})
})
}
}
}
</script>
<style scoped>
.logo {
}
.login-layout .left {
position: relative;
width: 50%;
height: 100%;
margin-left: 150px;
}
.login-layout .left .ad {
width: 45%;
}
.login-layout .right {
position: relative;
width: 50%;
height: 100%;
}
.icp {
position: absolute;
bottom: 10px;
width: 100%;
color: #000;
opacity: .5;
font-size: 12px;
}
.icp a {
color: #000;
text-decoration: none;
}
@keyframes bg-run {
0% {
background-position-x: 0;
}
to {
background-position-x: -1920px;
}
}
.container {
position: relative;
width: 100%;
height: 100%;
min-height: 100%;
overflow: hidden;
background-color: #FFFFFF;
}
.container:before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin-left: -48%;
background-image: url("/exadmin/img/login-bg.b9f5c736.svg");
background-position: 100%;
background-repeat: no-repeat;
background-size: auto 100%;
content: "";
}
.text-block {
margin-top: 30px;
font-size: 32px;
color: #FFFFFF;
}
.logo-container {
font-size: 24px;
color: #fff;
font-weight: 700;
position: relative;
top: 50px;
margin-left: 20px;
}
.logo-container img {
width: 100px;
height: 100px;
}
.login-layout {
height: 100%;
display: flex;
position: relative;
}
.left-container {
position: absolute;
top: calc(50% - 100px);
left: 0;
right: 0;
bottom: 0;
}
.login-container {
width: 400px;
position: absolute;
top: calc(50% - 250px);
left: 0;
right: 0;
bottom: 0;
}
.login-container .login-form {
}
.login-container .tips {
font-size: 14px;
color: #fff;
}
.login-container .svg-container {
padding: 6px 5px 6px 15px;
color: #889aa4;
vertical-align: middle;
display: inline-block;
}
.login-container .title-container .title {
font-size: 26px;
font-weight: bold;
}
.login-container .show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: #889aa4;
cursor: pointer;
user-select: none;
}
.verify {
height: 40px;
cursor: pointer;
border: 1px solid #ccc;
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div>
<a-tag color="processing" v-model="machine_status" v-if="isOnline">
<template #icon>
<sync-outlined :spin="true"/>
</template>
在线
</a-tag>
<a-tag color="default" v-model="machine_status" v-else>
<template #icon>
<minus-circle-outlined/>
</template>
离线
</a-tag>
</div>
</template>
<script>
export default {
name: "machine_status.vue",
props: {
id: String,
type: String,
department_id: String,
ws: String,
machine_status: String,
},
data() {
return {
isOnline: false
};
},
created() {
if (this.machine_status === 'online') {
this.isOnline = true;
}
if (this.ws) {
this.$nextTick(() => {
this.loadScript('/plugin/webman/push/push.js').then(() => {
let connection = new Push({
url: this.ws,
app_key: '20f94408fc4c52845f162e92a253c7a3',
auth: '/plugin/webman/push/auth'
});
let type = this.type;
let machine_id = this.id;
let department_id = this.department_id;
let group_channel = connection.subscribe('private-admin_group-' + type + '-' + department_id + '-' + machine_id);
group_channel.on('message', (data) => {
let content = JSON.parse(data.content);
switch (content.msg_type) {
case 'machine_now_status':
this.isOnline = content.machine_status === 'online';
break;
}
});
});
});
}
},
methods: {
loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
}
}
</script>

View File

@@ -0,0 +1,206 @@
<template>
<div class="media_btn">
<a-button shape="circle" @click="showActionDrawer" size="small">
<template #icon>
<interaction-outlined/>
</template>
</a-button>
<a-button shape="circle" @click="isActive = !isActive" size="small">
<template #icon>
<sync-outlined/>
</template>
</a-button>
<a-button shape="circle" @click="showDrawer" size="small">
<template #icon>
<play-circle-outlined/>
</template>
</a-button>
</div>
<div :class="{ animate_left:is_move }">
<a-spin :spinning="spinning" wrapperClassName="iframe_video">
<iframe
class="iframe_box"
:src="iframe_src"
@load="handleIframeLoad" sandbox='allow-scripts allow-same-origin allow-popups' ref="media" frameborder="0"
allowfullscreen v-bind:class="{ active: isActive }" id="my-iframe"
:key="refreshKey"></iframe>
</a-spin>
</div>
<a-drawer
:title="play_address"
placement="right"
:closable="false"
:visible="action_visible"
:get-container="false"
:style="{ position: 'absolute' }"
@close="onClose"
width="44%"
:maskStyle="{opacity:0}"
>
<a-button type="dashed" @click="handleMenuClick(item.key)" shape="round" size="small" v-for="(item) in action_list"
:key="item.key" style="margin: 6px">
<template #icon>
<tool-outlined/>
</template>
{{ item.action }}
</a-button>
</a-drawer>
<a-drawer
:title="play_address"
placement="right"
:closable="false"
:visible="visible"
:get-container="false"
:style="{ position: 'absolute' }"
@close="onClose"
width="44%"
:maskStyle="{opacity:0}"
>
<a-list item-layout="horizontal" :data-source="iframe_list">
<template #renderItem="{ item,index }">
<a-list-item>
<a-list-item-meta :description="item.desc">
<template #title>
<a href="javascript:void(0);" @click="changeMedia(item.src, index)"
:class="index === typeSelected ?'active_media':''">{{ item.title }}</a>
</template>
<template #avatar>
<video-camera-add-outlined/>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-drawer>
<div>
<a-modal v-model:visible="open_visible" title="自定义开分" @ok="openAnyPoint" destroyOnClose="true"
maskClosable="true" width="300px">
<a-input-number v-model:value="open_any_point_value" addon-before="+" addon-after="point" :max="5000" :min="0"
:step="1" :precision="0"></a-input-number>
</a-modal>
</div>
</template>
<script>
export default {
props: {
iframe_src: String,
btn_text: String,
iframe_list: String,
play_address: String,
type: String,
machine_id: String,
action_list: []
},
data() {
return {
spinning: true,
isActive: true,
refreshKey: 0,
visible: false,
typeSelected: 0,
open_visible: false,
open_any_point_value: null,
open_any_point_cmd: '4A',
is_move: false,
action_visible: false,
};
},
methods: {
handleIframeLoad() {
this.spinning = false;
},
showDrawer() {
this.visible = true;
this.is_move = true;
},
showActionDrawer() {
this.action_visible = true;
this.is_move = true;
},
onClose() {
this.visible = false;
this.action_visible = false;
this.is_move = false;
},
handleMenuClick(cmd) {
if (cmd === this.open_any_point_cmd) {
this.open_visible = true;
} else {
this.sendCmd(cmd);
}
},
sendCmd(cmd, data = null) {
this.$request({
url: 'ex-admin/system/doMachineCmd',
method: 'post',
data: {
'cmd': cmd,
'data': data,
'machine_id': this.machine_id,
},
}).then(res => {
if (res.code === 200) {
this.$message.success('操作成功');
} else {
this.$message.error(res.message ? res.message : res.msg);
}
}).catch(error => {
this.$message.error(error.message);
})
},
openAnyPoint() {
this.sendCmd(this.open_any_point_cmd, this.open_any_point_value)
this.open_visible = false;
},
changeMedia(src, index) {
this.$props.iframe_src = src;
this.isActive = false;
this.refreshKey++
this.visible = false;
this.open_visible = false;
this.typeSelected = index;
},
},
};
</script>
<style>
.active {
transform: rotate(270deg)
}
.active_media {
color: rgb(24, 144, 255) !important;
}
.iframe_video {
width: 527px !important;
height: 536px !important;
overflow: hidden !important;
display: flex !important;
margin: 0 auto !important;
}
.animate_left {
animation: left-move 1s ease-in-out;
animation-fill-mode: forwards
}
@keyframes left-move {
to {
transform: translateX(-131px);
}
}
.iframe_box {
width: 527px;
height: 357px;
margin-top: 93px;
display: flex;
}
.media_btn {
margin-top: -63px;
position: fixed;
margin-left: 382px;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<v-md-editor
v-model="value"
:disabled-menus="[]"
@upload-image="handleUploadImage"
height="500px"
/>
</template>
<script>
export default {
props:{
value:String,
},
data() {
return {
value: '',
};
},
setup(props, ctx) {
const value = Vueuse.useVModel(props, 'value',ctx.emit)
return {
value
}
},
methods: {
handleUploadImage(event, insertImage, files) {
// 此处只做示例
const FormData1=new FormData()
FormData1.append("file",files[0])
this.$request.post("ex-admin/addons-webman-controller-IndexController/myEditorUpload", FormData1,{
'Content-Type': 'multipart/form-data'
}).then(response=>{
console.log(response.data)
insertImage({
url:response.data.url
});
})
},
},
};
</script>

View File

@@ -0,0 +1,350 @@
<template>
<a-badge :count="count" showZeros="true">
<a-button shape="circle" type="ghost" style="color:white" @click="showModal">
<template #icon>
<MessageOutlined/>
</template>
</a-button>
</a-badge>
<a-drawer
v-model:visible="visible"
:title="title"
placement="right"
:destroyOnClose="true"
@close="closeDrawer()"
width="500px"
>
<div>
<div v-if="!timelineData.length && is_empty">
<a-empty/>
</div>
<a-skeleton :loading="loading" active>
<div v-if="timelineData.length" class="timeline-container" @scroll="handleScroll"
style="height: 800px;overflow: scroll;overflow-x: hidden; padding: 10px">
<a-timeline mode="alternate" pending="Recording...">
<a-timeline-item v-for="item in timelineData" :key="item.id" :color="getColor(item.type)">
<template #dot v-if="item.type === 6 || item.type === 5">
<ClockCircleOutlined style="font-size: 16px"/>
</template>
<a-card hoverable size="small" bodyStyle="text-align:left" @click="pageJump(item.url, item.source_id)">
<div style="display: inline-flex">
<span style="font-size: 12px;width: 128px;line-height: 23px">{{ item.created_at }}</span>
<a-tag v-if="item.type == 7" :color="item.status == 1 ? 'green' : 'red'"
style="height: 23px;border-radius: 4px">
{{ item.status == 1 ? online : untreated }}
</a-tag>
<a-tag v-else-if="item.type == 9 || item.type == 8 || item.type == 10"
:color="item.machine_status == 0 ? 'red' : 'green'" style="height: 23px;border-radius: 4px">
{{ item.machine_status == 0 ? untreated : processed }}
</a-tag>
<a-tag v-else-if="item.type == 20"
:color="item.machine_status == 1 ? 'red' : 'green'" style="height: 23px;border-radius: 4px">
{{ item.machine_status == 1 ? lock : open }}
</a-tag>
<a-tag v-else :color="item.type == 7 ? 'red' : 'green'" style="height: 23px;border-radius: 4px">
{{ item.type == 7 ? offline : processed }}
</a-tag>
</div>
<p style="font-weight: 700;margin-top: 5px">{{ item.title }}</p>
<p style="font-size: 11px">{{ item.content }}</p>
</a-card>
</a-timeline-item>
</a-timeline>
</div>
</a-skeleton>
</div>
</a-drawer>
<audio controls="controls" hidden muted src="/audio/activity_examine.mp3" ref="activity_examine_audio"></audio>
<audio controls="controls" hidden muted src="/audio/lottery_examine.mp3" ref="lottery_examine_audio"></audio>
<audio controls="controls" hidden muted src="/audio/recharge_examine.mp3" ref="recharge_examine_audio"></audio>
<audio controls="controls" hidden muted src="/audio/withdraw_examine.mp3" ref="withdraw_examine_audio"></audio>
</template>
<style>
.action_content {
height: 8px
}
</style>
<script>
const messages = {
//简体中文
'zh-CN': {
message: {
player_examine_recharge_order: '有新的充值订单需要审核!',
player_create_withdraw_order: '有新的提现订单需要审核!',
player_examine_activity_bonus: '当前存在待审核的活动奖励请尽快审核!',
player_examine_lottery: '当前存在待审核的彩金奖励请尽快审核!',
machine_online: '机台设备离线, 请尽快检查!',
machine_lock: '机台设备上下分异常(锁定), 请尽快检查!',
online: '在线',
offline: '离线',
processed: '已处理',
untreated: '未处理',
online_machine_info: '机台信息',
lock: '锁定',
open: '开启',
}
},
//英文
en: {
message: {
player_examine_recharge_order: 'There are new recharge orders that need to be reviewed',
player_create_withdraw_order: 'There are new withdrawal orders that need to be approved',
player_examine_activity_bonus: 'There are currently pending activity rewards for review. Please review them as soon as possible',
player_examine_lottery: 'There are currently lottery awards to be reviewed, please review as soon as possible',
machine_online: 'Machine equipment offline, please check as soon as possible!',
machine_lock: 'The upper and lower parts of the machine equipment are abnormal (locked), please check as soon as possible!',
online: 'on line',
offline: 'off line',
processed: 'processed',
untreated: 'untreated',
online_machine_info: 'Machine information',
lock: 'Lock',
open: 'Open',
}
},
jp: {
message: {
player_examine_recharge_order: '新規チャージ注文がある場合はレビューが必要です',
player_create_withdraw_order: '新規引出注文がある場合はレビューが必要です!',
player_examine_activity_bonus: '現在レビュー対象のアクティビティインセンティブがあります。できるだけ早くレビューしてください!',
player_examine_lottery: '現在レビュー対象のカラー報酬が存在します。できるだけ早くレビューしてください',
machine_online: '机台設備がオフラインになっているので、できるだけ早くチェックしてください!',
machine_lock: '机台設備の上下に異常(ロック)があるので、できるだけ早くチェックしてください!',
online: 'オンライン',
offline: 'オフライン',
processed: '処理済み',
untreated: '未処理',
online_machine_info: 'きょくだいじょうほう',
lock: 'Lock',
open: 'Open',
}
},
// 繁体中文
'zh-TW': {
message: {
player_examine_recharge_order: '有新的充值訂單需要審核!',
player_create_withdraw_order: '有新的提現訂單需要審核!',
player_examine_activity_bonus: '當前存在待審核的活動獎勵請盡快審核!',
player_examine_lottery: '當前存在待審核的彩金獎勵請盡快審核!',
machine_online: '機台設備離線, 請盡快檢查!',
machine_lock: '機台設備上下分异常(鎖定),請儘快檢查!',
online: '在線',
offline: '離線',
processed: '已處理',
untreated: '未處理',
online_machine_info: '機台信息',
lock: '鎖定',
open: '開啟',
}
}
}
export default {
name: "socket.vue",
//可传参数
props: {
id: String,
type: String,
department_id: String,
count: String,
lang: String,
topShow: String,
ws: String,
examine_withdraw: String,
examine_recharge: String,
examine_activity: String,
examine_lottery: String,
machine: String,
title: String,
},
data() {
return {
visible: false,
timelineData: [],
page: 1,
size: 20,
is_empty: false,
loading: true,
online: '',
offline: '',
open: '',
lock: '',
processed: '',
untreated: '',
connection: null
};
},
//生命周期渲染完执行
created() {
this.online = messages[this.lang].message.online;
this.offline = messages[this.lang].message.offline;
this.processed = messages[this.lang].message.processed;
this.untreated = messages[this.lang].message.untreated;
this.lock = messages[this.lang].message.lock;
this.open = messages[this.lang].message.open;
let this_p = this;
// 初始化时间线的初始数据
if (this.ws && (this.examine_withdraw === true || this.examine_recharge === true || this.examine_activity === true || this.examine_lottery === true || this.machine === true)) {
this.$script('/plugin/webman/push/push.js').then(() => {
this_p.connection = new Push({
url: this.ws, // websocket地址
app_key: '20f94408fc4c52845f162e92a253c7a3',
auth: '/plugin/webman/push/auth' // 订阅鉴权(仅限于私有频道)
});
let lang = this.lang;
let admin_id = this.id;
let type = this.type;
let examine_withdraw = this.examine_withdraw;
let examine_recharge = this.examine_recharge;
let department_id = this.department_id;
let admin_channel = this_p.connection.subscribe('private-' + type + '-' + department_id + '-' + admin_id);
let group_channel = this_p.connection.subscribe('private-admin_group-' + type + '-' + department_id);
let that = this;
let title = '';
let router = '';
let params = '';
let description = '';
admin_channel.on('message', function (data) {
let content = JSON.parse(data.content);
switch (content.msg_type) {
case 'machine_action_result':
that.$notification.info({
message: messages[lang].message.online_machine_info,
description: content.description.split('\n').map((paragraph) => {
return Vue.createVNode('p', {class: 'action_content'}, paragraph);
}),
});
break;
default:
that.openNotification(title, router, description, params);
break;
}
});
group_channel.on('message', function (data) {
let content = JSON.parse(data.content);
switch (content.msg_type) {
case 'player_create_withdraw_order':
if (examine_withdraw === true) {
title = messages[lang].message.player_create_withdraw_order;
router = '/ex-admin/addons-webman-controller-ChannelWithdrawRecordController/examineList';
params = content.tradeno
// 语言播报
that.startPlay('withdraw_examine');
that.openNotification(title, router, description, params);
}
break;
case 'player_examine_recharge_order':
if (examine_recharge === true) {
title = messages[lang].message.player_examine_recharge_order;
router = '/ex-admin/addons-webman-controller-ChannelRechargeRecordController/examineList';
params = content.tradeno
// 语言播报
that.startPlay('recharge_examine');
that.openNotification(title, router, description, params);
}
break;
}
});
});
}
},
beforeUnmount() {
if (this.connection) {
this.connection.disconnect();
this.connection = null;
}
},
//定义函数方法
methods: {
openNotification(title, router, description = '', params) {
this.$notification.info({
message: title,
description: description,
onClick: () => {
this.$router.push({path: router, query: {tradeno: params}})
},
});
},
openNotificationErro(title, router, description = '', params) {
this.$notification.error({
message: title,
description: description,
onClick: () => {
this.$router.push({path: router, query: {tradeno: params}})
},
});
},
async startPlay(v) {
this.$nextTick(() => {
this.$refs[`${v}_audio`].muted = false;
this.$refs[`${v}_audio`].currentTime = 0;
this.$refs[`${v}_audio`].play();
})
},
async startPlayLottery() {
this.$nextTick(() => {
this.$refs.lottery_examine_audio.muted = false;
this.$refs.lottery_examine_audio.currentTime = 0;
this.$refs.lottery_examine_audio.play();
})
},
showModal() {
this.visible = true;
this.loadMore();
},
closeDrawer() {
this.timelineData = [];
this.page = 1;
this.is_empty = false;
this.loading = true;
},
handleScroll() {
const container = document.querySelector('.timeline-container');
if (container.scrollTop + container.clientHeight >= container.scrollHeight) {
this.page = this.page + 1;
this.loadMore();
}
},
loadMore() {
this.$request({
url: 'ex-admin/system/noticeList',
method: 'post',
data: {
'page': this.page,
'size': this.size,
},
}).then(response => {
this.loading = false;
if (response.data.length > 0) {
this.timelineData = this.timelineData.concat(response.data);
} else {
this.is_empty = true;
}
}).catch(error => {
console.error(error);
});
},
getColor(type) {
switch (type) {
case 3:
return 'green'
case 4:
return 'gray'
case 5:
return 'green'
case 6:
return 'orange'
case 7:
return 'red'
}
},
pageJump(url, source_id) {
this.closeDrawer()
this.visible = false;
this.$router.push({
path: '/' + url,
params: {source_id: source_id}
})
},
}
}
</script>