You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

726 lines
24 KiB

<script setup>
import {
ref,
reactive,
onBeforeMount,
onMounted,
onBeforeUpdate,
onBeforeUnmount,
onUnmounted,
watch,
computed
} from "vue";
import Sortable from "sortablejs";
import {useStore, mapState} from "vuex";
import JsonEditorVue from "json-editor-vue3";
import {
ec_api_send_request,
get_ec_api_request_url,
ec_api_get_suggest,
ec_api_get_suggest_timestamp
} from "@/apis/ec_api.js";
import {Eleme, Refresh} from "@element-plus/icons-vue";
import "@/VueAceEditor/aceConfig"
import {VAceEditor} from "vue3-ace-editor";
import {ElMessage} from "element-plus";
import MovieShow from "@/components/ec_api/MovieShow.vue";
import SeatStatus from "@/components/ec_api/SeatStatus.vue";
import CinemaGoods from "@/components/ec_api/CinemaGoods.vue";
import EcardLevel from "@/components/ec_api/EcardLevel.vue";
import CheckQuan from "@/components/ec_api/CheckQuan.vue";
import ApiLockBuySelector from "@/components/ec_api/ApiLockBuySelector.vue";
// 注册store
const store = useStore()
// 标签相关的定义
const tabsRef = ref(null); // tabs的Ref
const activeTab = ref(store.state.ecApiModule.first_tab_api_id); // 当前选项卡,存储的是api_id
// 表格相关的定义
let ApiTableRef = ref(null) // 表格的Ref
// const multipleSelection = ref([]) // checkbox的处理
// 定义变量用于存储接口设置的数据
const UserApiData = ref({api: {}, base_info: {}, tab: {}})
// 发送按键的loading状态
const req_loading = ref(false)
const send_btn = ref('发送')
// 这对添加券接口和售卖接口的售卖类型
const saleType = ref('ticket')
// 售卖接口中的支付类型
const payType = ref('cash')
// 表格逻辑
// 多选改变后处理逻辑
function handleParamsChange(newCheckedResult) {
// console.log('handleParamsChange', newCheckedResult)
// console.log('activeTab.value', activeTab.value)
// console.log('UserApiData.value.api[activeTab.value]', UserApiData.value.api[activeTab.value])
}
// 遍历当前标签页下的参数如果勾选则标记is_checked为true
function handleParamsSelect(val) {
UserApiData.value.api[activeTab.value].params.forEach(param => {
param['is_checked'] = val.indexOf(param) >= 0;
})
}
// 自动勾选行 切换标签后检查全部字段,如果字段的is_checked是true则在页面上勾选字段
function markIsChecked() {
if (UserApiData.value.hasOwnProperty('api') || UserApiData.value.api.length > 0) {
UserApiData.value.api[activeTab.value].params.forEach(item => {
if (item['is_checked'] === true) {
ApiTableRef.value[UserApiData.value.tab[activeTab.value]].toggleRowSelection(item, true)
} else {
ApiTableRef.value[UserApiData.value.tab[activeTab.value]].toggleRowSelection(item, false)
}
})
}
}
// 过滤生成url的必须参数,必须包含'format', 'pid', '_sig'否则后端会报错
function checkboxFilter(row) {
const special_params = ['format', 'pid', '_sig']
return special_params.indexOf(row.param) < 0;
}
// 初始逻辑数据逻辑处理
function initApiData() {
// 更新基础数据
UserApiData.value['base_info'] = {
env: store.state.ecApiModule.ec_api_data.env,
cinema: store.state.ecApiModule.ec_api_data.cinema,
channel: store.state.ecApiModule.ec_api_data.channel,
}
// 根据store.state.ecApiModule.ec_api_data.api来创建本都数据
let api_id_array = []
let api_tab = {}
if (store.state.ecApiModule.ec_api_data.api) {
store.state.ecApiModule.ec_api_data.api.forEach((item, index) => {
api_id_array.push(item.id)
api_tab[item.id] = index
if (!UserApiData.value['api'].hasOwnProperty(item.id)) {
UserApiData.value['api'][item.id] = {
'id': item.id,
'description': item.description,
'path': item.path,
'type': item.type,
'url': '',
'response': '{"root": "root"}',
'handled': '',
'sig': '',
'format': 'json',
'reload': true,
'timestamp': 0,
'params': store.state.ecApiModule.ec_api_data.api_params[item.id]
}
}
})
// 如果删除某接口后同步删除UserApiData中的数据
Object.values(UserApiData.value['api']).forEach((api) => {
if (api_id_array.indexOf(api['id']) < 0) {
delete UserApiData.value['api'][api['id']]
}
})
// 处理cid和pid
Object.keys(UserApiData.value['api']).forEach(api_id => {
UserApiData.value['api'][api_id]['params'].forEach(
param_item => {
if (param_item['param'] === 'cid') {
param_item['value'] = UserApiData.value['base_info']['cinema']
}
if (param_item['param'] === 'pid') {
param_item['value'] = UserApiData.value['base_info']['channel']
}
// 写入默认值
param_item['default'] = param_item['value']
}
)
})
// 把生成的接口数据赋给UserApiData
UserApiData.value.tab = api_tab
}
}
// 添加新字段
function addNewParams() {
UserApiData.value.api[activeTab.value].params.push({
id: Date.now(),
api_id: activeTab.value,
param: '',
value: '',
is_request: false,
is_checked: false,
is_preset: false
})
}
// 读取参数默认值
function loadDefault(param) {
console.log(param)
UserApiData.value.api[activeTab.value].reload = true
get_suggest()
}
// 切换返回数据格式
function changeFormat() {
UserApiData.value.api[activeTab.value]['params'].forEach(p => {
if (p['param'] === 'format') {
if (['json', 'xml'].indexOf(p['value']) < 0) {
alert('format参数值支持json和xml')
return
}
if (p['value'] === 'json') {
UserApiData.value.api[activeTab.value]['format'] = 'json'
p['response'] = '{"root": "root"}'
}
if (p['value'] === 'xml') {
UserApiData.value.api[activeTab.value]['format'] = 'html'
UserApiData.value.api[activeTab.value]['response'] = '<root></root>'
}
}
})
}
// 准备获取url和发送请求需要的参数
function handle_request_data() {
let data = {}
let req_data = {}
UserApiData.value.api[activeTab.value]['params'].forEach(
p => {
if (p['param'] !== '_sig' && p['is_checked'] === true) {
req_data[p['param']] = p['value']
}
}
)
data['env'] = UserApiData.value.base_info['env']
data['type'] = UserApiData.value.api[activeTab.value]['type']
data['api'] = UserApiData.value.api[activeTab.value]['path']
data['pid'] = UserApiData.value.base_info['channel']
data['cid'] = UserApiData.value.base_info['cinema']
data['params'] = JSON.stringify(req_data)
return data
}
// 生成请求的url,用于页面展示
function handle_request_url() {
console.log('UserApiData.value', UserApiData.value)
if (UserApiData.value.base_info['cinema'] === '' || UserApiData.value.base_info['channel'] === '') {
alert('请选择影院和渠道!')
return
}
const data = handle_request_data()
get_ec_api_request_url(data).then(res => {
console.log('handle_update_url', res)
UserApiData.value.api[activeTab.value]['url'] = res['url']
UserApiData.value.api[activeTab.value]['params'].forEach(p => {
if (p['param'] === '_sig') {
p['value'] = res['sig']
}
})
}).catch(err => console.log(err))
}
// 发送api请求
function send_request() {
// 处理按钮状态
req_loading.value = true
send_btn.value = '请求中'
// 处理请求
if (UserApiData.value.api[activeTab.value].format === 'json') {
UserApiData.value.api[activeTab.value].response = '{"root": "root"}'
}
if (UserApiData.value.api[activeTab.value].format === 'html' || UserApiData.value.api[activeTab.value].format === 'xml') {
UserApiData.value.api[activeTab.value].response = '<root></root>'
}
const data = handle_request_data()
ec_api_send_request(data).then(res => {
UserApiData.value.api[activeTab.value].handled = JSON.parse(res.handled)
if (res.format === 'json') {
UserApiData.value.api[activeTab.value].response = JSON.stringify(JSON.parse(res.data), null, 2)
}
if (res.format === 'xml') {
const xmlDoc = new DOMParser().parseFromString(res.data, 'application/xml');
const serializer = new XMLSerializer();
UserApiData.value.api[activeTab.value].response = serializer.serializeToString(xmlDoc);
}
// 处理按钮状态
req_loading.value = false
send_btn.value = '发送'
}
).catch(err => {
req_loading.value = false
send_btn.value = '发送'
ElMessage.error('获取请求结果失败!')
})
}
async function get_suggest(options = {sale_type: saleType.value, pay_type: payType.value}) {
if (UserApiData.value.api[activeTab.value].reload === true) {
const api_type = UserApiData.value.api[activeTab.value].type
const api = UserApiData.value.api[activeTab.value].path
const env = UserApiData.value.base_info['env']
const cid = UserApiData.value.base_info['cinema']
const pid = UserApiData.value.base_info['channel']
await ec_api_get_suggest(api_type, api, env, cid, pid, options).then(
(res) => {
console.log('get_suggest', api, res)
let result = true
if (res.length === 0) {
result = true
}
res.forEach((suggest) => {
UserApiData.value.api[activeTab.value].params.forEach((param) => {
if (suggest.result === false) {
result = false
}
if (param.param === suggest.param) {
param['value'] = suggest['value']
param['default'] = suggest['value']
param['is_checked'] = suggest['is_checked']
}
})
})
UserApiData.value.api[activeTab.value].reload = !result
}
).catch((err) => {
console.log(err)
})
}
}
async function get_timestamp(options = {sale_type: saleType.value, pay_type: payType.value}) {
const api_type = UserApiData.value.api[activeTab.value].type
const api = UserApiData.value.api[activeTab.value].path
const env = UserApiData.value.base_info['env']
const cid = UserApiData.value.base_info['cinema']
const pid = UserApiData.value.base_info['channel']
await ec_api_get_suggest_timestamp(api_type, api, env, cid, pid, options).then(
res => {
console.log('get_timestamp', res['timestamp'])
if (res['timestamp'] > UserApiData.value.api[activeTab.value].timestamp) {
UserApiData.value.api[activeTab.value].reload = true
UserApiData.value.api[activeTab.value].timestamp = res['timestamp']
}
}
).catch((err) => {
console.log(err)
})
}
// 通过传参来勾选和取消勾选字段
function check_params(checked, unchecked) {
UserApiData.value.api[activeTab.value].params.forEach(param => {
if (checked.indexOf(param.param) >= 0) {
param.is_checked = true
}
if (unchecked.indexOf(param.param) >= 0) {
param.is_checked = false
}
})
// get_suggest()
}
// 获取售卖类型并自动勾选字段
function get_sale_type(sale_type) {
console.log('sale_type', sale_type)
saleType.value = sale_type
if (UserApiData.value.api[activeTab.value].path === "seat/check-coupon") {
switch (saleType.value) {
case 'ticket':
check_params(["play_id", "price", "seat_num"], ["goods"]);
break;
case 'goods':
check_params(["goods"], ["play_id", "price", "seat_num"]);
break;
case 'all':
check_params(["play_id", "price", "seat_num", "goods"], []);
break;
}
}
if (UserApiData.value.api[activeTab.value].path === "seat/lock-buy") {
switch (saleType.value) {
case 'ticket':
check_params(["play_id", "play_update_time", "seat", "lock_flag"], ["goods"]);
break;
case 'goods':
check_params(["goods"], ["play_id", "play_update_time", "seat", "lock_flag"]);
break;
case 'all':
check_params(["play_id", "play_update_time", "seat", "lock_flag", "goods"], []);
break;
}
}
get_timestamp()
get_suggest()
markIsChecked()
}
// 设置券的回调函数
function get_quan_detail(quan, cardQuan, quanType) {
console.log('quanType', quanType)
if (quanType === 'yushouquan') {
console.log('yushouquan')
UserApiData.value.api[activeTab.value].params.forEach(param => {
if (param['param'] === 'coupons') {
console.log('coupons', quan)
param['value'] = quan.join(',')
param['is_checked'] = true
}
if (param['param'] === 'card_coupons') {
param['value'] = null
param['is_checked'] = false
}
})
} else {
UserApiData.value.api[activeTab.value].params.forEach(param => {
if (param['param'] === 'card_coupons') {
param['value'] = JSON.stringify(cardQuan)
param['is_checked'] = true
}
if (param['param'] === 'coupons') {
param['value'] = null
param['is_checked'] = false
}
})
}
console.log('get_quan_detail - saleType.value', saleType.value)
get_timestamp()
get_suggest()
markIsChecked()
}
function get_pay_type(pay_type) {
console.log('pay_type', pay_type)
payType.value = pay_type
switch (pay_type) {
case 'cash':
check_params(["cash"], ["ecard_number", "coupons", "card_coupons"]);
break;
case 'ecard':
check_params(["cash", "ecard_number"], ["coupons", "card_coupons"]);
break;
case 'yushouquan':
check_params(["cash", "coupons"], ["ecard_number", "card_coupons"]);
break;
case 'equan':
check_params(["cash", "card_coupons"], ["ecard_number", "coupons"]);
break;
}
get_timestamp()
get_suggest()
markIsChecked()
}
// 用于测试,可以添加到需要的方法中
function test() {
console.log('store.state.ecApiModule.ec_api_data', store.state.ecApiModule.ec_api_data)
console.log('UserApiData.value', UserApiData.value)
console.log('activeTab.value', activeTab.value)
console.log('ApiTableRef.value', ApiTableRef.value)
// store.state.ecApiModule.ec_api_data.api = []
store.state.ecApiModule.show_tab_area = false
}
// 计算属性用来处理场次接口获取的数据,并同步给对应的选择模块
const showList = computed(() => {
const api = UserApiData.value.api[activeTab.value]
if (api.path === 'cinema/plays') {
if (api.format === 'json') {
if (api.response === '{"root": "root"}') {
return []
} else {
return api.handled['res']['data'] ? api.handled['res']['data'] : []
}
}
if (api.format === 'xml' || api.format === 'html') {
if (api.response === '<root></root>') {
return []
} else {
return api.handled['res']['data'] ? api.handled['res']['data'] : []
}
}
}
return []
})
// 计算属性用来处理座位接口获取的数据,并同步给对应的选择模块
const seatList = computed(() => {
const api = UserApiData.value.api[activeTab.value]
if (api.path === 'play/seat-status') {
if (api.format === 'json') {
if (api.response === '{"root": "root"}') {
return []
} else {
console.log("api.handled['res']['data']", api.handled['res']['data'])
return api.handled['res']['data'] ? api.handled['res']['data'] : []
}
}
if (api.format === 'xml' || api.format === 'html') {
if (api.response === '<root></root>') {
return []
} else {
console.log("api.handled['res']['data']", api.handled['res']['data'])
return api.handled['res']['data'] ? api.handled['res']['data'] : []
}
}
}
return []
})
// 计算属性用来处理卖品接口获取的数据,并同步给对应的选择模块
const goodsList = computed(() => {
const api = UserApiData.value.api[activeTab.value]
if (api.path === 'cinema/goods') {
if (api.format === 'json') {
if (api.response === '{"root": "root"}') {
return []
} else if (String(api.handled['res']['status']) !== '1') {
console.log("api.handled['res']['data'] else-if", api.handled['res']['data'])
console.log("api.handled['res']['status']", api.handled['res']['status'])
console.log("typeof api.handled['res']['status']", typeof api.handled['res']['status'])
return []
} else {
console.log("api.handled['res']['data']", api.handled['res']['data'])
return api.handled['res']['data']
}
}
if (api.format === 'xml' || api.format === 'html') {
if (api.response === '<root></root>') {
return []
} else if (String(api.handled['res']['status']) !== '1') {
console.log("api.handled['res']['status']", api.handled['res']['status'])
console.log("typeof api.handled['res']['status']", typeof api.handled['res']['status'])
return []
} else {
console.log("api.handled['res']['data']", api.handled['res']['data'])
return api.handled['res']['data']
}
}
}
return []
})
const levelList = computed(() => {
const api = UserApiData.value.api[activeTab.value]
if (api.path === 'ecard/ecard-levels') {
if (api.format === 'json') {
if (api.response === '{"root": "root"}') {
return []
} else {
console.log("api.handled['res']['data']['ecardLevelData']", api.handled['res']['data']['ecardLevelData'])
return api.handled['res']['data']['ecardLevelData']
}
}
if (api.format === 'xml' || api.format === 'html') {
if (api.response === '<root></root>') {
return []
} else {
console.log("api.handled['res']['data']['ecardLevelData']", api.handled['res']['data']['ecardLevelData'])
return api.handled['res']['data']['ecardLevelData']
}
}
}
return []
})
// 监测ec_select_api的变化 生成新的本地数据,如果当前选中的标签被取消勾选,则选择剩下标签的第一个
watch(() => store.state.ecApiModule.ec_api_data.api, (oldValue, newValue) => {
// 接口数据变化后初始化本地数据
initApiData()
// 处理空标签逻辑
const select_api_id = store.getters['ecApiModule/ec_select_api_getter']
if (select_api_id.indexOf(activeTab.value) < 0) {
activeTab.value = select_api_id[0]
}
}, {deep: true})
// 监测activeTab, 如果切换标签页,则执行自动勾选的函数
watch(activeTab, () => {
markIsChecked()
get_timestamp()
get_suggest()
handle_request_url()
})
// 监视本地用户数据,如果数据改变,则更新url
watch(UserApiData.value, () => {
console.log('watch UserApiData', UserApiData.value)
console.log('store.state.ecApiModule.ec_api_data', store.state.ecApiModule.ec_api_data)
get_suggest()
handle_request_url()
}, {deep: true, flush: "post"})
/*
生命周期逻辑
*/
onBeforeMount(
() => {
// console.log('TabArea onBeforeMount')
// 处理请求参数生成本地数据结构
initApiData()
}
)
// 挂载后
onMounted(() => {
// console.log('TabArea onMounted')
// 勾选is_checked选项
markIsChecked()
// 处理拖拽
const elTabsHeader = tabsRef.value.$el.querySelector('.el-tabs__header .el-tabs__nav');
const sortTabs = new Sortable(elTabsHeader, {
animation: 150,
ghostClass: 'dragging',
draggable: '.el-tabs__item',
onEnd: (evt) => {
console.log('sortTabs-onEnd')
store.commit('ecApiModule/handle_sort_ec_select_api', {'newIndex': evt.newIndex, 'oldIndex': evt.oldIndex})
},
});
});
// 设置vue3-ace-editor
const ace_options = {
useWorker: false, // 启用语法检查,必须为true
enableBasicAutocompletion: false, // 自动补全
enableLiveAutocompletion: false, // 智能补全
enableSnippets: true, // 启用代码段
showPrintMargin: false, // 去掉灰色的线,printMarginColumn
highlightActiveLine: true, // 高亮行
highlightSelectedWord: true, // 高亮选中的字符
tabSize: 2, // tab锁进字符
fontSize: 14, // 设置字号
wrap: false, // 是否换行
readonly: true, // 是否可编辑
minLines: 30, // 最小行数,minLines和maxLines同时设置之后,可以不用给editor再设置高度
maxLines: 30, // 最大行数
}
</script>
<template>
<el-tabs ref="tabsRef" v-model="activeTab" type="border-card">
<el-tab-pane
v-for="(api, index) in store.state.ecApiModule.ec_api_data.api"
:key="api['id']"
:label="api['description']"
:name="api['id']"
:disabled="req_loading"
>
<CheckQuan v-if="api['path'] === 'seat/check-coupon'" @getQuan="get_quan_detail" @getSaleType="get_sale_type"
:cid="UserApiData['base_info']['cinema']"></CheckQuan>
<ApiLockBuySelector v-if="api['path'] === 'seat/lock-buy'" @getSaleType="get_sale_type"
@getPayType="get_pay_type" type="ticket"></ApiLockBuySelector>
<ApiLockBuySelector v-if="api['path'] === 'order/buy-goods'" @getSaleType="get_sale_type"
@getPayType="get_pay_type" type="goods"></ApiLockBuySelector>
<el-table
ref='ApiTableRef'
:data="UserApiData.api[api['id']].params"
@selection-change="handleParamsChange"
@select="handleParamsSelect"
:key="api['id']"
>
<el-table-column type="selection" width="50" :selectable="checkboxFilter"/>
<el-table-column label="字段名" width="200">
<template v-slot="scope">
<span v-if="scope.row.is_preset">{{ scope.row.param }}</span>
<span v-else><el-input type="text" placeholder="输入字段名" v-model="scope.row.param"></el-input></span>
</template>
</el-table-column>
<el-table-column label="字段值" width="800">
<template v-slot="scope">
<el-input type="textarea" placeholder="输入字段值" v-model="scope.row.value" rows="1"
@blur="changeFormat()"></el-input>
</template>
</el-table-column>
<el-table-column label="刷新推荐值" width="100" align="center">
<template v-slot="scope">
<el-button @click="loadDefault(scope.row.param)">
<el-icon>
<Refresh/>
</el-icon>
</el-button>
</template>
</el-table-column>
<el-table-column label="必选" width="80" align="center">
<template v-slot="scope">
<span>{{ scope.row.is_request ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" show-overflow-tooltip/>
</el-table>
<el-row class="BtnRow" style="width: 100%">
<el-col :span="2">
<el-button type="primary" @click="addNewParams">添加新字段</el-button>
</el-col>
</el-row>
<hr id="break_line"/>
<el-row class="BtnRow" style="width: 100%" :gutter="10" align="middle">
<el-col :span="2"><span style="font-weight: bold; color: #909399">请求地址:</span></el-col>
<el-col :span="12">
<el-input type="text" v-model="UserApiData.api[api['id']].url" :style="{'font-size': '18px'}"></el-input>
</el-col>
<el-col :span="2">
<el-button type="primary" @click="send_request" :loading="req_loading" :disabled="req_loading">
{{ send_btn }}
</el-button>
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<v-ace-editor v-model:value="UserApiData.api[api['id']].response" :lang="UserApiData.api[api['id']]['format']"
theme="chrome" :options="ace_options" class="vue-ace-editor" :wrap="true" style="width: 1200px"/>
<MovieShow v-if="showList.length > 0" :show="showList" :data="UserApiData.api[activeTab]"
:base_info="UserApiData.base_info"/>
<SeatStatus v-if="seatList.length > 0" :seat="seatList" :data="UserApiData.api[activeTab]"
:base_info="UserApiData.base_info"/>
<CinemaGoods v-if="goodsList.length > 0" :goods="goodsList" :data="UserApiData.api[activeTab]"
:base_info="UserApiData.base_info"/>
<EcardLevel v-if="levelList.length > 0" :level="levelList" :data="UserApiData.api[activeTab]"
:base_info="UserApiData.base_info"/>
</el-tab-pane>
</el-tabs>
<el-backtop :right="300" :bottom="100" :visibility-height="100"/>
</template>
<style scoped>
.BtnRow {
margin-top: 10px;
}
.editor {
margin-top: 20px;
margin-bottom: 20px;
}
.vue-ace-editor {
/* ace-editor默认没有高度,所以必须设置高度,或者同时设置最小行和最大行使编辑器的高度自动增高 */
margin-top: 20px;
margin-left: 20px;
border: 1px solid rgba(144, 147, 153, 0.5);
border-radius: 10px;
}
#break_line {
margin-top: 30px;
margin-bottom: 30px;
background-color: rgba(144, 147, 153, 0.5);
height: 1px;
border: none
}
</style>