提交 5e642f8d 作者: 施汉文

Merge branch 'release' into shw-feat-style

...@@ -1467,3 +1467,45 @@ export function sendEmail(data) { ...@@ -1467,3 +1467,45 @@ export function sendEmail(data) {
}) })
}) })
} }
// 获取开/合服天数
export function getServerDayApi(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/role/getServerDay',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 项目-视频分类
export function teachingVideoCategoryListApi(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/teaching_video/categoryList',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
// 视频列表
export function teachingVideoVideoListApi(data) {
return new Promise((resolve, reject) => {
cross_systemRequest({
system: 'zhangyou',
api: '/api/teaching_video/videoList',
params: data
}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
\ No newline at end of file
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="14" height="14"><path d="M14 0H0v14h14z" fill="#fff"/></mask><g mask="url(#b)" stroke="#267ef0" stroke-width=".875" stroke-linecap="round" stroke-linejoin="round"><path d="M9.333 7.525v2.45c0 2.042-.816 2.858-2.858 2.858h-2.45c-2.042 0-2.858-.816-2.858-2.858v-2.45c0-2.042.816-2.858 2.858-2.858h2.45c2.042 0 2.858.816 2.858 2.858"/><path d="M12.833 4.025v2.45c0 2.042-.816 2.858-2.858 2.858h-.642V7.525c0-2.042-.816-2.858-2.858-2.858H4.667v-.642c0-2.042.816-2.858 2.858-2.858h2.45c2.042 0 2.858.816 2.858 2.858"/></g></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h14v14H0z"/></clipPath></defs></svg>
\ No newline at end of file
import Vue from 'vue'
{/* <iconpark-icon name="icon-fuzhi"></iconpark-icon> */}
const copy = {
// 当被绑定的元素插入到DOM中时
inserted: function(el, binding) {
// 创建复制图标元素
const copyIcon = document.createElement('iconpark-icon')
// const copyIcon = document.createElement('div')
// copyIcon.setAttribute('icon-class', 'copy')
copyIcon.name='icon-fuzhi'
copyIcon.style.cursor = 'pointer'
copyIcon.style.marginLeft = '8px'
copyIcon.style.fontSize = '16px'
copyIcon.title = '点击复制'
copyIcon.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="14" height="14"><path d="M14 0H0v14h14z" fill="#fff"/></mask><g mask="url(#b)" stroke="#267ef0" stroke-width=".875" stroke-linecap="round" stroke-linejoin="round"><path d="M9.333 7.525v2.45c0 2.042-.816 2.858-2.858 2.858h-2.45c-2.042 0-2.858-.816-2.858-2.858v-2.45c0-2.042.816-2.858 2.858-2.858h2.45c2.042 0 2.858.816 2.858 2.858"/><path d="M12.833 4.025v2.45c0 2.042-.816 2.858-2.858 2.858h-.642V7.525c0-2.042-.816-2.858-2.858-2.858H4.667v-.642c0-2.042.816-2.858 2.858-2.858h2.45c2.042 0 2.858.816 2.858 2.858"/></g></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h14v14H0z"/></clipPath></defs></svg>'
// 设置元素的position为relative,确保图标的absolute定位正确
if (getComputedStyle(el).position === 'static') {
el.style.position = 'relative'
}
// 添加复制图标到元素后面
el.insertBefore(copyIcon, el.nextSibling)
// 复制功能实现
copyIcon.addEventListener('click', async function(e) {
// 阻止事件冒泡
e.stopPropagation()
try {
// 获取要复制的内容
const textToCopy = binding.value || ''
if (!textToCopy) {
Vue.prototype.$message.warning('没有可复制的内容')
return
}
// 使用现代的剪贴板API
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(textToCopy)
} else {
// 兼容旧版浏览器
const textArea = document.createElement('textarea')
textArea.value = textToCopy
textArea.style.position = 'fixed'
textArea.style.left = '-999999px'
textArea.style.top = '-999999px'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
// 执行复制命令
const success = document.execCommand('copy')
document.body.removeChild(textArea)
if (!success) {
throw new Error('复制失败')
}
}
// 显示复制成功的提示
Vue.prototype.$message.success('复制成功')
} catch (error) {
console.error('复制失败:', error)
Vue.prototype.$message.error('复制失败,请手动复制')
}
})
// 存储图标引用,以便在组件卸载时清理
el.__copyIcon = copyIcon
},
// 当指令与元素解绑时
unbind: function(el) {
// 清理事件监听器和元素
if (el.__copyIcon) {
el.__copyIcon.removeEventListener('click', null)
el.parentNode.removeChild(el.__copyIcon)
delete el.__copyIcon
}
}
}
export default copy
\ No newline at end of file
import copy from './copy.js'
const install = function(Vue) {
Vue.directive('copy', copy)
}
if (window.Vue) {
window.copy = copy
Vue.use(install); // eslint-disable-line
}
copy.install = install
export default copy
...@@ -15,6 +15,7 @@ import '@/styles/index.scss'; ...@@ -15,6 +15,7 @@ import '@/styles/index.scss';
import moment from 'moment' import moment from 'moment'
import '@/styles/tailwind.css' import '@/styles/tailwind.css'
import VConsole from 'vconsole'; import VConsole from 'vconsole';
import copy from './directive/copy'
import uploading from '@/utils/cos-upload' import uploading from '@/utils/cos-upload'
import errorHandle from '@/utils/errorHandle' import errorHandle from '@/utils/errorHandle'
import { getParams,deepClone } from '@/utils/index' import { getParams,deepClone } from '@/utils/index'
...@@ -24,7 +25,7 @@ import loadmore from '@/directive/loadmore/index.js' // 加载更多 ...@@ -24,7 +25,7 @@ import loadmore from '@/directive/loadmore/index.js' // 加载更多
import clickagain from './directive/clickagain' import clickagain from './directive/clickagain'
import permission from '@/directive/permission/index.js' // 权限判断指令 import permission from '@/directive/permission/index.js' // 权限判断指令
import scroll from '@/directive/scroll' // 下拉加载更多指令 import scroll from '@/directive/scroll' // 下拉加载更多指令
Vue.use(globalComponent).use(permission).use(clickagain).use(loadmore).use(scroll) Vue.use(globalComponent).use(permission).use(clickagain).use(loadmore).use(scroll).use(copy)
// 导入 VConsole 清理工具 // 导入 VConsole 清理工具
import '@/utils/vconsoleCleanup' import '@/utils/vconsoleCleanup'
......
...@@ -16,6 +16,7 @@ const state = { ...@@ -16,6 +16,7 @@ const state = {
}, },
avatar:'',//客服头像 avatar:'',//客服头像
userid:Cookies.get('userid'), userid:Cookies.get('userid'),
weixin_blongs_id:localStorage.getItem('weixin_blongs_id'),//客服号项目id
corp_id:'', corp_id:'',
external_userid:'', external_userid:'',
token:'', token:'',
...@@ -39,6 +40,12 @@ const state = { ...@@ -39,6 +40,12 @@ const state = {
} }
const mutations = { const mutations = {
set_weixin_blongs_id(state,weixin_blongs_id){
state.weixin_blongs_id = weixin_blongs_id
// Cookies.set('weixin_blongs_id', weixin_blongs_id)
localStorage.setItem('weixin_blongs_id', weixin_blongs_id)
},
set_userInfo(state,userInfo){ set_userInfo(state,userInfo){
state.userInfo = userInfo state.userInfo = userInfo
}, },
......
...@@ -564,6 +564,10 @@ li { ...@@ -564,6 +564,10 @@ li {
height: 100%; height: 100%;
font-size: 300px; font-size: 300px;
} }
.el-loading-spinner{
display: flex;
justify-content: center;
}
.el-loading-spinner .circular { .el-loading-spinner .circular {
width: 60px !important; width: 60px !important;
......
<template>
<div class="h-full flex flex-col">
<el-input
placeholder="请输入内容"
prefix-icon="el-icon-search"
v-model.trim="searchText"
@input="debouncedGetVideoList"
>
</el-input>
<el-cascader
class="w-full mt-[8px]"
v-model="categoryValue"
:props="{ emitPath: false, expandTrigger: 'click' }"
:options="categoryList"
@change="debouncedGetVideoList"
></el-cascader>
<div
class="mt-[2px] space-y-[8px] flex-1 overflow-auto pb-[10px]"
v-loading="loading"
>
<div
v-for="item in videoList"
:key="item.id"
class="flex justify-between items-center py-[3px] px-[8px] bottom-[1px] border border-[#E5E7EB] rounded-[4px]"
>
<div class="text-[14px]">{{ item.video_name }}</div>
<div class="flex items-center">
<el-button
type="text"
size="small"
class="text-[12px] button-color-hover"
@click="previewVideo(item)"
>
<div class="flex items-center">
<iconpark-icon
name="xiaoxicaozuo-chakan"
class="mr-[4px] text-[14px]"
></iconpark-icon>
预览
</div>
</el-button>
<div
@click="sendVideo(item)"
class="h-[24px] ml-[8px] cursor-pointer hover:bg-[#E7F1FD] text-[12px] w-[58px] p-0 flex justify-center items-center rounded-full bg-[#F7F8FA] text-[#267EF0]"
>
<iconpark-icon
name="icon-fasonghuashu"
class="text-[14px] mr-[4px]"
></iconpark-icon>
<span> 发送</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
teachingVideoVideoListApi,
teachingVideoCategoryListApi,
} from "@/api/game";
import { mapMutations, mapState } from "vuex";
import { sendChatMessage } from "@/utils/index";
import { debounce } from "@/utils";
export default {
name: "InstructionalVideo",
data() {
return {
searchText: "",
categoryValue: {},
categoryList: [],
videoList: [],
debouncedGetVideoList: () => {},
loading: false,
};
},
mounted() {
this.getCategoryList();
// 初始化防抖函数,延迟300ms执行
this.debouncedGetVideoList = debounce(() => {
this.loading = true;
this.getVideoList().finally(() => {
this.loading = false;
});
}, 300);
},
computed: {
...mapState("user", ["userInfo", "weixin_blongs_id"]),
},
methods: {
...mapMutations("common", ["set_sendSkillMessage"]),
// 视频分类
async getCategoryList() {
// return;
const { data } = await teachingVideoCategoryListApi({
weixin_blongs_id: this.weixin_blongs_id,
});
this.categoryList = this.formatCategoryList(data);
},
formatCategoryList(data) {
return data.map((item) => {
return {
value: item,
label: item.main_game_name,
children: item.category.map((item) => ({
value: item,
label: item.category_name,
})),
};
});
},
// 视频列表
async getVideoList() {
if (!this.categoryValue.id) {
return;
}
const { data } = await teachingVideoVideoListApi({
weixin_blongs_id:
this.categoryValue.weixin_blongs_id || this.weixin_blongs_id,
main_game_id: this.categoryValue.main_game_id,
video_type: this.categoryValue.id,
video_name: this.searchText,
page: 1,
page_size: 200,
});
this.videoList = data.data;
},
sendVideo(item) {
try {
const link = {
title: item.video_name,
imgUrl: item.cover_url || "",
desc: "点击观看教学视频",
link: item.video_url,
};
sendChatMessage(link, "link");
} catch (error) {
console.error("发送视频链接失败:", error);
this.$message({ message: "发送视频链接失败", type: "error" });
}
},
previewVideo(item) {
window.open(item.video_url);
},
},
};
</script>
<style scoped>
.button-color-hover.el-button--text:hover {
color: #267ef0 !important;
}
.button-color-hover.el-button--text {
color: #86909c;
}
</style>
...@@ -308,6 +308,9 @@ export default { ...@@ -308,6 +308,9 @@ export default {
showConfirmLayer: false showConfirmLayer: false
} }
}, },
computed: {
...mapState('user', ['cser_name'])
},
watch: { watch: {
show(newVal, oldVal) { show(newVal, oldVal) {
if (newVal) { if (newVal) {
...@@ -432,7 +435,6 @@ export default { ...@@ -432,7 +435,6 @@ export default {
} }
} catch (error) { } catch (error) {
console.error('处理礼包等级属性时出错:', error) console.error('处理礼包等级属性时出错:', error)
// 出错时保持level_attribute为空数组
} }
// 构建规则数据 // 构建规则数据
const rule = [{ level_attribute, id: activeInfo.id }] const rule = [{ level_attribute, id: activeInfo.id }]
...@@ -440,7 +442,7 @@ export default { ...@@ -440,7 +442,7 @@ export default {
role_id:role_id, role_id:role_id,
recharge_date:'', recharge_date:'',
remark: this.remark, remark: this.remark,
create_user: this.name, create_user: this.cser_name,
task_id: this.task_id || null, task_id: this.task_id || null,
right_type: this.activeInfo.right_type || '', right_type: this.activeInfo.right_type || '',
select_type:this.activeInfo.gift_type==7?2:'', select_type:this.activeInfo.gift_type==7?2:'',
......
...@@ -312,14 +312,14 @@ ...@@ -312,14 +312,14 @@
taskDetails, taskDetails,
taskTrack, taskTrack,
} from '@/api/game' } from '@/api/game'
import { memberBindExternalUser } from '@/api/works' import { memberBindExternalUser,clientSessionBindTaskApi } from '@/api/works'
import layer from '@/components/dialog.vue' import layer from '@/components/dialog.vue'
import SendEmailDialog from './SendEmailDialog.vue' import SendEmailDialog from './SendEmailDialog.vue'
import applyGift from '@/views/components/giftRecord/applyGift.vue' import applyGift from '@/views/components/giftRecord/applyGift.vue'
export default { export default {
computed: { computed: {
...mapState('game', ['taskDetails']), ...mapState('game', ['taskDetails']),
...mapState('user', ['userInfo']) ...mapState('user', ['userInfo','userid'])
}, },
components: { components: {
layer, layer,
......
...@@ -188,6 +188,7 @@ export default { ...@@ -188,6 +188,7 @@ export default {
"set_cser_id", "set_cser_id",
"set_cser_name", "set_cser_name",
"set_external_userid", "set_external_userid",
"set_weixin_blongs_id",
]), ]),
async initLogin() { async initLogin() {
const urlParams = getParams(); const urlParams = getParams();
...@@ -327,6 +328,7 @@ export default { ...@@ -327,6 +328,7 @@ export default {
if (res.data.userid) { if (res.data.userid) {
this.cacheuserid(res.data.userid); this.cacheuserid(res.data.userid);
this.getUserList(res.data.userid); this.getUserList(res.data.userid);
this.set_weixin_blongs_id(res.data.weixin_blongs_id);
} else { } else {
this.$message.error("获取用户id失败"); this.$message.error("获取用户id失败");
return; return;
......
...@@ -2,87 +2,81 @@ ...@@ -2,87 +2,81 @@
<div class="details columnFlex"> <div class="details columnFlex">
<div class="content search-form"> <div class="content search-form">
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane <el-tab-pane label="个人话术" name="personal">
label="个人话术"
name="personal"
>
<skillPersonal <skillPersonal
v-if="activeName === 'personal'" v-if="activeName === 'personal'"
:active-name="activeName" :active-name="activeName"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane <el-tab-pane label="企业话术" name="company">
label="企业话术" <skillCompany :active-name="activeName" />
name="company"
>
<skillCompany
:active-name="activeName"
/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane <el-tab-pane label="知识库" name="library">
label="知识库"
name="library"
>
<skillLibrary <skillLibrary
v-if="activeName === 'library'" v-if="activeName === 'library'"
:active-name="activeName" :active-name="activeName"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane <el-tab-pane label="跨主体知识库" name="robotLibrary">
label="跨主体知识库"
name="robotLibrary"
>
<crossLibrary <crossLibrary
v-if="activeName === 'robotLibrary'" v-if="activeName === 'robotLibrary'"
:active-name="activeName" :active-name="activeName"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="教学视频" name="instructionalVideo">
<InstructionalVideo
v-if="activeName === 'instructionalVideo'"
:active-name="activeName"
/>
</el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import skillCompany from './components/skill/skillCompany.vue' import skillCompany from "./components/skill/skillCompany.vue";
import skillPersonal from './components/skill/skillPersonal.vue' import skillPersonal from "./components/skill/skillPersonal.vue";
import skillLibrary from './components/skill/skillLibrary.vue' import skillLibrary from "./components/skill/skillLibrary.vue";
import crossLibrary from './components/skill/crossLibrary.vue' import crossLibrary from "./components/skill/crossLibrary.vue";
import { mapActions } from 'vuex' import InstructionalVideo from "./components/InstructionalVideo/index.vue";
import { mapActions } from "vuex";
export default { export default {
name: 'quickReply', name: "quickReply",
components: { components: {
skillCompany, skillCompany,
skillPersonal, skillPersonal,
skillLibrary, skillLibrary,
crossLibrary crossLibrary,
InstructionalVideo,
}, },
data() { data() {
return { return {
activeName: 'personal' activeName: "personal",
} };
},
created() {
}, },
created() {},
mounted() { mounted() {
this.initializeWecom() this.initializeWecom();
}, },
methods: { methods: {
...mapActions('user', ['initWecom']), ...mapActions("user", ["initWecom"]),
async initializeWecom() { async initializeWecom() {
try { try {
console.log('🚀 开始初始化企业微信 SDK') console.log("🚀 开始初始化企业微信 SDK");
const result = await this.initWecom() const result = await this.initWecom();
console.log('✅ 企业微信 SDK 初始化成功', result) console.log("✅ 企业微信 SDK 初始化成功", result);
} catch (error) { } catch (error) {
console.error('❌ 企业微信 SDK 初始化失败:', error) console.error("❌ 企业微信 SDK 初始化失败:", error);
} }
}, },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.details { .details {
::v-deep .el-tabs__nav-next,::v-deep .el-tabs__nav-prev{ ::v-deep .el-tabs__nav-next,
::v-deep .el-tabs__nav-prev {
line-height: 50px; line-height: 50px;
} }
width: 100%; width: 100%;
......
<!-- <!--
* @Author: maoxiya 937667504@qq.com * @Author: maoxiya 937667504@qq.com
* @Date: 2025-09-13 14:05:01 * @Date: 2025-09-13 14:05:01
* @LastEditors: maoxiya 937667504@qq.com * @LastEditors: 金多虾 937667504@qq.com
* @LastEditTime: 2025-10-31 17:01:13 * @LastEditTime: 2025-12-05 17:03:24
* @FilePath: /company_wx_frontend/src/views/works/component/gameInfo/vipLevel.vue * @FilePath: /company_wx_frontend/src/views/works/component/gameInfo/vipLevel.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
--> -->
...@@ -25,6 +25,9 @@ ...@@ -25,6 +25,9 @@
<div class="vipLevelItem rowFlex columnCenter" v-for="(item,index) in vipLevelBenefit" :key="index"> <div class="vipLevelItem rowFlex columnCenter" v-for="(item,index) in vipLevelBenefit" :key="index">
<p class="vipLevelItemRow" v-if="item.num" :style="{color: item.target ? '#333333' : '#c9cdd4'}" > <p class="vipLevelItemRow" v-if="item.num" :style="{color: item.target ? '#333333' : '#c9cdd4'}" >
<span v-if="item.name" class="label">{{item.name}} </span> <span v-if="item.name" class="label">{{item.name}} </span>
<span v-if="item.num && item.type!=8" :style="{color: item.target ? '#00bf8a' : '#c9cdd4'}" class="value">
{{item.remain_num || item.num }}/{{item.num}} {{item.unit || '次'}}
</span>
<!-- 人工权益显示使用按钮 --> <!-- 人工权益显示使用按钮 -->
<div v-if="item.monitor_type === 2" class="benefit-actions"> <div v-if="item.monitor_type === 2" class="benefit-actions">
<el-button <el-button
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论